diff --git a/Cargo.lock b/Cargo.lock index ed8e31e9b5..9f0bcd44a0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1705,10 +1705,34 @@ source = "git+https://github.com/cartridge-gg/cainome?tag=v0.2.5#54df2a4114c0c61 dependencies = [ "anyhow", "async-trait", - "cainome-cairo-serde", - "cainome-parser", - "cainome-rs", - "cainome-rs-macro", + "cainome-cairo-serde 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.2.5)", + "cainome-parser 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.2.5)", + "cainome-rs 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.2.5)", + "cainome-rs-macro 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.2.5)", + "camino", + "clap", + "clap_complete", + "convert_case 0.6.0", + "serde", + "serde_json", + "starknet 0.9.0", + "thiserror", + "tracing", + "tracing-subscriber", + "url", +] + +[[package]] +name = "cainome" +version = "0.2.3" +source = "git+https://github.com/cartridge-gg/cainome?tag=v0.2.6#07ba9c91baa2f6aa2bc850b6699ffc2266be9a13" +dependencies = [ + "anyhow", + "async-trait", + "cainome-cairo-serde 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.2.6)", + "cainome-parser 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.2.6)", + "cainome-rs 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.2.6)", + "cainome-rs-macro 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.2.6)", "camino", "clap", "clap_complete", @@ -1731,6 +1755,15 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cainome-cairo-serde" +version = "0.1.0" +source = "git+https://github.com/cartridge-gg/cainome?tag=v0.2.6#07ba9c91baa2f6aa2bc850b6699ffc2266be9a13" +dependencies = [ + "starknet 0.9.0", + "thiserror", +] + [[package]] name = "cainome-parser" version = "0.1.0" @@ -1744,14 +1777,43 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cainome-parser" +version = "0.1.0" +source = "git+https://github.com/cartridge-gg/cainome?tag=v0.2.6#07ba9c91baa2f6aa2bc850b6699ffc2266be9a13" +dependencies = [ + "convert_case 0.6.0", + "quote", + "serde_json", + "starknet 0.9.0", + "syn 2.0.55", + "thiserror", +] + [[package]] name = "cainome-rs" version = "0.1.0" source = "git+https://github.com/cartridge-gg/cainome?tag=v0.2.5#54df2a4114c0c61359c2f1a70bc7e5fb57d9eaf2" dependencies = [ "anyhow", - "cainome-cairo-serde", - "cainome-parser", + "cainome-cairo-serde 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.2.5)", + "cainome-parser 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.2.5)", + "proc-macro2", + "quote", + "serde_json", + "starknet 0.9.0", + "syn 2.0.55", + "thiserror", +] + +[[package]] +name = "cainome-rs" +version = "0.1.0" +source = "git+https://github.com/cartridge-gg/cainome?tag=v0.2.6#07ba9c91baa2f6aa2bc850b6699ffc2266be9a13" +dependencies = [ + "anyhow", + "cainome-cairo-serde 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.2.6)", + "cainome-parser 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.2.6)", "proc-macro2", "quote", "serde_json", @@ -1766,9 +1828,26 @@ version = "0.1.0" source = "git+https://github.com/cartridge-gg/cainome?tag=v0.2.5#54df2a4114c0c61359c2f1a70bc7e5fb57d9eaf2" dependencies = [ "anyhow", - "cainome-cairo-serde", - "cainome-parser", - "cainome-rs", + "cainome-cairo-serde 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.2.5)", + "cainome-parser 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.2.5)", + "cainome-rs 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.2.5)", + "proc-macro2", + "quote", + "serde_json", + "starknet 0.9.0", + "syn 2.0.55", + "thiserror", +] + +[[package]] +name = "cainome-rs-macro" +version = "0.1.0" +source = "git+https://github.com/cartridge-gg/cainome?tag=v0.2.6#07ba9c91baa2f6aa2bc850b6699ffc2266be9a13" +dependencies = [ + "anyhow", + "cainome-cairo-serde 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.2.6)", + "cainome-parser 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.2.6)", + "cainome-rs 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.2.6)", "proc-macro2", "quote", "serde_json", @@ -3574,7 +3653,7 @@ name = "dojo-bindgen" version = "0.7.0-alpha.1" dependencies = [ "async-trait", - "cainome", + "cainome 0.2.3 (git+https://github.com/cartridge-gg/cainome?tag=v0.2.5)", "camino", "chrono", "convert_case 0.6.0", @@ -3741,7 +3820,7 @@ dependencies = [ "assert_fs", "assert_matches", "async-trait", - "cainome", + "cainome 0.2.3 (git+https://github.com/cartridge-gg/cainome?tag=v0.2.5)", "cairo-lang-filesystem", "cairo-lang-project", "cairo-lang-starknet", @@ -11316,6 +11395,8 @@ dependencies = [ "alloy-primitives", "anyhow", "assert_matches", + "async-trait", + "cainome 0.2.3 (git+https://github.com/cartridge-gg/cainome?tag=v0.2.6)", "cfg-if", "clap", "clap_complete", @@ -11329,6 +11410,7 @@ dependencies = [ "katana-rpc-api", "serde_json", "shellexpand", + "starknet 0.9.0", "starknet_api", "tokio", "tracing", @@ -11344,7 +11426,7 @@ dependencies = [ "assert_fs", "async-trait", "bigdecimal 0.4.3", - "cainome", + "cainome 0.2.3 (git+https://github.com/cartridge-gg/cainome?tag=v0.2.5)", "cairo-lang-compiler", "cairo-lang-defs", "cairo-lang-filesystem", @@ -11399,7 +11481,7 @@ dependencies = [ "anyhow", "assert_fs", "async-trait", - "cainome", + "cainome 0.2.3 (git+https://github.com/cartridge-gg/cainome?tag=v0.2.5)", "cairo-lang-compiler", "cairo-lang-defs", "cairo-lang-filesystem", diff --git a/artifacts/contract.abi.json b/artifacts/contract.abi.json new file mode 100644 index 0000000000..5ac0f4f06d --- /dev/null +++ b/artifacts/contract.abi.json @@ -0,0 +1,63 @@ +[ + { + "type": "impl", + "name": "ITestImpl", + "interface_name": "package_name::ITest" + }, + { + "type": "struct", + "name": "core::array::Span::", + "members": [ + { + "name": "snapshot", + "type": "@core::array::Array::" + } + ] + }, + { + "type": "struct", + "name": "package_name::TxCall", + "members": [ + { + "name": "to", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "selector", + "type": "core::felt252" + }, + { + "name": "calldata", + "type": "core::array::Span::" + } + ] + }, + { + "type": "interface", + "name": "package_name::ITest", + "items": [ + { + "type": "function", + "name": "ev", + "inputs": [], + "outputs": [ + { + "type": "package_name::TxCall" + } + ], + "state_mutability": "view" + } + ] + }, + { + "type": "constructor", + "name": "constructor", + "inputs": [] + }, + { + "type": "event", + "name": "package_name::c1::Event", + "kind": "enum", + "variants": [] + } +] diff --git a/artifacts/orderbook.abi.json b/artifacts/orderbook.abi.json new file mode 100644 index 0000000000..667d00b5df --- /dev/null +++ b/artifacts/orderbook.abi.json @@ -0,0 +1,671 @@ +[ + { + "type": "impl", + "name": "ImplOrderbook", + "interface_name": "ark_orderbook::orderbook::Orderbook" + }, + { + "type": "enum", + "name": "ark_common::protocol::order_types::RouteType", + "variants": [ + { + "name": "Erc20ToErc721", + "type": "()" + }, + { + "name": "Erc721ToErc20", + "type": "()" + } + ] + }, + { + "type": "struct", + "name": "core::integer::u256", + "members": [ + { + "name": "low", + "type": "core::integer::u128" + }, + { + "name": "high", + "type": "core::integer::u128" + } + ] + }, + { + "type": "enum", + "name": "core::option::Option::", + "variants": [ + { + "name": "Some", + "type": "core::integer::u256" + }, + { + "name": "None", + "type": "()" + } + ] + }, + { + "type": "struct", + "name": "core::array::Span::", + "members": [ + { + "name": "snapshot", + "type": "@core::array::Array::" + } + ] + }, + { + "type": "struct", + "name": "ark_orderbook::order::order_v1::OrderV1", + "members": [ + { + "name": "route", + "type": "ark_common::protocol::order_types::RouteType" + }, + { + "name": "currency_address", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "currency_chain_id", + "type": "core::felt252" + }, + { + "name": "salt", + "type": "core::felt252" + }, + { + "name": "offerer", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "token_chain_id", + "type": "core::felt252" + }, + { + "name": "token_address", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "token_id", + "type": "core::option::Option::" + }, + { + "name": "quantity", + "type": "core::integer::u256" + }, + { + "name": "start_amount", + "type": "core::integer::u256" + }, + { + "name": "end_amount", + "type": "core::integer::u256" + }, + { + "name": "start_date", + "type": "core::integer::u64" + }, + { + "name": "end_date", + "type": "core::integer::u64" + }, + { + "name": "broker_id", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "additional_data", + "type": "core::array::Span::" + } + ] + }, + { + "type": "struct", + "name": "ark_common::crypto::signer::SignInfo", + "members": [ + { + "name": "user_pubkey", + "type": "core::felt252" + }, + { + "name": "user_sig_r", + "type": "core::felt252" + }, + { + "name": "user_sig_s", + "type": "core::felt252" + } + ] + }, + { + "type": "enum", + "name": "ark_common::crypto::signer::Signer", + "variants": [ + { + "name": "WEIERSTRESS_STARKNET", + "type": "ark_common::crypto::signer::SignInfo" + } + ] + }, + { + "type": "struct", + "name": "ark_common::protocol::order_types::CancelInfo", + "members": [ + { + "name": "order_hash", + "type": "core::felt252" + }, + { + "name": "canceller", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "token_chain_id", + "type": "core::felt252" + }, + { + "name": "token_address", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "token_id", + "type": "core::option::Option::" + } + ] + }, + { + "type": "enum", + "name": "core::option::Option::", + "variants": [ + { + "name": "Some", + "type": "core::felt252" + }, + { + "name": "None", + "type": "()" + } + ] + }, + { + "type": "struct", + "name": "ark_common::protocol::order_types::FulfillInfo", + "members": [ + { + "name": "order_hash", + "type": "core::felt252" + }, + { + "name": "related_order_hash", + "type": "core::option::Option::" + }, + { + "name": "fulfiller", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "token_chain_id", + "type": "core::felt252" + }, + { + "name": "token_address", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "token_id", + "type": "core::option::Option::" + }, + { + "name": "fulfill_broker_address", + "type": "core::starknet::contract_address::ContractAddress" + } + ] + }, + { + "type": "enum", + "name": "ark_common::protocol::order_types::OrderType", + "variants": [ + { + "name": "Listing", + "type": "()" + }, + { + "name": "Auction", + "type": "()" + }, + { + "name": "Offer", + "type": "()" + }, + { + "name": "CollectionOffer", + "type": "()" + } + ] + }, + { + "type": "interface", + "name": "ark_orderbook::orderbook::Orderbook", + "items": [ + { + "type": "function", + "name": "whitelist_broker", + "inputs": [ + { + "name": "broker_id", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "create_order", + "inputs": [ + { + "name": "order", + "type": "ark_orderbook::order::order_v1::OrderV1" + }, + { + "name": "signer", + "type": "ark_common::crypto::signer::Signer" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "cancel_order", + "inputs": [ + { + "name": "cancel_info", + "type": "ark_common::protocol::order_types::CancelInfo" + }, + { + "name": "signer", + "type": "ark_common::crypto::signer::Signer" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "fulfill_order", + "inputs": [ + { + "name": "fulfill_info", + "type": "ark_common::protocol::order_types::FulfillInfo" + }, + { + "name": "signer", + "type": "ark_common::crypto::signer::Signer" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "get_order_type", + "inputs": [ + { + "name": "order_hash", + "type": "core::felt252" + } + ], + "outputs": [ + { + "type": "ark_common::protocol::order_types::OrderType" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "get_order_status", + "inputs": [ + { + "name": "order_hash", + "type": "core::felt252" + } + ], + "outputs": [ + { + "type": "core::felt252" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "get_auction_expiration", + "inputs": [ + { + "name": "order_hash", + "type": "core::felt252" + } + ], + "outputs": [ + { + "type": "core::integer::u64" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "get_order", + "inputs": [ + { + "name": "order_hash", + "type": "core::felt252" + } + ], + "outputs": [ + { + "type": "ark_orderbook::order::order_v1::OrderV1" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "get_order_signer", + "inputs": [ + { + "name": "order_hash", + "type": "core::felt252" + } + ], + "outputs": [ + { + "type": "core::felt252" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "get_order_hash", + "inputs": [ + { + "name": "token_hash", + "type": "core::felt252" + } + ], + "outputs": [ + { + "type": "core::felt252" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "upgrade", + "inputs": [ + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "update_starknet_executor_address", + "inputs": [ + { + "name": "value", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [], + "state_mutability": "external" + } + ] + }, + { + "type": "constructor", + "name": "constructor", + "inputs": [ + { + "name": "admin", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "chain_id", + "type": "core::felt252" + } + ] + }, + { + "type": "struct", + "name": "ark_common::protocol::order_types::ExecutionValidationInfo", + "members": [ + { + "name": "order_hash", + "type": "core::felt252" + }, + { + "name": "transaction_hash", + "type": "core::felt252" + }, + { + "name": "starknet_block_timestamp", + "type": "core::integer::u64" + } + ] + }, + { + "type": "l1_handler", + "name": "validate_order_execution", + "inputs": [ + { + "name": "_from_address", + "type": "core::felt252" + }, + { + "name": "info", + "type": "ark_common::protocol::order_types::ExecutionValidationInfo" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "event", + "name": "ark_orderbook::orderbook::orderbook::OrderPlaced", + "kind": "struct", + "members": [ + { + "name": "order_hash", + "type": "core::felt252", + "kind": "key" + }, + { + "name": "order_version", + "type": "core::felt252", + "kind": "key" + }, + { + "name": "order_type", + "type": "ark_common::protocol::order_types::OrderType", + "kind": "key" + }, + { + "name": "cancelled_order_hash", + "type": "core::option::Option::", + "kind": "data" + }, + { + "name": "order", + "type": "ark_orderbook::order::order_v1::OrderV1", + "kind": "data" + } + ] + }, + { + "type": "enum", + "name": "ark_common::protocol::order_types::OrderStatus", + "variants": [ + { + "name": "Open", + "type": "()" + }, + { + "name": "Fulfilled", + "type": "()" + }, + { + "name": "Executed", + "type": "()" + }, + { + "name": "CancelledUser", + "type": "()" + }, + { + "name": "CancelledByNewOrder", + "type": "()" + }, + { + "name": "CancelledAssetFault", + "type": "()" + }, + { + "name": "CancelledOwnership", + "type": "()" + } + ] + }, + { + "type": "event", + "name": "ark_orderbook::orderbook::orderbook::OrderExecuted", + "kind": "struct", + "members": [ + { + "name": "order_hash", + "type": "core::felt252", + "kind": "key" + }, + { + "name": "order_status", + "type": "ark_common::protocol::order_types::OrderStatus", + "kind": "key" + } + ] + }, + { + "type": "event", + "name": "ark_orderbook::orderbook::orderbook::OrderCancelled", + "kind": "struct", + "members": [ + { + "name": "order_hash", + "type": "core::felt252", + "kind": "key" + }, + { + "name": "reason", + "type": "core::felt252", + "kind": "key" + } + ] + }, + { + "type": "event", + "name": "ark_orderbook::orderbook::orderbook::OrderFulfilled", + "kind": "struct", + "members": [ + { + "name": "order_hash", + "type": "core::felt252", + "kind": "key" + }, + { + "name": "fulfiller", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + }, + { + "name": "related_order_hash", + "type": "core::option::Option::", + "kind": "key" + } + ] + }, + { + "type": "event", + "name": "ark_orderbook::orderbook::orderbook::Upgraded", + "kind": "struct", + "members": [ + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "ark_orderbook::orderbook::orderbook::RollbackStatus", + "kind": "struct", + "members": [ + { + "name": "order_hash", + "type": "core::felt252", + "kind": "key" + }, + { + "name": "reason", + "type": "core::felt252", + "kind": "key" + } + ] + }, + { + "type": "event", + "name": "ark_orderbook::orderbook::orderbook::Event", + "kind": "enum", + "variants": [ + { + "name": "OrderPlaced", + "type": "ark_orderbook::orderbook::orderbook::OrderPlaced", + "kind": "nested" + }, + { + "name": "OrderExecuted", + "type": "ark_orderbook::orderbook::orderbook::OrderExecuted", + "kind": "nested" + }, + { + "name": "OrderCancelled", + "type": "ark_orderbook::orderbook::orderbook::OrderCancelled", + "kind": "nested" + }, + { + "name": "OrderFulfilled", + "type": "ark_orderbook::orderbook::orderbook::OrderFulfilled", + "kind": "nested" + }, + { + "name": "Upgraded", + "type": "ark_orderbook::orderbook::orderbook::Upgraded", + "kind": "nested" + }, + { + "name": "RollbackStatus", + "type": "ark_orderbook::orderbook::orderbook::RollbackStatus", + "kind": "nested" + } + ] + } +] diff --git a/artifacts/starknet_utils.json b/artifacts/starknet_utils.json new file mode 100644 index 0000000000..2abf79b609 --- /dev/null +++ b/artifacts/starknet_utils.json @@ -0,0 +1,224 @@ +[ + { + "type": "struct", + "name": "package_name::ExecutionInfo", + "members": [ + { + "name": "order_hash", + "type": "core::felt252" + }, + { + "name": "nft_address", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "nft_from", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "nft_to", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "nft_token_id", + "type": "core::integer::u256" + }, + { + "name": "payment_from", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "payment_to", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "payment_amount", + "type": "core::integer::u256" + }, + { + "name": "payment_currency_address", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "listing_broker_address", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "fulfill_broker_address", + "type": "core::starknet::contract_address::ContractAddress" + } + ] + }, + { + "type": "struct", + "name": "core::integer::u256", + "members": [ + { + "name": "low", + "type": "core::integer::u128" + }, + { + "name": "high", + "type": "core::integer::u128" + } + ] + }, + { + "type": "function", + "name": "owner_of", + "inputs": [ + { + "name": "token_id", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "ownerOf", + "inputs": [ + { + "name": "token_id", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "state_mutability": "view" + }, + { + "type": "enum", + "name": "core::bool", + "variants": [ + { + "name": "False", + "type": "()" + }, + { + "name": "True", + "type": "()" + } + ] + }, + { + "type": "function", + "name": "is_approved_for_all", + "inputs": [ + { + "name": "owner", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "operator", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::bool" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "isApprovedForAll", + "inputs": [ + { + "name": "owner", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "operator", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::bool" + } + ], + "state_mutability": "view" + }, + { + "type": "struct", + "name": "core::array::Span::", + "members": [ + { + "name": "snapshot", + "type": "@core::array::Array::" + } + ] + }, + { + "type": "function", + "name": "is_valid_signature", + "inputs": [ + { + "name": "hash", + "type": "core::felt252" + }, + { + "name": "signature", + "type": "core::array::Span::" + } + ], + "outputs": [ + { + "type": "core::bool" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "balanceOf", + "inputs": [ + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "allowance", + "inputs": [ + { + "name": "owner", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "spender", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "event", + "name": "solis::starknet_utils::starknet_utils::Event", + "kind": "enum", + "variants": [] + } +] diff --git a/bin/solis/Cargo.toml b/bin/solis/Cargo.toml index fb009ea6aa..5b8debb1c2 100644 --- a/bin/solis/Cargo.toml +++ b/bin/solis/Cargo.toml @@ -9,6 +9,10 @@ version.workspace = true [dependencies] alloy-primitives.workspace = true anyhow.workspace = true +async-trait = "0.1.57" +cainome = { git = "https://github.com/cartridge-gg/cainome", tag = "v0.2.6", features = [ + "abigen-rs", +] } cfg-if = "1.0.0" clap.workspace = true clap_complete.workspace = true @@ -22,7 +26,8 @@ katana-rpc-api.workspace = true katana-rpc.workspace = true serde_json.workspace = true shellexpand = "3.1.0" -starknet_api.workspace = true +starknet = "0.9.0" +starknet_api = "0.10.0" tokio.workspace = true tracing-subscriber.workspace = true tracing.workspace = true diff --git a/bin/solis/src/contracts/account.rs b/bin/solis/src/contracts/account.rs new file mode 100644 index 0000000000..f19a7d9c6c --- /dev/null +++ b/bin/solis/src/contracts/account.rs @@ -0,0 +1,42 @@ +use starknet::{ + accounts::{ExecutionEncoding, SingleOwnerAccount}, + core::types::FieldElement, + providers::{jsonrpc::HttpTransport, AnyProvider, JsonRpcClient, Provider}, + signers::{LocalWallet, SigningKey}, +}; + +use url::Url; + +/// Initializes a new account to interact with Starknet. +/// +/// # Arguments +/// +/// * `provider_url` - Starknet provider's url. +/// * `account_address` - Starknet account's address. +/// * `private_key` - Private key associated to the Starknet account. +#[allow(dead_code)] +pub async fn new_account( + provider_url: &str, + account_address: FieldElement, + private_key: FieldElement, +) -> SingleOwnerAccount { + let rpc_url = Url::parse(provider_url).expect("Expecting valid Starknet RPC URL"); + let provider = + AnyProvider::JsonRpcHttp(JsonRpcClient::new(HttpTransport::new(rpc_url.clone()))); + + // TODO: need error instead of expect. + let chain_id = provider + .chain_id() + .await + .expect("couldn't get chain_id from provider"); + + let signer = LocalWallet::from(SigningKey::from_secret_scalar(private_key)); + + SingleOwnerAccount::new( + provider, + signer, + account_address, + chain_id, + ExecutionEncoding::Legacy, + ) +} diff --git a/bin/solis/src/contracts/executor.rs b/bin/solis/src/contracts/executor.rs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/bin/solis/src/contracts/mod.rs b/bin/solis/src/contracts/mod.rs new file mode 100644 index 0000000000..df2f73e1e5 --- /dev/null +++ b/bin/solis/src/contracts/mod.rs @@ -0,0 +1,3 @@ +pub mod account; +pub mod orderbook; +pub mod starknet_utils; diff --git a/bin/solis/src/contracts/orderbook.rs b/bin/solis/src/contracts/orderbook.rs new file mode 100644 index 0000000000..9bc801643b --- /dev/null +++ b/bin/solis/src/contracts/orderbook.rs @@ -0,0 +1,12 @@ +use cainome::rs::abigen; +use starknet::{accounts::ConnectedAccount, core::types::FieldElement}; + +abigen!(OrderbookContract, "./artifacts/orderbook.abi.json"); + +#[allow(dead_code)] +pub fn new_orderbook( + contract_address: FieldElement, + account: A, +) -> OrderbookContract { + OrderbookContract::new(contract_address, account) +} diff --git a/bin/solis/src/contracts/starknet_utils.rs b/bin/solis/src/contracts/starknet_utils.rs new file mode 100644 index 0000000000..280c9f2e6d --- /dev/null +++ b/bin/solis/src/contracts/starknet_utils.rs @@ -0,0 +1,19 @@ +use cainome::rs::abigen; +use starknet::{ + core::types::FieldElement, + providers::{jsonrpc::HttpTransport, AnyProvider, JsonRpcClient}, +}; + +use url::Url; + +abigen!(StarknetUtils, "./artifacts/starknet_utils.json"); +pub fn new_starknet_utils_reader( + contract_address: FieldElement, + provider_url: &str, +) -> StarknetUtilsReader { + let rpc_url = Url::parse(provider_url).expect("Expecting valid Starknet RPC URL"); + let provider = + AnyProvider::JsonRpcHttp(JsonRpcClient::new(HttpTransport::new(rpc_url.clone()))); + + StarknetUtilsReader::new(contract_address, provider) +} diff --git a/bin/solis/src/hooker.rs b/bin/solis/src/hooker.rs new file mode 100644 index 0000000000..4068940884 --- /dev/null +++ b/bin/solis/src/hooker.rs @@ -0,0 +1,578 @@ +//! Solis hooker on Katana transaction lifecycle. +//! +use crate::contracts::starknet_utils::{ExecutionInfo, U256}; +use async_trait::async_trait; +use cainome::cairo_serde::CairoSerde; +use cainome::rs::abigen; +use katana_core::hooker::{HookerAddresses, KatanaHooker}; +use katana_core::sequencer::KatanaSequencer; +use katana_executor::ExecutorFactory; + +use katana_primitives::chain::ChainId; +use katana_primitives::contract::ContractAddress; +use katana_primitives::transaction::{ExecutableTx, ExecutableTxWithHash, L1HandlerTx}; +use katana_primitives::utils::transaction::compute_l1_message_hash; +use starknet::accounts::Call; +use starknet::core::types::BroadcastedInvokeTransaction; +use starknet::core::types::FieldElement; +use starknet::macros::selector; +use starknet::providers::Provider; +use std::sync::Arc; +use std::fs::File; +use std::fs::OpenOptions; +use std::io::Write; +use std::io::Read; +use serde_json::Value; +use std::path::Path; +use serde_json::json; + + +const FILE_PATH_ADDRESSES: &str = "addresses.json"; + + +use crate::contracts::orderbook::{OrderV1, RouteType}; +use crate::contracts::starknet_utils::StarknetUtilsReader; +use crate::CHAIN_ID_SOLIS; +use tracing::info; + +#[allow(dead_code)] +pub enum CancelStatus { + CancelledUser, + CancelledByNewOrder, + CancelledAssetFault, + CancelledOwnership, +} + +impl CancelStatus { + fn to_u32(&self) -> u32 { + match self { + CancelStatus::CancelledUser => 1, + CancelStatus::CancelledByNewOrder => 2, + CancelStatus::CancelledAssetFault => 3, + CancelStatus::CancelledOwnership => 4, + } + } +} + +struct OwnershipVerifier { + token_address: ContractAddress, + token_id: U256, + current_owner: cainome::cairo_serde::ContractAddress, +} + +struct BalanceVerifier { + currency_address: ContractAddress, + offerer: cainome::cairo_serde::ContractAddress, + start_amount: U256, +} + +abigen!(CallContract, "./artifacts/contract.abi.json"); + +/// Hooker struct, with already instanciated contracts/readers +/// to avoid allocating them at each transaction that is being +/// verified. +pub struct SolisHooker { + // Solis interacts with the orderbook only via `L1HandlerTransaction`. Only the + // address is required. + pub orderbook_address: FieldElement, + // TODO: replace this by the Executor Contract object! + pub sn_executor_address: FieldElement, + pub sn_utils_reader: StarknetUtilsReader

, + sequencer: Option>>, +} + +impl + SolisHooker +{ + /// Verify the ownership of a token + async fn verify_ownership(&self, ownership_verifier: &OwnershipVerifier) -> bool { + let sn_utils_reader_nft_address = StarknetUtilsReader::new( + ownership_verifier.token_address.into(), + self.sn_utils_reader.provider(), + ); + + // check the current owner of the token. + let owner = sn_utils_reader_nft_address + .ownerOf(&ownership_verifier.token_id) + .call() + .await; + + if let Ok(owner_address) = owner { + if owner_address != ownership_verifier.current_owner { + tracing::trace!( + "\nOwner {:?} differs from offerer {:?} ", + owner, + ownership_verifier.current_owner + ); + + println!( + "\nOwner {:?} differs from offerer {:?} ", + owner, ownership_verifier.current_owner + ); + + return false; + } + } + + true + } + + async fn verify_balance(&self, balance_verifier: &BalanceVerifier) -> bool { + info!("HOOKER: Verify Balance"); + + let sn_utils_reader_erc20_address = StarknetUtilsReader::new( + balance_verifier.currency_address.into(), + self.sn_utils_reader.provider(), + ); + let allowance = sn_utils_reader_erc20_address + .allowance(&balance_verifier.offerer, &self.sn_executor_address.into()) + .call() + .await; + + info!( + "HOOKER: Verify Balance allowance {:?}, amount {:?}", + allowance, balance_verifier.start_amount + ); + + if let Ok(allowance) = allowance { + if allowance < balance_verifier.start_amount { + println!( + "\nAllowance {:?} is not enough {:?} for offerer {:?}", + allowance, balance_verifier.start_amount, balance_verifier.offerer + ); + return false; + } + } + + // check the balance + let balance = sn_utils_reader_erc20_address + .balanceOf(&balance_verifier.offerer) + .call() + .await; + if let Ok(balance) = balance { + if balance < balance_verifier.start_amount { + tracing::trace!( + "\nBalance {:?} is not enough {:?} ", + balance, + balance_verifier.start_amount + ); + println!( + "\nBalance {:?} is not enough {:?} ", + balance, balance_verifier.start_amount + ); + return false; + } + } + + true + } + + async fn verify_call(&self, call: &TxCall) -> bool { + let order = match OrderV1::cairo_deserialize(&call.calldata, 0) { + Ok(order) => order, + Err(e) => { + tracing::error!("Fail deserializing OrderV1: {:?}", e); + return false; + } + }; + + // ERC721 to ERC20 + if order.route == RouteType::Erc721ToErc20 { + let token_id = order.token_id.clone().unwrap(); + let n_token_id = U256 { + low: token_id.low, + high: token_id.high, + }; + + let verifier = OwnershipVerifier { + token_address: ContractAddress(order.token_address.into()), + token_id: n_token_id, + current_owner: cainome::cairo_serde::ContractAddress(order.offerer.into()), + }; + + let owner_ship_verification = self.verify_ownership(&verifier).await; + if !owner_ship_verification { + return false; + } + } + + // ERC20 to ERC721 : we check the allowance and the offerer balance. + if order.route == RouteType::Erc20ToErc721 { + if !self + .verify_balance(&BalanceVerifier { + currency_address: ContractAddress(order.currency_address.into()), + offerer: cainome::cairo_serde::ContractAddress(order.offerer.into()), + start_amount: U256 { + low: order.start_amount.low, + high: order.start_amount.high, + }, + }) + .await + { + println!("verify balance for starknet before failed"); + return false; + } + } + return true; + } +} + +impl + SolisHooker +{ + fn get_addresses_from_file() -> Result<(FieldElement, FieldElement), Box> { + let mut file = match File::open(FILE_PATH_ADDRESSES) { + Ok(file) => file, + Err(_) => return Err("File not found".into()), + }; + + let mut contents = String::new(); + file.read_to_string(&mut contents)?; + + let v: Value = match serde_json::from_str(&contents) { + Ok(value) => value, + Err(_) => return Err("Error parsing JSON".into()), + }; + + let orderbook_address = match v["orderbook_address"].as_str() { + Some(address) => address, + None => return Err("orderbook_address key not found in JSON".into()), + }; + + let sn_executor_address = match v["sn_executor_address"].as_str() { + Some(address) => address, + None => return Err("sn_executor_address key not found in JSON".into()), + }; + + let orderbook_address = match FieldElement::from_hex_be(&orderbook_address[2..]) { + Ok(val) => FieldElement::from_dec_str(&val.to_string()), + Err(_) => return Err("Failed to parse orderbook_address".into()), + }; + + let sn_executor_address = match FieldElement::from_hex_be(&sn_executor_address[2..]) { + Ok(val) => FieldElement::from_dec_str(&val.to_string()), + Err(_) => return Err("Failed to parse sn_executor_address".into()), + }; + + println!("Addresses loaded from file: {:?}, {:?}", orderbook_address, sn_executor_address); + Ok((orderbook_address?, sn_executor_address?)) + } + + /// Initializes a new instance. + pub fn new( + sn_utils_reader: StarknetUtilsReader

, + orderbook_address: FieldElement, + sn_executor_address: FieldElement, + ) -> Self { + let (orderbook_address, sn_executor_address) = if orderbook_address == FieldElement::ZERO && sn_executor_address == FieldElement::ZERO { + match Self::get_addresses_from_file() { + Ok((orderbook, executor)) => (orderbook, executor), + Err(e) => { + eprintln!("Error reading addresses from file: {}", e); + (orderbook_address, sn_executor_address) + } + } + } else { + (orderbook_address, sn_executor_address) + }; + + Self { + orderbook_address, + sn_utils_reader, + sn_executor_address, + sequencer: None, + } + } + + /// Retrieves a reference to the sequencer. + #[allow(dead_code)] + pub fn sequencer_ref(&self) -> &Arc> { + // The expect is used here as it must always be set by Katana core. + // If not set, the merge on Katana may be revised. + self.sequencer + .as_ref() + .expect("Sequencer must be set to get a reference to it") + } + + /// Adds a `L1HandlerTransaction` to the transaction pool that is directed to the + /// orderbook only. + /// `L1HandlerTransaction` is a special type of transaction that can only be + /// sent by the sequencer itself. This transaction is not validated by any account. + /// + /// In the case of Solis, `L1HandlerTransaction` are sent by Solis for two purposes: + /// 1. A message was collected from the L2, and it must be executed. + /// 2. A transaction has been rejected by Solis (asset faults), and the order + /// must then be updated. + /// + /// This function is used for the scenario 2. For this reason, the `from_address` + /// field is automatically filled up by the sequencer to use the executor address + /// deployed on L2 to avoid any attack by other contracts. + /// + /// # Arguments + /// + /// * `selector` - The selector of the recipient contract to execute. + /// * `payload` - The payload of the message. + #[allow(dead_code)] + pub fn add_l1_handler_transaction_for_orderbook( + &self, + selector: FieldElement, + payload: &[FieldElement], + ) { + let to_address = self.orderbook_address; + let from_address = self.sn_executor_address; + let chain_id = ChainId::Id(CHAIN_ID_SOLIS); + + // The nonce is normally used by the messaging contract on Starknet. But in the + // case of those transaction, as they are only sent by Solis itself, we use 0. + // TODO: this value of 0 must be checked by the `l1_handler` function. + let nonce = FieldElement::ZERO; + + // The calldata always starts with the from_address. + let mut calldata: Vec = vec![from_address]; + for p in payload.into_iter() { + calldata.push(*p); + } + + let message_hash = compute_l1_message_hash(from_address, to_address, payload); + + let tx = L1HandlerTx { + nonce, + chain_id, + paid_fee_on_l1: 30000_u128, + version: FieldElement::ZERO, + message_hash, + calldata, + contract_address: ContractAddress(to_address), + entry_point_selector: selector, + }; + + if let Some(seq) = &self.sequencer { + let exe = ExecutableTxWithHash::new_query(ExecutableTx::L1Handler(tx), false); + seq.add_transaction_to_pool(exe); + } + } +} + +/// Solis hooker relies on verifiers to inspect and verify +/// the transaction and starknet state before acceptance. +#[async_trait] +impl KatanaHooker + for SolisHooker +{ + fn set_sequencer(&mut self, sequencer: Arc>) { + self.sequencer = Some(sequencer); + } + + fn set_addresses(&mut self, addresses: HookerAddresses) { + info!("HOOKER: Addresses set for hooker: {:?}", addresses); + self.orderbook_address = addresses.orderbook_arkchain; + self.sn_executor_address = addresses.executor_starknet; + + let path = Path::new(FILE_PATH_ADDRESSES); + let file = OpenOptions::new() + .write(true) + .create(true) + .open(&path); + + match file { + Ok(mut file) => { + let data = json!({ + "orderbook_address": format!("{:#x}", self.orderbook_address), + "sn_executor_address": format!("{:#x}", self.sn_executor_address) + }); + + if let Err(e) = writeln!(file, "{}", data.to_string()) { + eprintln!("Error writing file : {}", e); + } + } + Err(e) => { + eprintln!("Error opening file : {}", e); + } + } + } + + /// Verifies if the message is directed to the orderbook and comes from + /// the executor contract on L2. + /// + /// Currently, only the `from` and `to` are checked. + /// More checks may be added on the selector, and the data. + async fn verify_message_to_appchain( + &self, + from: FieldElement, + to: FieldElement, + _selector: FieldElement, + ) -> bool { + info!( + "HOOKER: verify_message_to_appchain called with from: {:?}, to: {:?}, {:?}, {:?}", + from, to, self.sn_executor_address, self.orderbook_address + ); + // For now, only the from/to are checked. + from == self.sn_executor_address && to == self.orderbook_address + } + + /// Verifies an invoke transaction that is: + /// 1. Directed to the orderbook only. + /// 2. With the selector `create_order` only as the fulfill + /// is verified by `verify_message_to_starknet_before_tx`. + async fn verify_invoke_tx_before_pool( + &self, + transaction: BroadcastedInvokeTransaction, + ) -> bool { + info!( + "HOOKER: verify_invoke_tx_before_pool called with transaction: {:?}", + transaction + ); + + let calldata = match transaction { + BroadcastedInvokeTransaction::V1(v1_transaction) => v1_transaction.calldata, + BroadcastedInvokeTransaction::V3(v3_transaction) => v3_transaction.calldata, + }; + info!( + "HOOKER: cairo_deserialize called with transaction: {:?}", + calldata + ); + + let calls = match Vec::::cairo_deserialize(&calldata, 0) { + Ok(calls) => calls, + Err(e) => { + tracing::error!("Fail deserializing OrderV1: {:?}", e); + return false; + } + }; + + for call in calls { + if call.selector != selector!("create_order") + && call.selector != selector!("create_order_from_l2") + && call.selector != selector!("fulfill_order_from_l2") + { + continue; + } + + if !self.verify_call(&call).await { + return false; + } + + // TODO: check assets on starknet. + // TODO: if not valid, in some cases we want to send L1HandlerTransaction + // to change the status of the order. (entrypoint to be written). + } + true + } + + async fn verify_tx_for_starknet(&self, call: Call) -> bool { + println!("verify message to starknet before tx: {:?}", call); + if call.selector != selector!("fulfill_order") { + return true; + } + + let execution_info = match ExecutionInfo::cairo_deserialize(&call.calldata, 0) { + Ok(execution_info) => execution_info, + Err(e) => { + tracing::error!("Fail deserializing ExecutionInfo: {:?}", e); + return false; + } + }; + + let verifier = OwnershipVerifier { + token_address: ContractAddress(execution_info.nft_address.into()), + token_id: execution_info.nft_token_id, + current_owner: cainome::cairo_serde::ContractAddress(execution_info.nft_from.into()), + }; + + let owner_ship_verification = self.verify_ownership(&verifier).await; + if !owner_ship_verification { + // rollback the status + let status = CancelStatus::CancelledOwnership; + + self.add_l1_handler_transaction_for_orderbook( + selector!("rollback_status_order"), + &[execution_info.order_hash, status.to_u32().into()], + ); + return false; + } + + if !self + .verify_balance(&BalanceVerifier { + currency_address: ContractAddress(execution_info.payment_currency_address.into()), + offerer: cainome::cairo_serde::ContractAddress(execution_info.nft_to.into()), + start_amount: U256 { + low: execution_info.payment_amount.low, + high: execution_info.payment_amount.high, + }, + }) + .await + { + // rollback the status + let status = CancelStatus::CancelledAssetFault; + + self.add_l1_handler_transaction_for_orderbook( + selector!("rollback_status_order"), + &[execution_info.order_hash, status.to_u32().into()], + ); + return false; + } + + true + } + + async fn on_starknet_tx_failed(&self, call: Call) { + println!("Starknet tx failed: {:?}", call); + + let execution_info = match ExecutionInfo::cairo_deserialize(&call.calldata, 0) { + Ok(execution_info) => execution_info, + Err(e) => { + tracing::error!("Fail deserializing ExecutionInfo: {:?}", e); + return; + } + }; + + // rollback the status + self.add_l1_handler_transaction_for_orderbook( + selector!("rollback_status_order"), + &[execution_info.order_hash], + ); + } +} + +#[cfg(test)] +mod test { + use super::*; + use starknet::macros::{felt, selector}; + + #[test] + fn test_calldata_calls_parsing_new_encoding() { + // Calldata for a transaction to starkgate: + // Tx hash Goerli: 0x78140a4777bdf508feec62485c2d49b90b8346875c19470790935bcfbb9594 + let data = vec![ + FieldElement::ONE, + // to (starkgate). + felt!("0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"), + // selector (transfert). + felt!("0x83afd3f4caedc6eebf44246fe54e38c95e3179a5ec9ea81740eca5b482d12e"), + // data offset. + felt!("0x0"), + // data len. + felt!("0x0"), + // Calldata len. + FieldElement::THREE, + felt!("0x06cdcce7333a7143ad0aebbaffe54a809cc53b65c0936ecfbebaecc0de099e8e"), + felt!("0x071afd498d0000"), + felt!("0x00"), + ]; + + let calls = match Vec::::cairo_deserialize(&data, 0) { + Ok(calls) => calls, + Err(e) => { + tracing::error!("Fail deserializing OrderV1: {:?}", e); + Vec::new() + } + }; + + assert_eq!(calls.len(), 1); + assert_eq!( + calls[0].to, + felt!("0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7").into() + ); + assert_eq!(calls[0].selector, selector!("transfer")); + } +} diff --git a/bin/solis/src/main.rs b/bin/solis/src/main.rs index 07c48d1d14..ce806b1e34 100644 --- a/bin/solis/src/main.rs +++ b/bin/solis/src/main.rs @@ -2,13 +2,14 @@ use std::io; use std::net::SocketAddr; use std::sync::Arc; +use crate::hooker::SolisHooker; use clap::{CommandFactory, Parser}; use clap_complete::{generate, Shell}; use console::Style; use dojo_metrics::{metrics_process, prometheus_exporter}; use katana_core::constants::MAX_RECURSION_DEPTH; use katana_core::env::get_default_vm_resource_fee_cost; -use katana_core::hooker::{DefaultKatanaHooker, KatanaHooker}; +use katana_core::hooker::KatanaHooker; use katana_core::sequencer::KatanaSequencer; use katana_executor::SimulationFlag; use katana_primitives::class::ClassHash; @@ -17,12 +18,23 @@ use katana_primitives::env::{CfgEnv, FeeTokenAddressses}; use katana_primitives::genesis::allocation::GenesisAccountAlloc; use katana_primitives::genesis::Genesis; use katana_rpc::{spawn, NodeHandle}; +use starknet::core::types::FieldElement; use tokio::signal::ctrl_c; use tokio::sync::RwLock as AsyncRwLock; use tracing::info; mod args; mod utils; +mod hooker; +mod contracts; + +// Chain ID: 'SOLIS' cairo short string. +pub const CHAIN_ID_SOLIS: FieldElement = FieldElement::from_mont([ + 18446732623703627169, + 18446744073709551615, + 18446744073709551615, + 576266102202707888, +]); use args::Commands::Completions; use args::KatanaArgs; @@ -95,18 +107,35 @@ async fn main() -> Result<(), Box> { .await?; } - // Create a default hooker instance - // Create a default hooker instance - // Create a default hooker instance - let hooker: Arc + Send + Sync>> = - Arc::new(AsyncRwLock::new(DefaultKatanaHooker::new())); + // ** SOLIS + let sn_utils_reader = contracts::starknet_utils::new_starknet_utils_reader( + FieldElement::ZERO, + &sequencer_config.messaging.clone().unwrap().rpc_url, + ); + + let executor_address = FieldElement::ZERO; + let orderbook_address = FieldElement::ZERO; + + let hooker:Arc + Send + Sync>> = + Arc::new(AsyncRwLock::new(SolisHooker::new( + sn_utils_reader, + orderbook_address, + executor_address, + ))); + // ** let sequencer = Arc::new( - KatanaSequencer::new(executor_factory, sequencer_config, starknet_config, Some(hooker)) + KatanaSequencer::new(executor_factory, sequencer_config, starknet_config, Some(hooker.clone())) .await?, ); let NodeHandle { addr, handle, .. } = spawn(Arc::clone(&sequencer), server_config).await?; + // ** SOLIS + // Important to set the sequencer reference in the hooker, to allow the hooker + // to send `L1HandlerTransaction` to the orderbook. + hooker.write().await.set_sequencer(sequencer.clone()); + // ** + if !args.silent { let genesis = &sequencer.backend().config.genesis; print_intro(&args, genesis, addr); @@ -152,7 +181,7 @@ fn print_intro(args: &KatanaArgs, genesis: &Genesis, address: SocketAddr) { ██╔══██║██╔══██╗██╔═██╗░██╔═══╝░██╔══██╗██║░░██║██╗░░██║██╔══╝░░██║░░██╗░░░██║░░░ ██║░░██║██║░░██║██║░╚██╗██║░░░░░██║░░██║╚█████╔╝╚█████╔╝███████╗╚█████╔╝░░░██║░░░ ╚═╝░░╚═╝╚═╝░░╚═╝╚═╝░░╚═╝╚═╝░░░░░╚═╝░░╚═╝░╚════╝░░╚════╝░╚══════╝░╚════╝░░░░╚═╝░░░ - + ░██████╗░█████╗░██╗░░░░░██╗░██████╗ ██╔════╝██╔══██╗██║░░░░░██║██╔════╝ ╚█████╗░██║░░██║██║░░░░░██║╚█████╗░ diff --git a/crates/katana/core/src/service/messaging/service.rs b/crates/katana/core/src/service/messaging/service.rs index dd3090825c..3bed0e18ea 100644 --- a/crates/katana/core/src/service/messaging/service.rs +++ b/crates/katana/core/src/service/messaging/service.rs @@ -120,6 +120,7 @@ impl MessagingService { backend: Arc>, messenger: Arc>, ) -> MessengerResult> { + // Retrieve messages to be sent from the local blockchain for the given block number let Some(messages) = ReceiptProvider::receipts_by_block( backend.blockchain.provider(), BlockHashOrNumber::Num(block_num), @@ -130,23 +131,43 @@ impl MessagingService { }; if messages.is_empty() { - Ok(Some((block_num, 0))) - } else { - match messenger.as_ref() { - MessengerMode::Ethereum(inner) => { - let hashes = inner.send_messages(&messages).await.map(|hashes| { - hashes.iter().map(|h| format!("{h:#x}")).collect::>() - })?; - trace_msg_to_l1_sent(&messages, &hashes); - Ok(Some((block_num, hashes.len()))) - } + info!(target: LOG_TARGET, "No messages to send from block {}", block_num); + return Ok(Some((block_num, 0))); + } - MessengerMode::Starknet(inner) => { - let hashes = inner.send_messages(&messages).await.map(|hashes| { - hashes.iter().map(|h| format!("{h:#x}")).collect::>() - })?; - trace_msg_to_l1_sent(&messages, &hashes); - Ok(Some((block_num, hashes.len()))) + info!(target: LOG_TARGET, "Retrieved {} messages from block {}", messages.len(), block_num); + + match messenger.as_ref() { + MessengerMode::Ethereum(inner) => { + match inner.send_messages(&messages).await { + Ok(hashes) => { + let hash_strings: Vec = + hashes.iter().map(|h| format!("{:#x}", h)).collect(); + trace_msg_to_l1_sent(&messages, &hash_strings); + info!(target: LOG_TARGET, "Successfully sent {} messages from block {}", hash_strings.len(), block_num); + Ok(Some((block_num, hash_strings.len()))) + } + Err(e) => { + error!(target: LOG_TARGET, error = %e, "Error sending messages from block {}", block_num); + // Even if there's an error, we should move to the next block to avoid infinite retries + Ok(Some((block_num, 0))) // Marking as processed to avoid retries + } + } + } + MessengerMode::Starknet(inner) => { + match inner.send_messages(&messages).await { + Ok(hashes) => { + let hash_strings: Vec = + hashes.iter().map(|h| format!("{:#x}", h)).collect(); + trace_msg_to_l1_sent(&messages, &hash_strings); + info!(target: LOG_TARGET, "Successfully sent {} messages from block {}", hash_strings.len(), block_num); + Ok(Some((block_num, hash_strings.len()))) + } + Err(e) => { + error!(target: LOG_TARGET, error = %e, "Error sending messages from block {}", block_num); + // Even if there's an error, we should move to the next block to avoid infinite retries + Ok(Some((block_num, 0))) // Marking as processed to avoid retries + } } } } @@ -192,15 +213,16 @@ impl Stream for MessagingService { pin.send_from_block, pin.backend.clone(), pin.messenger.clone(), - ))) + ))); } } } - // Poll the gathering future. + // Poll the gathering future if let Some(mut gather_fut) = pin.msg_gather_fut.take() { match gather_fut.poll_unpin(cx) { Poll::Ready(Ok((last_block, msg_count))) => { + info!(target: LOG_TARGET, "Gathered {} transactions up to block {}", msg_count, last_block); pin.gather_from_block = last_block + 1; return Poll::Ready(Some(MessagingOutcome::Gather { lastest_block: last_block, @@ -208,34 +230,25 @@ impl Stream for MessagingService { })); } Poll::Ready(Err(e)) => { - error!( - target: LOG_TARGET, - block = %pin.gather_from_block, - error = %e, - "Gathering messages for block." - ); + error!(target: LOG_TARGET, block = %pin.gather_from_block, error = %e, "Error gathering messages for block."); return Poll::Pending; } Poll::Pending => pin.msg_gather_fut = Some(gather_fut), } } - // Poll the message sending future. + // Poll the message sending future if let Some(mut send_fut) = pin.msg_send_fut.take() { match send_fut.poll_unpin(cx) { Poll::Ready(Ok(Some((block_num, msg_count)))) => { - // +1 to move to the next local block to check messages to be - // sent on the settlement chain. - pin.send_from_block += 1; + info!(target: LOG_TARGET, "Sent {} messages from block {}", msg_count, block_num); + pin.send_from_block = block_num + 1; return Poll::Ready(Some(MessagingOutcome::Send { block_num, msg_count })); } Poll::Ready(Err(e)) => { - error!( - target: LOG_TARGET, - block = %pin.send_from_block, - error = %e, - "Settling messages for block." - ); + error!(target: LOG_TARGET, block = %pin.send_from_block, error = %e, "Error sending messages for block."); + // Even if there's an error, we should move to the next block to avoid infinite retries + pin.send_from_block += 1; return Poll::Pending; } Poll::Ready(_) => return Poll::Pending, diff --git a/crates/katana/core/src/service/messaging/starknet.rs b/crates/katana/core/src/service/messaging/starknet.rs index b1cf4457bf..786db7ecfa 100644 --- a/crates/katana/core/src/service/messaging/starknet.rs +++ b/crates/katana/core/src/service/messaging/starknet.rs @@ -107,22 +107,22 @@ impl StarknetMessaging { } if let Ok(tx) = l1_handler_tx_from_event(&event, chain_id) { - // if let Ok((from, to, selector)) = info_from_event(&event) { - // let hooker = Arc::clone(&self.hooker); - // let is_message_accepted = hooker - // .read() - // .await - // .verify_message_to_appchain(from, to, selector) - // .await; - // if is_message_accepted { - debug!(target: LOG_TARGET, "Event ID: {} accepted, adding to transactions", event_id); - l1_handler_txs.push(tx); - let mut cache = self.event_cache.write().await; - cache.insert(event_id); - // } else { - // debug!(target: LOG_TARGET, "Event ID: {} not accepted by hooker", event_id); - // } - // } + if let Ok((from, to, selector)) = info_from_event(&event) { + let hooker = Arc::clone(&self.hooker); + let is_message_accepted = hooker + .read() + .await + .verify_message_to_appchain(from, to, selector) + .await; + if is_message_accepted { + debug!(target: LOG_TARGET, "Event ID: {} accepted, adding to transactions", event_id); + l1_handler_txs.push(tx); + let mut cache = self.event_cache.write().await; + cache.insert(event_id); + } else { + debug!(target: LOG_TARGET, "Event ID: {} not accepted by hooker", event_id); + } + } } } @@ -139,7 +139,6 @@ impl StarknetMessaging { async fn send_invoke_tx(&self, calls: Vec) -> Result { let signer = Arc::new(&self.wallet); - let mut account = SingleOwnerAccount::new( &self.provider, signer, @@ -148,13 +147,23 @@ impl StarknetMessaging { ExecutionEncoding::New, ); + info!(target: LOG_TARGET, "Setting block ID to Pending."); account.set_block_id(BlockId::Tag(BlockTag::Pending)); let execution = account.execute(calls).fee_estimate_multiplier(10f64); - let estimated_fee = (execution.estimate_fee().await?.overall_fee) * 10u64.into(); - let execution_with_fee = execution.max_fee(estimated_fee); + let estimated_fee = match execution.estimate_fee().await { + Ok(fee) => { + info!(target: LOG_TARGET, "Estimated fee: {:?}", fee.overall_fee); + (fee.overall_fee) * 10u64.into() + } + Err(e) => { + error!(target: LOG_TARGET, "Error estimating fee: {:?}", e); + return Err(e.into()); + } + }; - info!(target: LOG_TARGET, "Sending invoke transaction."); + let execution_with_fee = execution.max_fee(estimated_fee); + info!(target: LOG_TARGET, "Sending invoke transaction with max fee: {:?}", estimated_fee); match execution_with_fee.send().await { Ok(tx) => { @@ -176,20 +185,23 @@ impl StarknetMessaging { return Ok(FieldElement::ZERO); } - let mut calldata = hashes; + info!(target: LOG_TARGET, "Preparing to send {} hashes.", hashes.len()); + + let mut calldata = hashes.clone(); calldata.insert(0, calldata.len().into()); let call = Call { selector: selector!("add_messages_hashes_from_appchain"), to: self.messaging_contract_address, - calldata, + calldata: calldata.clone(), }; - info!(target: LOG_TARGET, "Sending hashes to Starknet."); + info!(target: LOG_TARGET, "Sending hashes to Starknet: {:?}", calldata); match self.send_invoke_tx(vec![call]).await { Ok(tx_hash) => { trace!(target: LOG_TARGET, tx_hash = %format!("{:#064x}", tx_hash), "Hashes sending transaction."); + info!(target: LOG_TARGET, "Successfully sent hashes with transaction hash: {:#064x}", tx_hash); Ok(tx_hash) } Err(e) => { @@ -246,23 +258,37 @@ impl Messenger for StarknetM messages: &[MessageToL1], ) -> MessengerResult> { if messages.is_empty() { + info!(target: LOG_TARGET, "No messages to send."); return Ok(vec![]); } let (hashes, calls) = parse_messages(messages)?; + for call in &calls { + if !self.hooker.read().await.verify_tx_for_starknet(call.clone()).await { + warn!(target: LOG_TARGET, "Call verification failed for call: {:?}", call); + continue; + } + } if !calls.is_empty() { - match self.send_invoke_tx(calls).await { - Ok(tx_hash) => { - trace!(target: LOG_TARGET, tx_hash = %format!("{:#064x}", tx_hash), "Invoke transaction hash."); + info!(target: LOG_TARGET, "Sending {} calls.", calls.len()); + if let Err(e) = self.send_invoke_tx(calls.clone()).await { + error!(target: LOG_TARGET, error = %e, "Error sending invoke transaction."); + for call in calls { + self.hooker.read().await.on_starknet_tx_failed(call).await; } - Err(e) => { - error!(target: LOG_TARGET, error = %e, "Sending invoke tx on Starknet."); - return Err(Error::SendError); - } - }; + return Err(Error::SendError); + } + info!(target: LOG_TARGET, "Successfully sent invoke transaction."); } - self.send_hashes(hashes.clone()).await?; + + if let Err(e) = self.send_hashes(hashes.clone()).await { + error!(target: LOG_TARGET, error = %e, "Error sending hashes."); + return Err(Error::SendError); + } + info!(target: LOG_TARGET, "Successfully sent hashes."); + + info!(target: LOG_TARGET, "Finished sending messages."); Ok(hashes) } }