// Copyright 2019-2021 Parity Technologies (UK) Ltd. // This file is part of substrate-desub. // // substrate-desub is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // substrate-desub is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with substrate-desub. If not, see . use desub_current::{ decoder::{self, SignedExtensionWithAdditional}, value, Metadata, Value, ValueDef, }; static V14_METADATA_POLKADOT_SCALE: &[u8] = include_bytes!("data/v14_metadata_polkadot.scale"); fn metadata() -> Metadata { Metadata::from_bytes(V14_METADATA_POLKADOT_SCALE).expect("valid metadata") } fn to_bytes(hex_str: &str) -> Vec { let hex_str = hex_str.strip_prefix("0x").expect("0x should prefix hex encoded bytes"); hex::decode(hex_str).expect("valid bytes from hex") } fn empty_value() -> Value<()> { Value::unnamed_composite(vec![]) } fn singleton_value(x: Value<()>) -> Value<()> { Value::unnamed_composite(vec![x]) } fn hash_value(xs: Vec) -> Value<()> { singleton_value(Value::unnamed_composite(xs.iter().map(|x| Value::u8(*x)).collect())) } fn assert_args_equal(args: &[Value], expected: Vec>) { let args: Vec<_> = args.into_iter().map(|v| v.clone().without_context()).collect(); assert_eq!(&args, &expected); } // These tests are intended to roughly check that we can decode a range of "real" extrinsics into something // that looks sane. // // How did I write these tests (and how can you add more)? // // 1. Start up a polkadot node locally using `cargo run -- --dev --tmp` in the polkadot repo (I happened to // be on polkadot master, commit 6da1cf6233728a8142e4b9cebdcf29cd67eb8352). // // 2. I downloaded the SCALE encoded metadata from it and save in this repo to include in the tests. You don't // need to do this again (unless you'd like to make sure it's fully uptodate). // // curl -sX POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"state_getMetadata", "id": 1}' localhost:9933 \ // | jq .result \ // | cut -d '"' -f 2 \ // | xxd -r -p > ./tests/node_runtime.scale // // 3. Navigate to https://polkadot.js.org/apps/#/explorer and switch it to pointing at a local development node. // (the one you just started up in step 1). // // 4. In "Developer -> Extrinsics", we can now build, sign and submit extrinsics. // - If you want the hex str for a signed extrinsic, Keep the network tab open and find the open WS connection. When an extrinsic is // submitted, You'll see a new message to the method "author_submitAndWatchExtrinsic". // - If you want an unsigned extrinsic, just copy the "call data" hex string and prepend a "04" after the "0x" and before everything // else, to turn the call data into a V4 unsigned extrinsic (minus the byte length, which would normally be first). We can test // decoding of this using `decode_unwrapped_extrinsic`. // // 5. With that in mind, see the tests below for examples of how we can test decoding of this extrinsic hex string you've acquired. #[test] fn balance_transfer_signed() { let meta = metadata(); // Balances.transfer (amount: 12345) let ext_bytes = &mut &*to_bytes("0x31028400d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d016ada9b477ef454972200e098f1186d4a2aeee776f1f6a68609797f5ba052906ad2427bdca865442158d118e2dfc82226077e4dfdff975d005685bab66eefa38a150200000500001cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07ce5c0"); let ext = decoder::decode_extrinsic(&meta, ext_bytes).expect("can decode extrinsic"); assert!(ext_bytes.is_empty(), "No more bytes expected"); assert_eq!(ext.call_data.pallet_name, "Balances"); assert_eq!(&*ext.call_data.ty.name(), "transfer"); assert_eq!(ext.call_data.arguments.len(), 2); assert_eq!(ext.call_data.arguments[1].clone().without_context(), Value::u128(12345)); } #[test] fn balance_transfer_all_signed() { let meta = metadata(); // Balances.transfer_all (keepalive: false) let ext_bytes = &mut &*to_bytes("0x2d028400d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d01f0431ffe387134b4f84d92d3c3f1ac18c0f42237ad7dbd455bb0cf8a18efb1760528f052b2219ad1601d9a4719e1a446cf307bf6d7e9c56175bfe6e7bf8cbe81450304000504001cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07c00"); let ext = decoder::decode_extrinsic(&meta, ext_bytes).expect("can decode extrinsic"); assert!(ext_bytes.is_empty(), "No more bytes expected"); assert_eq!(ext.call_data.pallet_name, "Balances"); assert_eq!(&*ext.call_data.ty.name(), "transfer_all"); assert_eq!(ext.call_data.arguments.len(), 2); assert_eq!(ext.call_data.arguments[1].clone().without_context(), Value::bool(false)); } /// This test is interesting because: /// a) The Auctions pallet index is not the same as where it is listed in the list of pallets. /// b) One of the arguments is a compact-encoded wrapper struct, which caused a hiccup. #[test] fn auctions_bid_unsigned() { let meta = metadata(); // Auctions.bid (Args: (1,), 2, 3, 4, 5, all compact encoded). let ext_bytes = &mut &*to_bytes("0x04480104080c1014"); let ext = decoder::decode_unwrapped_extrinsic(&meta, ext_bytes).expect("can decode extrinsic"); assert!(ext_bytes.is_empty(), "No more bytes expected"); assert_eq!(ext.call_data.pallet_name, "Auctions"); assert_eq!(&*ext.call_data.ty.name(), "bid"); assert_eq!(ext.call_data.arguments.len(), 5); assert_args_equal( &ext.call_data.arguments, vec![singleton_value(Value::u32(1)), Value::u32(2), Value::u32(3), Value::u32(4), Value::u128(5)], ); } #[test] fn auctions_bid_unsigned_excess_bytes_allowed() { let meta = metadata(); // Auctions.bid (Args: (1,), 2, 3, 4, 5, all compact encoded). let mut ext_bytes = to_bytes("0x04480104080c1014"); // Push some extra bytes to the end that we expect not to be consumed: ext_bytes.extend(b"extra bytes!"); let ext_bytes_cursor = &mut &*ext_bytes; decoder::decode_unwrapped_extrinsic(&meta, ext_bytes_cursor).expect("can decode extrinsic"); assert_eq!(ext_bytes_cursor, b"extra bytes!"); } #[test] fn system_fill_block_unsigned() { let meta = metadata(); // System.fill_block (Args: Perblock(1234)). let ext_bytes = &mut &*to_bytes("0x040000d2040000"); let ext = decoder::decode_unwrapped_extrinsic(&meta, ext_bytes).expect("can decode extrinsic"); assert!(ext_bytes.is_empty(), "No more bytes expected"); assert_eq!(ext.call_data.pallet_name, "System"); assert_eq!(&*ext.call_data.ty.name(), "fill_block"); assert_eq!(ext.call_data.arguments.len(), 1); assert_args_equal(&ext.call_data.arguments, vec![singleton_value(Value::u32(1234))]); } /// This test is interesting because you provide a nested enum representing a call /// as an argument to this call. #[test] fn technical_committee_execute_unsigned() { let meta = metadata(); // TechnicalCommittee.execute (Args: Balances.transfer(Alice -> Bob, 12345), 500). let ext_bytes = &mut &*to_bytes("0x0410010500001cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07ce5c0d107"); let ext = decoder::decode_unwrapped_extrinsic(&meta, ext_bytes).expect("can decode extrinsic"); assert!(ext_bytes.is_empty(), "No more bytes expected"); assert_eq!(ext.call_data.pallet_name, "TechnicalCommittee"); assert_eq!(&*ext.call_data.ty.name(), "execute"); assert_eq!(ext.call_data.arguments.len(), 2); // It's a bit hard matching the entire thing, so we just verify that the first arg looks like // a variant representing a call to "Balances.transfer". assert!(matches!(&ext.call_data.arguments[0], Value { value: ValueDef::Variant(value::Variant { name, values: value::Composite::Unnamed(args) }), .. } if &*name == "Balances" && matches!(&args[0], Value { value: ValueDef::Variant(value::Variant { name, ..}), .. } if &*name == "transfer") )); assert_eq!(ext.call_data.arguments[1].clone().without_context(), Value::u32(500)); } #[test] fn tips_report_awesome_unsigned() { let meta = metadata(); // Tips.report_awesome (Args: b"This person rocks!", AccountId). let ext_bytes = &mut &*to_bytes("0x042300485468697320706572736f6e20726f636b73211cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07c"); let ext = decoder::decode_unwrapped_extrinsic(&meta, ext_bytes).expect("can decode extrinsic"); assert!(ext_bytes.is_empty(), "No more bytes expected"); assert_eq!(ext.call_data.pallet_name, "Tips"); assert_eq!(&*ext.call_data.ty.name(), "report_awesome"); assert_eq!(ext.call_data.arguments.len(), 2); assert_eq!( ext.call_data.arguments[0].clone().without_context(), Value::unnamed_composite("This person rocks!".bytes().map(Value::u8).collect()) ); } // Named structs shouldn't be an issue; this extrinsic contains one. #[test] fn vesting_force_vested_transfer_unsigned() { let meta = metadata(); // Vesting.force_vested_transfer (Args: AccountId, AccountId, { locked: 1u128, perBlock: 2u128, startingBlock: 3u32 }). let ext_bytes = &mut &*to_bytes("0x04190300d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48010000000000000000000000000000000200000000000000000000000000000003000000"); let ext = decoder::decode_unwrapped_extrinsic(&meta, ext_bytes).expect("can decode extrinsic"); assert!(ext_bytes.is_empty(), "No more bytes expected"); assert_eq!(ext.call_data.pallet_name, "Vesting"); assert_eq!(&*ext.call_data.ty.name(), "force_vested_transfer"); assert_eq!(ext.call_data.arguments.len(), 3); assert_eq!( ext.call_data.arguments[2].clone().without_context(), Value::named_composite(vec![ ("locked".into(), Value::u128(1)), ("per_block".into(), Value::u128(2)), ("starting_block".into(), Value::u32(3)), ]) ); } #[test] fn can_decode_multiple_extrinsics_with_extra_bytes() { let meta = metadata(); // the same extrinsic repeated 3 times: let extrinsics_hex = "0x0C2004480104080c10142004480104080c10142004480104080c1014"; let mut extrinsics_bytes = hex::decode(extrinsics_hex.strip_prefix("0x").unwrap()).unwrap(); // add some extra bytes, which shouldn't be consumed: extrinsics_bytes.extend(b"extra bytes!"); let extrinsics_cursor = &mut &*extrinsics_bytes; let extrinsics = decoder::decode_extrinsics(&meta, extrinsics_cursor).unwrap(); assert_eq!(extrinsics_cursor, b"extra bytes!"); assert_eq!(extrinsics.len(), 3); } // We can decode the payload that we'd be getting signed, too. #[test] fn can_decode_signer_payload() { let meta = metadata(); let signer_payload = &mut &*to_bytes("0x0706b9340000962300000800000091b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c31c81d421f68281950ad2901291603b5e49fc5c872f129e75433f4b55f07ca072"); let r = decoder::decode_signer_payload(&meta, signer_payload).expect("can decode signer payload"); assert_eq!(signer_payload.len(), 0); assert_eq!(r.call_data.pallet_name, "Staking"); assert_eq!(&*r.call_data.ty.name(), "chill"); assert_eq!(r.call_data.arguments, vec![]); // Expected tuples of name, extension, additional. let expected = vec![ ("CheckSpecVersion", empty_value(), Value::u32(9110)), ("CheckTxVersion", empty_value(), Value::u32(8)), ( "CheckGenesis", empty_value(), hash_value(to_bytes("0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3")), ), ( "CheckMortality", singleton_value(Value::variant("Mortal185".to_string(), value::Composite::Unnamed(vec![Value::u8(52)]))), hash_value(to_bytes("0x1c81d421f68281950ad2901291603b5e49fc5c872f129e75433f4b55f07ca072")), ), ("CheckNonce", singleton_value(Value::u32(0)), empty_value()), ("CheckWeight", empty_value(), empty_value()), ("ChargeTransactionPayment", singleton_value(Value::u128(0)), empty_value()), ("PrevalidateAttests", empty_value(), empty_value()), ]; for (actual, expected) in r.extensions.into_iter().zip(expected) { let (name, SignedExtensionWithAdditional { extension, additional }) = actual; let (expected_name, expected_extension, expected_additional) = expected; assert_eq!(&*name, expected_name); assert_eq!(extension.without_context(), expected_extension); assert_eq!(additional.without_context(), expected_additional); } }