diff --git a/sdk/rust/protocol/src/operation.rs b/sdk/rust/protocol/src/operation.rs index 4e18d00d17468de1b4fd2f2690199fa48221288d..5b9885e7fafad62237e547ab01811864c92ecd8a 100644 --- a/sdk/rust/protocol/src/operation.rs +++ b/sdk/rust/protocol/src/operation.rs @@ -6,9 +6,24 @@ /// The whole module is inspired of `src/proto_alpha/lib_protocol/operation_repr.ml` to represent the operation use crate::contract::Contract; use crate::entrypoint::Entrypoint; -use tezos_crypto_rs::{hash::BlsSignature, public_key::PublicKey, public_key_hash::PublicKeyHash}; +use tezos_crypto_rs::{ + hash::{BlockHash, BlsSignature}, + public_key::PublicKey, + public_key_hash::PublicKeyHash, +}; use tezos_data_encoding::{enc::BinWriter, nom::NomReader, types::Narith, types::WithDefaultValue}; +#[derive(PartialEq, Debug, Clone, NomReader, BinWriter)] +pub struct UnsignedOperation { + pub branch: BlockHash, + pub content_list: OperationContentList, +} + +#[derive(PartialEq, Debug, Clone, NomReader, BinWriter)] +pub struct OperationContentList { + pub contents: Vec, +} + #[derive(PartialEq, Debug, Clone, NomReader, BinWriter)] #[encoding(tags = "u8")] pub enum OperationContent { @@ -572,4 +587,417 @@ mod tests { assert_eq!(operation, decoded_operation); assert!(bytes.is_empty()); } + + /* + octez-codec encode "023-PtSeouLo.operation.contents_list" from '[ + { + "kind": "reveal", + "source": "tz1i4tRMvnyb672Wm7HGTi21MHyW4DSxSqvd", + "fee": "264", + "counter": "756631", + "gas_limit": "155", + "storage_limit": "0", + "public_key": "edpkuwYfBr7zWiUmzwppY75v8ut7Xfg73qht3Hpbwfa9GVAmcFDUwT" + }, + { + "kind": "transaction", + "source": "tz1i4tRMvnyb672Wm7HGTi21MHyW4DSxSqvd", + "fee": "472", + "counter": "756632", + "gas_limit": "2169", + "storage_limit": "277", + "amount": "20000", + "destination": "tz1Rc6wtS349fFTyuUhDXTXoBUZ9j7XiN61o" + } + ]' + */ + #[test] + fn basic_content_list_encoding() { + let reveal = OperationContent::Reveal(ManagerOperationContent { + source: PublicKeyHash::from_b58check("tz1i4tRMvnyb672Wm7HGTi21MHyW4DSxSqvd").unwrap(), + fee: 264.into(), + counter: 756631.into(), + gas_limit: 155.into(), + storage_limit: 0.into(), + operation: RevealContent { + pk: PublicKey::from_b58check( + "edpkuwYfBr7zWiUmzwppY75v8ut7Xfg73qht3Hpbwfa9GVAmcFDUwT", + ) + .unwrap(), + proof: None, + }, + }); + + let transaction = OperationContent::Transaction(ManagerOperationContent { + source: PublicKeyHash::from_b58check("tz1i4tRMvnyb672Wm7HGTi21MHyW4DSxSqvd").unwrap(), + fee: 472.into(), + counter: 756632.into(), + gas_limit: 2169.into(), + storage_limit: 277.into(), + operation: TransactionContent { + amount: 20000.into(), + destination: Contract::from_b58check("tz1Rc6wtS349fFTyuUhDXTXoBUZ9j7XiN61o") + .unwrap(), + parameters: Parameters::default().into(), + }, + }); + + let content_list = OperationContentList { + contents: vec![reveal, transaction], + }; + + let encoded_content_list = content_list.to_bytes().unwrap(); + + let bytes = hex::decode("6b00f60642ed6e39e3a5ecf6e8313cd044323a315c03880297972e9b010000ab0795c320972960ffe5e222a163494294a8d9259acab890f96fcbd18fd158f8006c00f60642ed6e39e3a5ecf6e8313cd044323a315c03d80398972ef9109502a09c0100004173796fda78e7ca2512edbb87124834f358bca200").unwrap(); + assert_eq!(bytes, encoded_content_list); + + let (bytes, decoded_content_list) = + OperationContentList::nom_read(&encoded_content_list).unwrap(); + assert_eq!(content_list, decoded_content_list); + assert!(bytes.is_empty()); + } + + /* + octez-codec encode "023-PtSeouLo.operation.contents_list" from '[ + { + "kind": "reveal", + "source": "tz2DdCuWDUQffJxQLJQb6fk1MDAzKcP9LtMy", + "fee": "469", + "counter": "1", + "gas_limit": "2169", + "storage_limit": "277", + "public_key": "sppk7aDbxVasHyAqzGXYKRHBXCq4xjs1qYD8MsCfgDRb2kb3Wqkssxf" + }, + { + "kind": "transaction", + "source": "tz2DdCuWDUQffJxQLJQb6fk1MDAzKcP9LtMy", + "fee": "733", + "counter": "2", + "gas_limit": "3250", + "storage_limit": "0", + "amount": "10000000", + "destination": "KT1Cdu5oK8anYaDnXoDJMDnF9AKZS7fQ4fah", + "parameters": { + "entrypoint": "balance_of", + "value": { "string": "tz3j873xGK219DrCKYL4usxM8EQgHUmDdbJB" } + } + }, + { + "kind": "origination", + "source": "tz2DdCuWDUQffJxQLJQb6fk1MDAzKcP9LtMy", + "fee": "5871", + "counter": "3", + "gas_limit": "1728", + "storage_limit": "1000000", + "balance": "0", + "script": { + "code": [ + { + "prim": "parameter", + "args": [ + { "prim": "list", "args": [ { "prim": "bytes" } ] } + ] + }, + { "prim": "storage", "args": [ { "prim": "bytes" } ] }, + { "prim": "code", + "args": [ + [ + { "prim": "UNPAIR" }, + { "prim": "SWAP" }, + { "prim": "CONS" }, + { "prim": "CONCAT" }, + { "prim": "NIL", "args": [ { "prim": "operation" } ] }, + { "prim": "PAIR" } + ] + ] + } + ], + "storage": { "bytes": "dead" } + } + }, + { + "kind": "delegation", + "source": "tz2DdCuWDUQffJxQLJQb6fk1MDAzKcP9LtMy", + "fee": "572", + "counter": "4", + "gas_limit": "928", + "storage_limit": "0", + "delegate": "tz1QfAJGaWpTnrDg8KMGkAtUxAavYnJ7pn13" + } + ]' + */ + #[test] + fn all_operations_content_list_encoding() { + let reveal = OperationContent::Reveal(ManagerOperationContent { + source: PublicKeyHash::from_b58check("tz2DdCuWDUQffJxQLJQb6fk1MDAzKcP9LtMy").unwrap(), + fee: 469.into(), + counter: 1.into(), + gas_limit: 2169.into(), + storage_limit: 277.into(), + operation: RevealContent { + pk: PublicKey::from_b58check( + "sppk7aDbxVasHyAqzGXYKRHBXCq4xjs1qYD8MsCfgDRb2kb3Wqkssxf", + ) + .unwrap(), + proof: None, + }, + }); + + let transaction = OperationContent::Transaction(ManagerOperationContent { + source: PublicKeyHash::from_b58check("tz2DdCuWDUQffJxQLJQb6fk1MDAzKcP9LtMy").unwrap(), + fee: 733.into(), + counter: 2.into(), + gas_limit: 3250.into(), + storage_limit: 0.into(), + operation: TransactionContent { + amount: 10000000.into(), + destination: Contract::from_b58check("KT1Cdu5oK8anYaDnXoDJMDnF9AKZS7fQ4fah") + .unwrap(), + parameters: Parameters { + entrypoint: Entrypoint::try_from("balance_of").unwrap(), + // octez-client convert data '"tz3j873xGK219DrCKYL4usxM8EQgHUmDdbJB"' from Michelson to binary + value: hex::decode("0100000024747a336a38373378474b3231394472434b594c347573784d3845516748556d4464624a42").unwrap(), + } + .into(), + }, + }); + + let origination = OperationContent::Origination(ManagerOperationContent { + source: PublicKeyHash::from_b58check("tz2DdCuWDUQffJxQLJQb6fk1MDAzKcP9LtMy").unwrap(), + fee: 5871.into(), + counter: 3.into(), + gas_limit: 1728.into(), + storage_limit: 1000000.into(), + operation: OriginationContent { + balance: 0.into(), + delegate: None, + script: Script { + /* + octez-client convert script " + parameter (list bytes); + storage bytes; + code { UNPAIR ; SWAP ; CONS ; CONCAT; NIL operation; PAIR} + " from Michelson to binary + */ + code: hex::decode( + "020000001f0500055f0369050103690502020000000e037a034c031b031a053d036d0342", + ) + .unwrap(), + // octez-client convert data "0xdead" from Michelson to binary + storage: hex::decode("0a00000002dead").unwrap(), + }, + }, + }); + + let delegation = OperationContent::Delegation(ManagerOperationContent { + source: PublicKeyHash::from_b58check("tz2DdCuWDUQffJxQLJQb6fk1MDAzKcP9LtMy").unwrap(), + fee: 572.into(), + counter: 4.into(), + gas_limit: 928.into(), + storage_limit: 0.into(), + operation: DelegationContent { + delegate: Some( + PublicKeyHash::from_b58check("tz1QfAJGaWpTnrDg8KMGkAtUxAavYnJ7pn13").unwrap(), + ), + }, + }); + + let content_list = OperationContentList { + contents: vec![reveal, transaction, origination, delegation], + }; + + let encoded_content_list = content_list.to_bytes().unwrap(); + + let bytes = hex::decode("6b013a3ea68f994f44c4790124ca15f6feda471f07c2d50301f910950201027844c87c7eda69da7248e3aa977247dbaff53c1fead305721cb0ba03001ae231006c013a3ea68f994f44c4790124ca15f6feda471f07c2dd0502b2190080ade204012c7806ce4f30169a32212537d5383b4810d0322c00ffff0a62616c616e63655f6f66000000290100000024747a336a38373378474b3231394472434b594c347573784d3845516748556d4464624a426d013a3ea68f994f44c4790124ca15f6feda471f07c2ef2d03c00dc0843d000000000024020000001f0500055f0369050103690502020000000e037a034c031b031a053d036d0342000000070a00000002dead6e013a3ea68f994f44c4790124ca15f6feda471f07c2bc0404a00700ff00370f64e2866fd59ff21271a806331465a54ccf79").unwrap(); + assert_eq!(bytes, encoded_content_list); + + let (bytes, decoded_content_list) = + OperationContentList::nom_read(&encoded_content_list).unwrap(); + assert_eq!(content_list, decoded_content_list); + assert!(bytes.is_empty()); + } + + /* + jq -n '[range(1024) | { + "kind": "reveal", + "source": "tz1RwaPNHJVUTcY5cNPD6yK7PsyJ1KBQzAhF", + "fee": "931", + "counter": "236025", + "gas_limit": "692", + "storage_limit": "789", + "public_key": "edpkvNBSpWssiLZYRdLwneLSLunkYjkWbdddeZjh4Y1ZZYJXbBphBX" + }]' > /tmp/tezos-sdk-tests-many-operations.json + octez-codec encode "023-PtSeouLo.operation.contents_list" from /tmp/tezos-sdk-tests-many-operations.json + */ + #[test] + fn many_operations_content_list_encoding() { + const NB_REVEAL: usize = 1024; + let reveal = OperationContent::Reveal(ManagerOperationContent { + source: PublicKeyHash::from_b58check("tz1RwaPNHJVUTcY5cNPD6yK7PsyJ1KBQzAhF").unwrap(), + fee: 931.into(), + counter: 236025.into(), + gas_limit: 692.into(), + storage_limit: 789.into(), + operation: RevealContent { + pk: PublicKey::from_b58check( + "edpkvNBSpWssiLZYRdLwneLSLunkYjkWbdddeZjh4Y1ZZYJXbBphBX", + ) + .unwrap(), + proof: None, + }, + }); + + let content_list = OperationContentList { + contents: vec![reveal; NB_REVEAL], + }; + + let encoded_content_list = content_list.to_bytes().unwrap(); + + /* + octez-codec encode "023-PtSeouLo.operation.contents" from '{ + "kind": "reveal", + "source": "tz1RwaPNHJVUTcY5cNPD6yK7PsyJ1KBQzAhF", + "fee": "931", + "counter": "236025", + "gas_limit": "692", + "storage_limit": "789", + "public_key": "edpkvNBSpWssiLZYRdLwneLSLunkYjkWbdddeZjh4Y1ZZYJXbBphBX" + }' + */ + let bytes = hex::decode("6b0045224867079dbc728ac2f7566bfd0adb31e2b266a307f9b30eb405950600e2f6f7b871734c41d121dc3351a6234433b8df8098447f8cf22c82e094a148c500".repeat(NB_REVEAL)).unwrap(); + + assert_eq!(bytes, encoded_content_list); + + let (bytes, decoded_content_list) = + OperationContentList::nom_read(&encoded_content_list).unwrap(); + assert_eq!(content_list, decoded_content_list); + assert!(bytes.is_empty()); + } + + /* + octez-codec encode "023-PtSeouLo.operation.unsigned" from '{ + "branch": "BLsyiLvAMFD7Yuk84hDuzLqgj7Sr8h4DTMWfxfE9TKtzff9LiFX", + "contents": [ + { + "kind": "reveal", + "source": "tz4W54guExgiT71XKrNxkYQUdTKVQuJUq5mw", + "fee": "711", + "counter": "624692", + "gas_limit": "4959", + "storage_limit": "200", + "public_key": "BLpk1wQsAuwyhy6z5rmRXiB3AQriT3Pes2gBQ9anuQwF99WypxHNyRjBnugF7sCVXCQgmPGdAHEr", + "proof": "BLsigAUhX5J5G9uM94eTfL69paT7y6fiLQMLTxS9sWzHX8vX4317jZhxm2HCsJwdw61NAx3qH4WDzWdTMddkjZSUaRQtbU3L6nyhx2e94dKMCuiJ1Pg9RsM6ZTKjHYo6cNW7wK3k212SPN" + }, + { + "kind": "delegation", + "source": "tz4W54guExgiT71XKrNxkYQUdTKVQuJUq5mw", + "fee": "620", + "counter": "624693", + "gas_limit": "5032", + "storage_limit": "138" + }, + { + "kind": "origination", + "source": "tz4W54guExgiT71XKrNxkYQUdTKVQuJUq5mw", + "fee": "285", + "counter": "624694", + "gas_limit": "7741", + "storage_limit": "139", + "balance": "50", + "delegate": "tz2K1cCKb5HXoXX19yhmr8ChKbn4T8teV9Ei", + "script": { + "code": [ + { "prim": "parameter", "args": [ { "prim": "unit" } ] }, + { "prim": "storage", "args": [ { "prim": "mutez" } ] }, + { "prim": "code", + "args": [ + [ + { "prim": "DROP" }, + { "prim": "BALANCE" }, + { "prim": "NIL", "args": [ { "prim": "operation" } ] }, + { "prim": "PAIR" } + ] + ] + } + ], + "storage": { "int": "0" } + } + } + ] + }' + */ + #[test] + fn unsigned_encoding() { + let reveal = OperationContent::Reveal(ManagerOperationContent { + source: PublicKeyHash::from_b58check("tz4W54guExgiT71XKrNxkYQUdTKVQuJUq5mw").unwrap(), + fee: 711.into(), + counter: 624692.into(), + gas_limit: 4959.into(), + storage_limit: 200.into(), + operation: RevealContent { + pk: PublicKey::from_b58check( + "BLpk1wQsAuwyhy6z5rmRXiB3AQriT3Pes2gBQ9anuQwF99WypxHNyRjBnugF7sCVXCQgmPGdAHEr", + ) + .unwrap(), + proof: Some(BlsSignature::from_b58check("BLsigAUhX5J5G9uM94eTfL69paT7y6fiLQMLTxS9sWzHX8vX4317jZhxm2HCsJwdw61NAx3qH4WDzWdTMddkjZSUaRQtbU3L6nyhx2e94dKMCuiJ1Pg9RsM6ZTKjHYo6cNW7wK3k212SPN").unwrap()), + }, + }); + + let delegation = OperationContent::Delegation(ManagerOperationContent { + source: PublicKeyHash::from_b58check("tz4W54guExgiT71XKrNxkYQUdTKVQuJUq5mw").unwrap(), + fee: 620.into(), + counter: 624693.into(), + gas_limit: 5032.into(), + storage_limit: 138.into(), + operation: DelegationContent { delegate: None }, + }); + + let origination = OperationContent::Origination(ManagerOperationContent { + source: PublicKeyHash::from_b58check("tz4W54guExgiT71XKrNxkYQUdTKVQuJUq5mw").unwrap(), + fee: 285.into(), + counter: 624694.into(), + gas_limit: 7741.into(), + storage_limit: 139.into(), + operation: OriginationContent { + balance: 50.into(), + delegate: Some( + PublicKeyHash::from_b58check("tz2K1cCKb5HXoXX19yhmr8ChKbn4T8teV9Ei").unwrap(), + ), + script: Script { + /* + octez-client convert script " + parameter unit; + storage mutez; + code {DROP; BALANCE; NIL operation; PAIR} + " from Michelson to binary + */ + code: hex::decode( + "02000000190500036c0501036a0502020000000a03200315053d036d0342", + ) + .unwrap(), + // octez-client convert data "0" from Michelson to binary + storage: hex::decode("0000").unwrap(), + }, + }, + }); + + let content_list = OperationContentList { + contents: vec![reveal, delegation, origination], + }; + + let unsigned = UnsignedOperation { + branch: BlockHash::from_b58check("BLsyiLvAMFD7Yuk84hDuzLqgj7Sr8h4DTMWfxfE9TKtzff9LiFX") + .unwrap(), + content_list, + }; + + let encoded_unsigned = unsigned.to_bytes().unwrap(); + + let bytes = hex::decode("99b6629866d30896c198f26db69e0a937cbd0f2c3778cf13f6e977d94f46397b6b03e71335ab27e461cbada3a6d682580cc4e221f78ac705b49026df26c80103acdc542d7eadf36d6f3df4f1cdfa42a7238e89d8a8111f231a4234db9612066c7ae975b7185ab00da40faa07ad89783cff00000060990404e54f617bc3be20da2960744a67b874c446a2e673f028be7103878322710134714da0159196179e6738ea71ed100943b33c213672d40b1fe413b72d0f49805ecfe86939d06d9dcf3a5fcaff9f2f1d55c59a8c4ea2c944888a178b9673e26e03e71335ab27e461cbada3a6d682580cc4e221f78aec04b59026a8278a01006d03e71335ab27e461cbada3a6d682580cc4e221f78a9d02b69026bd3c8b0132ff017553df46bf9d1994760cfa8ca26e0e8a509d3f900000001e02000000190500036c0501036a0502020000000a03200315053d036d0342000000020000").unwrap(); + assert_eq!(bytes, encoded_unsigned); + + let (bytes, decoded_unsigned) = UnsignedOperation::nom_read(&encoded_unsigned).unwrap(); + assert_eq!(unsigned, decoded_unsigned); + assert!(bytes.is_empty()); + } }