diff --git a/pallets/account-linker/src/benchmarking.rs b/pallets/account-linker/src/benchmarking.rs index 69fb92e..a870e42 100644 --- a/pallets/account-linker/src/benchmarking.rs +++ b/pallets/account-linker/src/benchmarking.rs @@ -1,49 +1,45 @@ #![cfg(feature = "runtime-benchmarks")] use super::*; -use frame_benchmarking::{benchmarks, account, impl_benchmark_test_suite}; -use frame_system::RawOrigin; use crate::Pallet as AccountLinker; +use frame_benchmarking::{account, benchmarks, impl_benchmark_test_suite}; +use frame_system::RawOrigin; use sp_std::prelude::*; const SEED: u32 = 0; -benchmarks!{ - link_eth { - let caller = account("caller", 0, 0); - let account_id: T::AccountId = account("Alice", 0, SEED); - let index: u32 = 0; - let addr_expected: EthAddress = [16, 146, 71, 235, 177, 95, 237, 92, 255, 45, 73, 190, 133, 132, 185, 41, 14, 77, 9, 207]; - let expiring_block_number: u32 = 10000; - let sig: Signature = [133, 13, 66, 20, 141, 102, 233, 186, 153, 38, 81, 149, 29, 16, 191, 87, 206, 103, 230, 184, 32, 165, 174, 40, 221, 54, 212, 61, 132, 38, 254, 39, 19, 118, 77, 20, 241, 238, 52, 206, 124, 232, 254, 37, 109, 69, 191, 253, 242, 19, 48, 32, 92, 134, 123, 2, 6, 223, 233, 225, 129, 41, 235, 116, 28]; - }: link_eth(RawOrigin::Signed(caller), account_id.clone(), index, addr_expected, expiring_block_number.into(), sig) - - link_btc { - let caller = account("caller", 0, 0); - let account_id: T::AccountId = account("Alice", 0, SEED); - let index: u32 = 0; - let addr_expected = vec![49, 51, 121, 55, 106, 72, 52, 85, 57, 113, 68, 112, 69, 77, 77, 119, 87, 90, 117, 52, 99, 122, 52, 107, 55, 67, 81, 107, 90, 72, 100, 101, 113, 71]; - let expiring_block_number: u32 = 10000; - let sig: Signature = [250, 57, 156, 18, 181, 153, 186, 77, 81, 242, 31, 146, 82, 115, 85, 163, 136, 220, 104, 194, 98, 88, 28, 109, 163, 113, 12, 47, 193, 183, 189, 106, 41, 163, 172, 76, 129, 83, 66, 195, 126, 213, 207, 91, 186, 70, 255, 125, 111, 38, 123, 240, 178, 101, 22, 192, 133, 22, 245, 109, 50, 175, 225, 208, 0]; - }: link_btc(RawOrigin::Signed(caller), account_id.clone(), index, addr_expected, expiring_block_number.into(), sig) - - link_polkadot { - let caller = account("caller", 0, 0); - let linked_account: T::AccountId = account("Alice", 0, SEED); - let index: u32 = 0; - }: _(RawOrigin::Signed(caller), linked_account, index) - - accept_polkadot { - let caller: T::AccountId = account("caller", 0, 0); - let linked_account: T::AccountId = account("Alice", 0, SEED); - let index: u32 = 0; - crate::Pallet::::link_polkadot(RawOrigin::Signed(caller.clone()).into(), linked_account.clone(), index)?; - }: _(RawOrigin::Signed(linked_account), caller) +benchmarks! { + link_eth { + let caller = account("caller", 0, 0); + let account_id: T::AccountId = account("Alice", 0, SEED); + let index: u32 = 0; + let addr_expected: EthAddress = [16, 146, 71, 235, 177, 95, 237, 92, 255, 45, 73, 190, 133, 132, 185, 41, 14, 77, 9, 207]; + let expiring_block_number: u32 = 10000; + let sig: Signature = [133, 13, 66, 20, 141, 102, 233, 186, 153, 38, 81, 149, 29, 16, 191, 87, 206, 103, 230, 184, 32, 165, 174, 40, 221, 54, 212, 61, 132, 38, 254, 39, 19, 118, 77, 20, 241, 238, 52, 206, 124, 232, 254, 37, 109, 69, 191, 253, 242, 19, 48, 32, 92, 134, 123, 2, 6, 223, 233, 225, 129, 41, 235, 116, 28]; + }: link_eth(RawOrigin::Signed(caller), account_id.clone(), index, addr_expected, expiring_block_number.into(), sig) + + link_btc { + let caller = account("caller", 0, 0); + let account_id: T::AccountId = account("Alice", 0, SEED); + let index: u32 = 0; + let addr_expected = vec![49, 51, 121, 55, 106, 72, 52, 85, 57, 113, 68, 112, 69, 77, 77, 119, 87, 90, 117, 52, 99, 122, 52, 107, 55, 67, 81, 107, 90, 72, 100, 101, 113, 71]; + let expiring_block_number: u32 = 10000; + let sig: Signature = [250, 57, 156, 18, 181, 153, 186, 77, 81, 242, 31, 146, 82, 115, 85, 163, 136, 220, 104, 194, 98, 88, 28, 109, 163, 113, 12, 47, 193, 183, 189, 106, 41, 163, 172, 76, 129, 83, 66, 195, 126, 213, 207, 91, 186, 70, 255, 125, 111, 38, 123, 240, 178, 101, 22, 192, 133, 22, 245, 109, 50, 175, 225, 208, 0]; + }: link_btc(RawOrigin::Signed(caller), account_id.clone(), index, addr_expected, expiring_block_number.into(), sig) + + link_polkadot { + let caller = account("caller", 0, 0); + let linked_account: T::AccountId = account("Alice", 0, SEED); + let index: u32 = 0; + }: _(RawOrigin::Signed(caller), linked_account, index) + + accept_polkadot { + let caller: T::AccountId = account("caller", 0, 0); + let linked_account: T::AccountId = account("Alice", 0, SEED); + let index: u32 = 0; + crate::Pallet::::link_polkadot(RawOrigin::Signed(caller.clone()).into(), linked_account.clone(), index)?; + }: _(RawOrigin::Signed(linked_account), caller) } -impl_benchmark_test_suite!( - AccountLinker, - crate::mock::new_test_ext(), - crate::mock::Test, -); +impl_benchmark_test_suite!(AccountLinker, crate::mock::new_test_ext(), crate::mock::Test,); diff --git a/pallets/account-linker/src/btc/base58.rs b/pallets/account-linker/src/btc/base58.rs index 8c27b4d..2b8dcc2 100644 --- a/pallets/account-linker/src/btc/base58.rs +++ b/pallets/account-linker/src/btc/base58.rs @@ -29,7 +29,7 @@ impl ToBase58 for [u8] { carry /= 58; // in original trezor implementation it was underflowing - if j > 0 { + if j > 0 { j -= 1; } } @@ -57,8 +57,8 @@ impl ToBase58 for [u8] { #[cfg(test)] mod tests { use super::ToBase58; - use std::str::from_utf8; use hex::decode; + use std::str::from_utf8; #[test] fn test_to_base58_basic() { @@ -72,7 +72,10 @@ mod tests { assert_eq!(from_utf8(&[49, 49].to_base58()).unwrap(), "4k8"); assert_eq!(from_utf8(&b"abc".to_base58()).unwrap(), "ZiCa"); assert_eq!(from_utf8(&b"1234598760".to_base58()).unwrap(), "3mJr7AoUXx2Wqd"); - assert_eq!(from_utf8(&b"abcdefghijklmnopqrstuvwxyz".to_base58()).unwrap(), "3yxU3u1igY8WkgtjK92fbJQCd4BZiiT1v25f"); + assert_eq!( + from_utf8(&b"abcdefghijklmnopqrstuvwxyz".to_base58()).unwrap(), + "3yxU3u1igY8WkgtjK92fbJQCd4BZiiT1v25f" + ); } #[test] diff --git a/pallets/account-linker/src/btc/legacy.rs b/pallets/account-linker/src/btc/legacy.rs index c4734f9..b37c085 100644 --- a/pallets/account-linker/src/btc/legacy.rs +++ b/pallets/account-linker/src/btc/legacy.rs @@ -1,28 +1,28 @@ -use sha2::{Digest, Sha256}; use ripemd160::Ripemd160; +use sha2::{Digest, Sha256}; pub fn btc_addr_from_pk(pk: &[u8]) -> [u8; 25] { - let mut result = [0u8; 25]; - - // Now only support P2PKH (Mainnet) prefix = 0 - result[0] = 0; - result[1..21].copy_from_slice(&hash160(pk)); - let cs = checksum(&result[0..21]); - result[21..25].copy_from_slice(&cs); - result + let mut result = [0u8; 25]; + + // Now only support P2PKH (Mainnet) prefix = 0 + result[0] = 0; + result[1..21].copy_from_slice(&hash160(pk)); + let cs = checksum(&result[0..21]); + result[21..25].copy_from_slice(&cs); + result } pub fn hash160(bytes: &[u8]) -> [u8; 20] { - let mut hasher_sha256 = Sha256::new(); - hasher_sha256.update(bytes); - let digest = hasher_sha256.finalize(); + let mut hasher_sha256 = Sha256::new(); + hasher_sha256.update(bytes); + let digest = hasher_sha256.finalize(); - let mut hasher_ripemd = Ripemd160::new(); - hasher_ripemd.update(digest); + let mut hasher_ripemd = Ripemd160::new(); + hasher_ripemd.update(digest); - let mut ret = [0; 20]; - ret.copy_from_slice(&hasher_ripemd.finalize()[..]); - ret + let mut ret = [0; 20]; + ret.copy_from_slice(&hasher_ripemd.finalize()[..]); + ret } fn checksum(input: &[u8]) -> [u8; 4] { @@ -39,16 +39,16 @@ fn checksum(input: &[u8]) -> [u8; 4] { /// # Returns /// * The double SHA256 hash encoded as LE bytes from data fn dsha256(bytes: &[u8]) -> [u8; 32] { - let mut hasher = Sha256::new(); - hasher.update(bytes); - let digest = hasher.finalize(); + let mut hasher = Sha256::new(); + hasher.update(bytes); + let digest = hasher.finalize(); - let mut second_hasher = Sha256::new(); - second_hasher.update(digest); + let mut second_hasher = Sha256::new(); + second_hasher.update(digest); - let mut ret = [0; 32]; - ret.copy_from_slice(&second_hasher.finalize()[..]); - ret + let mut ret = [0; 32]; + ret.copy_from_slice(&second_hasher.finalize()[..]); + ret } // test data can be obtained from here http://gobittest.appspot.com/Address @@ -59,30 +59,29 @@ mod tests { #[test] fn correct_dhash160() { - let pk = decode("0450863AD64A87AE8A2FE83C1AF1A8403CB53F53E486D8511DAD8A04887E5B23522CD470243453A299FA9E77237716103ABC11A1DF38855ED6F2EE187E9C582BA6").unwrap(); - let hash = hash160(&pk); + let hash = hash160(&pk); - let result = decode("010966776006953D5567439E5E39F86A0D273BEE").unwrap(); + let result = decode("010966776006953D5567439E5E39F86A0D273BEE").unwrap(); let mut hash_expected = [0u8; 20]; hash_expected[0..20].copy_from_slice(&result[0..20]); assert_eq!(hash, hash_expected); - } + } - #[test] - fn correct_btc_addr_from_pk() { - let pk = decode("0450863AD64A87AE8A2FE83C1AF1A8403CB53F53E486D8511DAD8A04887E5B23522CD470243453A299FA9E77237716103ABC11A1DF38855ED6F2EE187E9C582BA6").unwrap(); - let mut pk_input = [0u8; 65]; - pk_input[0..65].copy_from_slice(&pk[0..65]); - - let addr = btc_addr_from_pk(&pk_input); + #[test] + fn correct_btc_addr_from_pk() { + let pk = decode("0450863AD64A87AE8A2FE83C1AF1A8403CB53F53E486D8511DAD8A04887E5B23522CD470243453A299FA9E77237716103ABC11A1DF38855ED6F2EE187E9C582BA6").unwrap(); + let mut pk_input = [0u8; 65]; + pk_input[0..65].copy_from_slice(&pk[0..65]); - let addr_expected_hex = decode("00010966776006953D5567439E5E39F86A0D273BEED61967F6").unwrap(); - let mut addr_expected = [0u8; 25]; - addr_expected[0..25].copy_from_slice(&addr_expected_hex[0..25]); - assert_eq!(addr, addr_expected); - } + let addr = btc_addr_from_pk(&pk_input); -} \ No newline at end of file + let addr_expected_hex = + decode("00010966776006953D5567439E5E39F86A0D273BEED61967F6").unwrap(); + let mut addr_expected = [0u8; 25]; + addr_expected[0..25].copy_from_slice(&addr_expected_hex[0..25]); + assert_eq!(addr, addr_expected); + } +} diff --git a/pallets/account-linker/src/btc/mod.rs b/pallets/account-linker/src/btc/mod.rs index 0641643..28c0484 100644 --- a/pallets/account-linker/src/btc/mod.rs +++ b/pallets/account-linker/src/btc/mod.rs @@ -1,3 +1,3 @@ pub mod base58; pub mod legacy; -pub mod witness; \ No newline at end of file +pub mod witness; diff --git a/pallets/account-linker/src/btc/witness.rs b/pallets/account-linker/src/btc/witness.rs index 098917a..cd22fb5 100644 --- a/pallets/account-linker/src/btc/witness.rs +++ b/pallets/account-linker/src/btc/witness.rs @@ -22,117 +22,114 @@ use sp_std::prelude::*; pub struct WitnessProgram { - /// Witness program version - pub version: u8, - /// Witness program content - pub program: Vec + /// Witness program version + pub version: u8, + /// Witness program content + pub program: Vec, } impl WitnessProgram { - /// Converts a Witness Program to a SegWit Address - pub fn to_address(&self, hrp: Vec) -> Result, &'static str> { - // Verify that the program is valid - let mut data: Vec = vec![self.version]; - // Convert 8-bit program into 5-bit - let p5 = self.program.to_base32(); - // let p5 = convert_bits(self.program.to_vec(), 8, 5, true)?; - data.extend_from_slice(&p5); - let b32 = data.encode(hrp)?; - Ok(b32) - } - - /// Extracts a WitnessProgram out of a provided script public key - pub fn from_scriptpubkey(pubkey: &[u8]) -> Result { - // We need a version byte and a program length byte, with a program at - // least 2 bytes long. - if pubkey.len() < 4 { - return Err("TooShort") - } - let proglen: usize = pubkey[1] as usize; - // Check that program length byte is consistent with pubkey length - if pubkey.len() != 2 + proglen { - return Err("InvalidLengthByte") - } - // Process script version - let mut v: u8 = pubkey[0]; - if v > 0x50 { - v -= 0x50; - } - let program = &pubkey[2..]; - Ok(WitnessProgram { - version: v, - program: program.to_vec() - }) - } + /// Converts a Witness Program to a SegWit Address + pub fn to_address(&self, hrp: Vec) -> Result, &'static str> { + // Verify that the program is valid + let mut data: Vec = vec![self.version]; + // Convert 8-bit program into 5-bit + let p5 = self.program.to_base32(); + // let p5 = convert_bits(self.program.to_vec(), 8, 5, true)?; + data.extend_from_slice(&p5); + let b32 = data.encode(hrp)?; + Ok(b32) + } + + /// Extracts a WitnessProgram out of a provided script public key + pub fn from_scriptpubkey(pubkey: &[u8]) -> Result { + // We need a version byte and a program length byte, with a program at + // least 2 bytes long. + if pubkey.len() < 4 { + return Err("TooShort") + } + let proglen: usize = pubkey[1] as usize; + // Check that program length byte is consistent with pubkey length + if pubkey.len() != 2 + proglen { + return Err("InvalidLengthByte") + } + // Process script version + let mut v: u8 = pubkey[0]; + if v > 0x50 { + v -= 0x50; + } + let program = &pubkey[2..]; + Ok(WitnessProgram { version: v, program: program.to_vec() }) + } } const SEP: u8 = b'1'; const ALPHABET: &'static [u8] = b"qpzry9x8gf2tvdw0s3jn54khce6mua7l"; pub trait Bech32 { - fn encode(&self, hrp: Vec) -> Result, &'static str>; + fn encode(&self, hrp: Vec) -> Result, &'static str>; } impl Bech32 for [u8] { - fn encode(&self, hrp: Vec) -> Result, &'static str> { - if hrp.len() < 1 { - return Err("invalidData") - } - - let mut combined: Vec = self.clone().to_vec(); - combined.extend_from_slice(&create_checksum(&hrp, &self.to_vec())); - let mut encoded = hrp; - encoded.push(SEP); - for p in combined { - if p >= 32 { - return Err("invalidData") - } - encoded.push(ALPHABET[p as usize]); - } - Ok(encoded) - } + fn encode(&self, hrp: Vec) -> Result, &'static str> { + if hrp.len() < 1 { + return Err("invalidData") + } + + let mut combined: Vec = self.clone().to_vec(); + combined.extend_from_slice(&create_checksum(&hrp, &self.to_vec())); + let mut encoded = hrp; + encoded.push(SEP); + for p in combined { + if p >= 32 { + return Err("invalidData") + } + encoded.push(ALPHABET[p as usize]); + } + Ok(encoded) + } } const GEN: [u32; 5] = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3]; fn hrp_expand(hrp: &Vec) -> Vec { - let mut v: Vec = Vec::new(); - for b in hrp { - v.push(*b >> 5); - } - v.push(0); - for b in hrp { - v.push(*b & 0x1f); - } - v + let mut v: Vec = Vec::new(); + for b in hrp { + v.push(*b >> 5); + } + v.push(0); + for b in hrp { + v.push(*b & 0x1f); + } + v } fn create_checksum(hrp: &Vec, data: &Vec) -> Vec { - let mut values: Vec = hrp_expand(hrp); - values.extend_from_slice(data); - // Pad with 6 zeros - values.extend_from_slice(&[0u8; 6]); - let plm: u32 = polymod(values) ^ 1; - let mut checksum: Vec = Vec::new(); - for p in 0..6 { - checksum.push(((plm >> 5 * (5 - p)) & 0x1f) as u8); - } - checksum + let mut values: Vec = hrp_expand(hrp); + values.extend_from_slice(data); + // Pad with 6 zeros + values.extend_from_slice(&[0u8; 6]); + let plm: u32 = polymod(values) ^ 1; + let mut checksum: Vec = Vec::new(); + for p in 0..6 { + checksum.push(((plm >> 5 * (5 - p)) & 0x1f) as u8); + } + checksum } fn polymod(values: Vec) -> u32 { - let mut chk: u32 = 1; - let mut b: u8; - for v in values { - b = (chk >> 25) as u8; - chk = (chk & 0x1ffffff) << 5 ^ (v as u32); - for i in 0..5 { - if (b >> i) & 1 == 1 { - chk ^= GEN[i] - } - } - } - chk + let mut chk: u32 = 1; + let mut b: u8; + for v in values { + b = (chk >> 25) as u8; + chk = (chk & 0x1ffffff) << 5 ^ (v as u32); + for i in 0..5 { + if (b >> i) & 1 == 1 { + chk ^= GEN[i] + } + } + } + chk } /// A trait for converting a value to base58 encoded string. @@ -142,76 +139,74 @@ pub trait ToBase32 { } impl ToBase32 for [u8] { - // /// Convert between bit sizes - // fn to_base32(&self) -> Vec { - // let from: u32 = 8; - // let to: u32 = 5; - - // let mut acc: u32 = 0; - // let mut bits: u32 = 0; - // let mut ret: Vec = Vec::new(); - // let maxv: u32 = (1<= to { - // bits -= to; - // ret.push(((acc >> bits) & maxv) as u8); - // } - // } - // if bits > 0 { - // ret.push(((acc << (to - bits)) & maxv) as u8); - // } - - // ret - // } + // /// Convert between bit sizes + // fn to_base32(&self) -> Vec { + // let from: u32 = 8; + // let to: u32 = 5; + + // let mut acc: u32 = 0; + // let mut bits: u32 = 0; + // let mut ret: Vec = Vec::new(); + // let maxv: u32 = (1<= to { + // bits -= to; + // ret.push(((acc >> bits) & maxv) as u8); + // } + // } + // if bits > 0 { + // ret.push(((acc << (to - bits)) & maxv) as u8); + // } + + // ret + // } fn to_base32(&self) -> Vec { - // Amount of bits left over from last round, stored in buffer. - let mut buffer_bits = 0u32; - // Holds all unwritten bits left over from last round. The bits are stored beginning from - // the most significant bit. E.g. if buffer_bits=3, then the byte with bits a, b and c will - // look as follows: [a, b, c, 0, 0, 0, 0, 0] - let mut buffer: u8 = 0; - - let mut result = Vec::new(); - - - for b in self.into_iter() { - // Write first u5 if we have to write two u5s this round. That only happens if the - // buffer holds too many bits, so we don't have to combine buffer bits with new bits - // from this rounds byte. - if buffer_bits >= 5 { - result.push((buffer & 0b1111_1000) >> 3); - buffer <<= 5; - buffer_bits -= 5; - } - - // Combine all bits from buffer with enough bits from this rounds byte so that they fill - // a u5. Save remaining bits from byte to buffer. - let from_buffer = buffer >> 3; - let from_byte = b >> (3 + buffer_bits); // buffer_bits <= 4 - - result.push(from_buffer | from_byte); - buffer = b << (5 - buffer_bits); - buffer_bits += 3; - } - - // There can be at most two u5s left in the buffer after processing all bytes, write them. - if buffer_bits >= 5 { - result.push((buffer & 0b1111_1000) >> 3); - buffer <<= 5; - buffer_bits -= 5; - } - - if buffer_bits != 0 { - result.push(buffer >> 3); - } - - result - + // Amount of bits left over from last round, stored in buffer. + let mut buffer_bits = 0u32; + // Holds all unwritten bits left over from last round. The bits are stored beginning from + // the most significant bit. E.g. if buffer_bits=3, then the byte with bits a, b and c will + // look as follows: [a, b, c, 0, 0, 0, 0, 0] + let mut buffer: u8 = 0; + + let mut result = Vec::new(); + + for b in self.into_iter() { + // Write first u5 if we have to write two u5s this round. That only happens if the + // buffer holds too many bits, so we don't have to combine buffer bits with new bits + // from this rounds byte. + if buffer_bits >= 5 { + result.push((buffer & 0b1111_1000) >> 3); + buffer <<= 5; + buffer_bits -= 5; + } + + // Combine all bits from buffer with enough bits from this rounds byte so that they fill + // a u5. Save remaining bits from byte to buffer. + let from_buffer = buffer >> 3; + let from_byte = b >> (3 + buffer_bits); // buffer_bits <= 4 + + result.push(from_buffer | from_byte); + buffer = b << (5 - buffer_bits); + buffer_bits += 3; + } + + // There can be at most two u5s left in the buffer after processing all bytes, write them. + if buffer_bits >= 5 { + result.push((buffer & 0b1111_1000) >> 3); + buffer <<= 5; + buffer_bits -= 5; + } + + if buffer_bits != 0 { + result.push(buffer >> 3); + } + + result } } @@ -222,57 +217,54 @@ mod tests { #[test] fn test_to_base32_basic() { - assert_eq!(from_utf8(&vec![0x00, 0x01, 0x02].encode(b"bech32".to_vec()).unwrap()).unwrap(), "bech321qpz4nc4pe"); - } - - #[test] - fn valid_address() { - let pairs: Vec<(&str, Vec)> = vec![ - ( - "BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4", - vec![ - 0x00, 0x14, 0x75, 0x1e, 0x76, 0xe8, 0x19, 0x91, 0x96, 0xd4, 0x54, - 0x94, 0x1c, 0x45, 0xd1, 0xb3, 0xa3, 0x23, 0xf1, 0x43, 0x3b, 0xd6 - ] - ), - ( - "bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx", - vec![ - 0x51, 0x28, 0x75, 0x1e, 0x76, 0xe8, 0x19, 0x91, 0x96, 0xd4, 0x54, - 0x94, 0x1c, 0x45, 0xd1, 0xb3, 0xa3, 0x23, 0xf1, 0x43, 0x3b, 0xd6, - 0x75, 0x1e, 0x76, 0xe8, 0x19, 0x91, 0x96, 0xd4, 0x54, 0x94, 0x1c, - 0x45, 0xd1, 0xb3, 0xa3, 0x23, 0xf1, 0x43, 0x3b, 0xd6 - ] - ), - ( - "BC1SW50QA3JX3S", - vec![ - 0x60, 0x02, 0x75, 0x1e - ] - ), - ( - "bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj", - vec![ - 0x52, 0x10, 0x75, 0x1e, 0x76, 0xe8, 0x19, 0x91, 0x96, 0xd4, 0x54, - 0x94, 0x1c, 0x45, 0xd1, 0xb3, 0xa3, 0x23 - ] - ), - ]; - for p in pairs { - let (address, scriptpubkey) = p; - - let hrp = b"bc".to_vec(); - - let spk_result = WitnessProgram::from_scriptpubkey(&scriptpubkey); - assert!(spk_result.is_ok()); - let prog = spk_result.unwrap(); - - let enc_result = prog.to_address(hrp); - assert!(enc_result.is_ok()); - - let enc_address = enc_result.unwrap(); - assert_eq!(address.to_lowercase(), from_utf8(&enc_address).unwrap().to_lowercase()); - } - } - -} \ No newline at end of file + assert_eq!( + from_utf8(&vec![0x00, 0x01, 0x02].encode(b"bech32".to_vec()).unwrap()).unwrap(), + "bech321qpz4nc4pe" + ); + } + + #[test] + fn valid_address() { + let pairs: Vec<(&str, Vec)> = vec![ + ( + "BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4", + vec![ + 0x00, 0x14, 0x75, 0x1e, 0x76, 0xe8, 0x19, 0x91, 0x96, 0xd4, 0x54, 0x94, 0x1c, + 0x45, 0xd1, 0xb3, 0xa3, 0x23, 0xf1, 0x43, 0x3b, 0xd6, + ], + ), + ( + "bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx", + vec![ + 0x51, 0x28, 0x75, 0x1e, 0x76, 0xe8, 0x19, 0x91, 0x96, 0xd4, 0x54, 0x94, 0x1c, + 0x45, 0xd1, 0xb3, 0xa3, 0x23, 0xf1, 0x43, 0x3b, 0xd6, 0x75, 0x1e, 0x76, 0xe8, + 0x19, 0x91, 0x96, 0xd4, 0x54, 0x94, 0x1c, 0x45, 0xd1, 0xb3, 0xa3, 0x23, 0xf1, + 0x43, 0x3b, 0xd6, + ], + ), + ("BC1SW50QA3JX3S", vec![0x60, 0x02, 0x75, 0x1e]), + ( + "bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj", + vec![ + 0x52, 0x10, 0x75, 0x1e, 0x76, 0xe8, 0x19, 0x91, 0x96, 0xd4, 0x54, 0x94, 0x1c, + 0x45, 0xd1, 0xb3, 0xa3, 0x23, + ], + ), + ]; + for p in pairs { + let (address, scriptpubkey) = p; + + let hrp = b"bc".to_vec(); + + let spk_result = WitnessProgram::from_scriptpubkey(&scriptpubkey); + assert!(spk_result.is_ok()); + let prog = spk_result.unwrap(); + + let enc_result = prog.to_address(hrp); + assert!(enc_result.is_ok()); + + let enc_address = enc_result.unwrap(); + assert_eq!(address.to_lowercase(), from_utf8(&enc_address).unwrap().to_lowercase()); + } + } +} diff --git a/pallets/account-linker/src/lib.rs b/pallets/account-linker/src/lib.rs index 2f42171..3ec6907 100644 --- a/pallets/account-linker/src/lib.rs +++ b/pallets/account-linker/src/lib.rs @@ -1,6 +1,6 @@ //! # AccountLinker Pallet //! -//! The AccountLinker pallet provides functionality for linking a Litentry account to account at +//! The AccountLinker pallet provides functionality for linking a Litentry account to account at //! other networks. (currently support Ethereum (BSC), BTC and Polkadot ecosystem) //! //! ## Overview @@ -12,13 +12,13 @@ //! ## Interface //! //! ### Dispatchable Functions -//! +//! //! * `link_eth` - Link an Ethereum address to a Litentry account providing a proof signature //! from the private key of that Ethereum address. //! * `link_btc` - Link an BTC address to a Litentry account providing a proof signature //! from the private key of that BTC address. //! * `link_polkadot` - Initiate a link request to link a Litentry address to another Litentry address -//! * `accept_polkadot` - Accept a pending `link_polkadot` request to link a Litentry address +//! * `accept_polkadot` - Accept a pending `link_polkadot` request to link a Litentry address //! to another Litentry address. //! //! [`Call`]: ./enum.Call.html @@ -34,9 +34,9 @@ mod mock; #[cfg(test)] mod tests; +mod benchmarking; mod btc; mod util_eth; -mod benchmarking; pub mod weights; type EthAddress = [u8; 20]; @@ -46,14 +46,12 @@ type Signature = [u8; 65]; #[frame_support::pallet] pub mod pallet { use crate::*; - use frame_system::pallet_prelude::*; + use btc::{base58::ToBase58, witness::WitnessProgram}; use codec::Encode; - use sp_std::prelude::*; + use frame_support::{dispatch::DispatchResultWithPostInfo, pallet_prelude::*}; + use frame_system::{ensure_signed, pallet_prelude::*}; use sp_io::crypto::secp256k1_ecdsa_recover_compressed; - use frame_support::{dispatch::DispatchResultWithPostInfo, pallet_prelude::*,}; - use frame_system::{ensure_signed}; - use btc::base58::ToBase58; - use btc::witness::WitnessProgram; + use sp_std::prelude::*; use weights::WeightInfo; pub const EXPIRING_BLOCK_NUMBER_MAX: u32 = 10 * 60 * 24 * 30; // 30 days for 6s per block pub const MAX_ETH_LINKS: usize = 3; @@ -110,37 +108,40 @@ pub mod pallet { #[pallet::storage] #[pallet::getter(fn eth_addresses)] - pub(super) type EthereumLink = StorageMap<_, Blake2_128Concat, T::AccountId, Vec, ValueQuery>; + pub(super) type EthereumLink = + StorageMap<_, Blake2_128Concat, T::AccountId, Vec, ValueQuery>; #[pallet::storage] #[pallet::getter(fn btc_addresses)] - pub(super) type BitcoinLink = StorageMap<_, Blake2_128Concat, T::AccountId, Vec>, ValueQuery>; + pub(super) type BitcoinLink = + StorageMap<_, Blake2_128Concat, T::AccountId, Vec>, ValueQuery>; #[pallet::storage] #[pallet::getter(fn polkadot_addresses)] - pub(super) type PolkadotLink = StorageMap<_, Blake2_128Concat, T::AccountId, Vec, ValueQuery>; + pub(super) type PolkadotLink = + StorageMap<_, Blake2_128Concat, T::AccountId, Vec, ValueQuery>; #[pallet::storage] #[pallet::getter(fn polkadot_pending)] - pub(super) type PolkadotPending = StorageMap<_, Blake2_128Concat, T::AccountId, (T::AccountId, u32), ValueQuery>; + pub(super) type PolkadotPending = + StorageMap<_, Blake2_128Concat, T::AccountId, (T::AccountId, u32), ValueQuery>; #[pallet::call] - impl Pallet { - + impl Pallet { /// Link an Ethereum address to a Litentry account providing a proof signature from the private key /// of that Ethereum address. /// - /// The runtime needs to ensure that a malicious index can be handled correctly. - /// Currently, when vec.len > MAX_ETH_LINKS, replacement will always happen at the final index. + /// The runtime needs to ensure that a malicious index can be handled correctly. + /// Currently, when vec.len > MAX_ETH_LINKS, replacement will always happen at the final index. /// Otherwise it will use the next new slot unless index is valid against a currently available slot. - /// + /// /// Parameters: /// - `account`: The Litentry address that is to be linked /// - `index`: The index of the linked Ethereum address that the user wants to replace with. /// - `addr_expected`: The intended Ethereum address to link to the origin's Litentry address /// - `expiring_block_number`: The block number after which this link request will expire /// - `sig`: The rsv-signature generated by the private key of the addr_expected - /// + /// /// Emits `EthAddressLinked` event when successful. #[pallet::weight(T::WeightInfo::link_eth())] pub fn link_eth( @@ -151,23 +152,26 @@ pub mod pallet { expiring_block_number: T::BlockNumber, sig: Signature, ) -> DispatchResultWithPostInfo { - let _ = ensure_signed(origin)?; let current_block_number = >::block_number(); ensure!(expiring_block_number > current_block_number, Error::::LinkRequestExpired); - ensure!((expiring_block_number - current_block_number) < T::BlockNumber::from(EXPIRING_BLOCK_NUMBER_MAX), - Error::::InvalidExpiringBlockNumber); + ensure!( + (expiring_block_number - current_block_number) < + T::BlockNumber::from(EXPIRING_BLOCK_NUMBER_MAX), + Error::::InvalidExpiringBlockNumber + ); let bytes = Self::generate_raw_message(&account, expiring_block_number); - let hash = util_eth::eth_data_hash(bytes).map_err(|_| Error::::UnexpectedEthMsgLength)?; + let hash = + util_eth::eth_data_hash(bytes).map_err(|_| Error::::UnexpectedEthMsgLength)?; let mut msg = [0u8; 32]; msg[..32].copy_from_slice(&hash[..32]); - let addr = util_eth::addr_from_sig(msg, sig) - .map_err(|_| Error::::EcdsaRecoverFailure)?; + let addr = + util_eth::addr_from_sig(msg, sig).map_err(|_| Error::::EcdsaRecoverFailure)?; ensure!(addr == addr_expected, Error::::UnexpectedAddress); EthereumLink::::mutate(&account, |addrs| { @@ -185,24 +189,23 @@ pub mod pallet { Self::deposit_event(Event::EthAddressLinked(account, addr.to_vec())); Ok(().into()) - } /// Link a BTC address to a Litentry account providing a proof signature from the private key /// of that BTC address. The BTC address may either be a legacy P2PK one (started with b'1') /// or a Segwit P2PK one (started with b'bc'). /// - /// The runtime needs to ensure that a malicious index can be handled correctly. - /// Currently, when vec.len > MAX_ETH_LINKS, replacement will always happen at the final index. + /// The runtime needs to ensure that a malicious index can be handled correctly. + /// Currently, when vec.len > MAX_ETH_LINKS, replacement will always happen at the final index. /// Otherwise it will use the next new slot unless index is valid against a currently available slot. - /// + /// /// Parameters: /// - `account`: The Litentry address that is to be linked /// - `index`: The index of the linked BTC address that the user wants to replace with. /// - `addr_expected`: The intended BTC address to link to the origin's Litentry address /// - `expiring_block_number`: The block number after which this link request will expire /// - `sig`: The rsv-signature generated by the private key of the addr_expected - /// + /// /// Emits `BtcAddressLinked` event when successful. #[pallet::weight(T::WeightInfo::link_btc())] pub fn link_btc( @@ -213,13 +216,15 @@ pub mod pallet { expiring_block_number: T::BlockNumber, sig: Signature, ) -> DispatchResultWithPostInfo { - let _ = ensure_signed(origin)?; let current_block_number = >::block_number(); ensure!(expiring_block_number > current_block_number, Error::::LinkRequestExpired); - ensure!((expiring_block_number - current_block_number) < T::BlockNumber::from(EXPIRING_BLOCK_NUMBER_MAX), - Error::::InvalidExpiringBlockNumber); + ensure!( + (expiring_block_number - current_block_number) < + T::BlockNumber::from(EXPIRING_BLOCK_NUMBER_MAX), + Error::::InvalidExpiringBlockNumber + ); // TODO: we may enlarge this 2 if addr_expected.len() < 2 { @@ -228,7 +233,8 @@ pub mod pallet { let addr_type = if addr_expected[0] == b'1' { BTCAddrType::Legacy - } else if addr_expected[0] == b'b' && addr_expected[1] == b'c' { // TODO: a better way? + } else if addr_expected[0] == b'b' && addr_expected[1] == b'c' { + // TODO: a better way? BTCAddrType::Segwit } else { Err(Error::::InvalidBTCAddress)? @@ -243,12 +249,10 @@ pub mod pallet { msg[..32].copy_from_slice(&hash[..32]); let pk = secp256k1_ecdsa_recover_compressed(&sig, &msg) - .map_err(|_| Error::::EcdsaRecoverFailure)?; + .map_err(|_| Error::::EcdsaRecoverFailure)?; let addr = match addr_type { - BTCAddrType::Legacy => { - btc::legacy::btc_addr_from_pk(&pk).to_base58() - }, + BTCAddrType::Legacy => btc::legacy::btc_addr_from_pk(&pk).to_base58(), // Native P2WPKH is a scriptPubKey of 22 bytes. // It starts with a OP_0, followed by a canonical push of the keyhash (i.e. 0x0014{20-byte keyhash}) // keyhash is RIPEMD160(SHA256) of a compressed public key @@ -259,9 +263,10 @@ pub mod pallet { pk[0] = 0; pk[1] = 20; pk[2..].copy_from_slice(&pk_hash); - let wp = WitnessProgram::from_scriptpubkey(&pk.to_vec()).map_err(|_| Error::::InvalidBTCAddress)?; + let wp = WitnessProgram::from_scriptpubkey(&pk.to_vec()) + .map_err(|_| Error::::InvalidBTCAddress)?; wp.to_address(b"bc".to_vec()).map_err(|_| Error::::InvalidBTCAddress)? - } + }, }; ensure!(addr == addr_expected, Error::::UnexpectedAddress); @@ -281,12 +286,11 @@ pub mod pallet { Self::deposit_event(Event::BtcAddressLinked(account, addr)); Ok(().into()) - } /// Initiate a link request to link a Litentry address (= any account in Polkadot ecosystem) /// to another Litentry address (= any account in Polkadot ecosystem). - /// + /// /// Parameters: /// - `account`: The Litentry address that is to be linked /// - `index`: The index of the linked Litentry address that the user wants to replace with. @@ -296,7 +300,6 @@ pub mod pallet { account: T::AccountId, index: u32, ) -> DispatchResultWithPostInfo { - let origin = ensure_signed(origin)?; // TODO: charge some fee @@ -304,26 +307,24 @@ pub mod pallet { >::insert(origin, (account, index)); Ok(().into()) - } /// Accept a pending `link_polkadot` request to link a Litentry address (= any account in Polkadot ecosystem) /// to another Litentry address (= any account in Polkadot ecosystem). /// - /// The runtime needs to ensure that a malicious index can be handled correctly. - /// Currently, when vec.len > MAX_ETH_LINKS, replacement will always happen at the final index. + /// The runtime needs to ensure that a malicious index can be handled correctly. + /// Currently, when vec.len > MAX_ETH_LINKS, replacement will always happen at the final index. /// Otherwise it will use the next new slot unless index is valid against a currently available slot. - /// + /// /// Parameters: /// - `account`: The Litentry address that is to be linked - /// + /// /// Emits `PolkadotAddressLinked` event when successful. #[pallet::weight(T::WeightInfo::accept_polkadot())] pub fn accept_polkadot( origin: OriginFor, account: T::AccountId, ) -> DispatchResultWithPostInfo { - let origin = ensure_signed(origin)?; let (target, index) = Self::polkadot_pending(&account); @@ -348,14 +349,17 @@ pub mod pallet { } } - impl Pallet { - /// Assemble the message that the user has signed + impl Pallet { + /// Assemble the message that the user has signed /// Format: "Link Litentry: " + Litentry account + expiring block number - fn generate_raw_message(account: &T::AccountId, expiring_block_number: T::BlockNumber) -> Vec { + fn generate_raw_message( + account: &T::AccountId, + expiring_block_number: T::BlockNumber, + ) -> Vec { let mut bytes = b"Link Litentry: ".encode(); let mut account_vec = account.encode(); let mut expiring_block_number_vec = expiring_block_number.encode(); - + bytes.append(&mut account_vec); bytes.append(&mut expiring_block_number_vec); bytes diff --git a/pallets/account-linker/src/mock.rs b/pallets/account-linker/src/mock.rs index 61d300a..c9f581b 100644 --- a/pallets/account-linker/src/mock.rs +++ b/pallets/account-linker/src/mock.rs @@ -1,14 +1,14 @@ +use crate as account_linker; use frame_support::{ parameter_types, traits::{OnFinalize, OnInitialize}, }; use frame_system as system; -use crate as account_linker; use sp_core::H256; use sp_runtime::{ + generic, traits::{BlakeTwo256, IdentityLookup}, AccountId32, - generic, }; pub use crate::MAX_ETH_LINKS; @@ -68,20 +68,17 @@ pub type AccountLinkerError = account_linker::Error; // Build genesis storage according to the mock runtime. pub fn new_test_ext() -> sp_io::TestExternalities { - system::GenesisConfig::default() - .build_storage::() - .unwrap() - .into() + system::GenesisConfig::default().build_storage::().unwrap().into() } pub fn run_to_block(n: u32) { - while System::block_number() < n { - AccountLinker::on_finalize(System::block_number()); - System::on_finalize(System::block_number()); - System::set_block_number(System::block_number() + 1); - System::on_initialize(System::block_number()); - AccountLinker::on_initialize(System::block_number()); - } + while System::block_number() < n { + AccountLinker::on_finalize(System::block_number()); + System::on_finalize(System::block_number()); + System::set_block_number(System::block_number() + 1); + System::on_initialize(System::block_number()); + AccountLinker::on_initialize(System::block_number()); + } } pub fn events() -> Vec { @@ -90,4 +87,4 @@ pub fn events() -> Vec { System::reset_events(); evt -} \ No newline at end of file +} diff --git a/pallets/account-linker/src/tests/btc.rs b/pallets/account-linker/src/tests/btc.rs index ea53902..1ec69c1 100644 --- a/pallets/account-linker/src/tests/btc.rs +++ b/pallets/account-linker/src/tests/btc.rs @@ -1,15 +1,15 @@ -use crate::{mock::*}; +use crate::mock::*; use codec::Encode; +use frame_support::{assert_noop, assert_ok}; use parity_crypto::Keccak256; -use frame_support::{assert_ok, assert_noop}; use sp_runtime::AccountId32; -use bitcoin::network::constants::Network; -use bitcoin::util::address::Address; -use bitcoin::util::key; -use bitcoin::secp256k1::{Secp256k1, Message as BTCMessage}; -use bitcoin::secp256k1::rand::thread_rng; +use bitcoin::{ + network::constants::Network, + secp256k1::{rand::thread_rng, Message as BTCMessage, Secp256k1}, + util::{address::Address, key}, +}; #[test] fn test_invalid_expiring_block_number_btc() { @@ -17,10 +17,7 @@ fn test_invalid_expiring_block_number_btc() { // Generate random key pair let s = Secp256k1::new(); let pair = s.generate_keypair(&mut thread_rng()); - let public_key = key::PublicKey { - compressed: true, - key: pair.1, - }; + let public_key = key::PublicKey { compressed: true, key: pair.1 }; // Generate pay-to-pubkey-hash address let address = Address::p2pkh(&public_key, Network::Bitcoin); @@ -50,26 +47,22 @@ fn test_invalid_expiring_block_number_btc() { 0, address.clone().to_string().as_bytes().to_vec(), block_number, - sig), + sig + ), AccountLinkerError::InvalidExpiringBlockNumber ); - }); } #[test] fn test_btc_link_p2pkh() { new_test_ext().execute_with(|| { - - run_to_block(1); + run_to_block(1); // Generate random key pair let s = Secp256k1::new(); let pair = s.generate_keypair(&mut thread_rng()); - let public_key = key::PublicKey { - compressed: true, - key: pair.1, - }; + let public_key = key::PublicKey { compressed: true, key: pair.1 }; // Generate pay-to-pubkey-hash address let address = Address::p2pkh(&public_key, Network::Bitcoin); @@ -92,7 +85,7 @@ fn test_btc_link_p2pkh() { sig[..64].copy_from_slice(&rs[..]); sig[64] = v.to_i32() as u8; - let addr_expected = address.clone().to_string().as_bytes().to_vec(); + let addr_expected = address.clone().to_string().as_bytes().to_vec(); assert_ok!(AccountLinker::link_btc( Origin::signed(account.clone()), @@ -103,33 +96,27 @@ fn test_btc_link_p2pkh() { sig )); - let addr_stored = String::from_utf8(AccountLinker::btc_addresses(&account)[0].clone()).unwrap(); + let addr_stored = + String::from_utf8(AccountLinker::btc_addresses(&account)[0].clone()).unwrap(); assert_eq!(addr_stored, address.to_string()); assert_eq!( events(), - [ - Event::AccountLinker(crate::Event::BtcAddressLinked(account.clone(), addr_expected)), - ] + [Event::AccountLinker(crate::Event::BtcAddressLinked(account.clone(), addr_expected)),] ); - }); } #[test] fn test_btc_link_p2wpkh() { new_test_ext().execute_with(|| { - - run_to_block(1); + run_to_block(1); // Generate random key pair let s = Secp256k1::new(); let pair = s.generate_keypair(&mut thread_rng()); - let public_key = key::PublicKey { - compressed: true, - key: pair.1, - }; + let public_key = key::PublicKey { compressed: true, key: pair.1 }; // Generate pay-to-pubkey-hash address let address = Address::p2wpkh(&public_key, Network::Bitcoin).unwrap(); @@ -153,7 +140,7 @@ fn test_btc_link_p2wpkh() { sig[..64].copy_from_slice(&rs[..]); sig[64] = v.to_i32() as u8; - let addr_expected = address.clone().to_string().as_bytes().to_vec(); + let addr_expected = address.clone().to_string().as_bytes().to_vec(); assert_ok!(AccountLinker::link_btc( Origin::signed(account.clone()), @@ -164,15 +151,14 @@ fn test_btc_link_p2wpkh() { sig )); - let addr_stored = String::from_utf8(AccountLinker::btc_addresses(&account)[0].clone()).unwrap(); + let addr_stored = + String::from_utf8(AccountLinker::btc_addresses(&account)[0].clone()).unwrap(); - assert_eq!(addr_stored, address.to_string()); + assert_eq!(addr_stored, address.to_string()); - assert_eq!( + assert_eq!( events(), - [ - Event::AccountLinker(crate::Event::BtcAddressLinked(account.clone(), addr_expected)), - ] - ); + [Event::AccountLinker(crate::Event::BtcAddressLinked(account.clone(), addr_expected)),] + ); }); -} \ No newline at end of file +} diff --git a/pallets/account-linker/src/tests/eth.rs b/pallets/account-linker/src/tests/eth.rs index 1196029..e59a79d 100644 --- a/pallets/account-linker/src/tests/eth.rs +++ b/pallets/account-linker/src/tests/eth.rs @@ -1,13 +1,14 @@ -use crate::{mock::*}; +use crate::mock::*; use codec::Encode; -use parity_crypto::Keccak256; -use parity_crypto::publickey::{Random, Generator, Message, sign, KeyPair}; -use frame_support::{assert_ok, assert_noop}; +use frame_support::{assert_noop, assert_ok}; +use parity_crypto::{ + publickey::{sign, Generator, KeyPair, Message, Random}, + Keccak256, +}; use sp_runtime::AccountId32; fn generate_msg(account: &AccountId32, block_number: u32) -> Message { - let mut bytes = b"\x19Ethereum Signed Message:\n51Link Litentry: ".encode(); let mut account_vec = account.encode(); let mut expiring_block_number_vec = block_number.encode(); @@ -25,11 +26,10 @@ fn generate_sig(key_pair: &KeyPair, msg: &Message) -> [u8; 65] { #[test] fn test_expired_block_number_eth() { new_test_ext().execute_with(|| { - let account: AccountId32 = AccountId32::from([0u8; 32]); let block_number: u32 = 0; - let mut gen = Random{}; + let mut gen = Random {}; let key_pair = gen.generate(); let msg = generate_msg(&account, block_number); @@ -42,7 +42,8 @@ fn test_expired_block_number_eth() { 0, key_pair.address().to_fixed_bytes(), block_number, - sig), + sig + ), AccountLinkerError::LinkRequestExpired ); }); @@ -51,11 +52,10 @@ fn test_expired_block_number_eth() { #[test] fn test_invalid_expiring_block_number_eth() { new_test_ext().execute_with(|| { - let account: AccountId32 = AccountId32::from([0u8; 32]); let block_number: u32 = crate::EXPIRING_BLOCK_NUMBER_MAX + 1; - let mut gen = Random{}; + let mut gen = Random {}; let key_pair = gen.generate(); let msg = generate_msg(&account, block_number); @@ -68,7 +68,8 @@ fn test_invalid_expiring_block_number_eth() { 0, key_pair.address().to_fixed_bytes(), block_number, - sig), + sig + ), AccountLinkerError::InvalidExpiringBlockNumber ); }); @@ -77,11 +78,10 @@ fn test_invalid_expiring_block_number_eth() { #[test] fn test_unexpected_address_eth() { new_test_ext().execute_with(|| { - let account: AccountId32 = AccountId32::from([72u8; 32]); let block_number: u32 = 99999; - let mut gen = Random{}; + let mut gen = Random {}; let key_pair = gen.generate(); let msg = generate_msg(&account, block_number); @@ -94,7 +94,8 @@ fn test_unexpected_address_eth() { 0, gen.generate().address().to_fixed_bytes(), block_number, - sig), + sig + ), AccountLinkerError::UnexpectedAddress ); }); @@ -103,17 +104,15 @@ fn test_unexpected_address_eth() { #[test] fn test_insert_eth_address() { new_test_ext().execute_with(|| { - - run_to_block(1); + run_to_block(1); let account: AccountId32 = AccountId32::from([5u8; 32]); let block_number: u32 = 99999; - let mut gen = Random{}; + let mut gen = Random {}; let mut expected_vec = Vec::new(); for i in 0..(MAX_ETH_LINKS) { - let key_pair = gen.generate(); let msg = generate_msg(&account, block_number + i as u32); @@ -128,14 +127,15 @@ fn test_insert_eth_address() { sig )); - assert_eq!(AccountLinker::eth_addresses(&account).len(), i+1); + assert_eq!(AccountLinker::eth_addresses(&account).len(), i + 1); expected_vec.push(key_pair.address().to_fixed_bytes()); assert_eq!( - events(), - [ - Event::AccountLinker(crate::Event::EthAddressLinked(account.clone(), key_pair.address().to_fixed_bytes().to_vec())), - ] - ); + events(), + [Event::AccountLinker(crate::Event::EthAddressLinked( + account.clone(), + key_pair.address().to_fixed_bytes().to_vec() + )),] + ); } assert_eq!(AccountLinker::eth_addresses(&account), expected_vec); }); @@ -144,11 +144,10 @@ fn test_insert_eth_address() { #[test] fn test_update_eth_address() { new_test_ext().execute_with(|| { - let account: AccountId32 = AccountId32::from([40u8; 32]); let block_number: u32 = 99999; - let mut gen = Random{}; + let mut gen = Random {}; for i in 0..(MAX_ETH_LINKS) { let key_pair = gen.generate(); let msg = generate_msg(&account, block_number + i as u32); @@ -166,7 +165,7 @@ fn test_update_eth_address() { let index: u32 = 2 as u32; // Retrieve previous addr - let addr_before_update = AccountLinker::eth_addresses(&account)[index as usize]; + let addr_before_update = AccountLinker::eth_addresses(&account)[index as usize]; // Update addr at slot `index` let key_pair = gen.generate(); let block_number = block_number + 9 as u32; @@ -182,24 +181,22 @@ fn test_update_eth_address() { sig )); - let updated_addr = AccountLinker::eth_addresses(&account)[index as usize]; + let updated_addr = AccountLinker::eth_addresses(&account)[index as usize]; assert_ne!(updated_addr, addr_before_update); assert_eq!(updated_addr, key_pair.address().to_fixed_bytes()); }); } - #[test] fn test_eth_address_pool_overflow() { new_test_ext().execute_with(|| { - let account: AccountId32 = AccountId32::from([113u8; 32]); let block_number: u32 = 99999; - let mut gen = Random{}; + let mut gen = Random {}; let mut expected_vec = Vec::new(); - for index in 0..(MAX_ETH_LINKS*2) { + for index in 0..(MAX_ETH_LINKS * 2) { let key_pair = gen.generate(); let msg = generate_msg(&account, block_number); @@ -217,7 +214,7 @@ fn test_eth_address_pool_overflow() { if index < MAX_ETH_LINKS { expected_vec.push(key_pair.address().to_fixed_bytes()); } else { - expected_vec[MAX_ETH_LINKS-1] = key_pair.address().to_fixed_bytes(); + expected_vec[MAX_ETH_LINKS - 1] = key_pair.address().to_fixed_bytes(); } } assert_eq!(AccountLinker::eth_addresses(&account).len(), MAX_ETH_LINKS); diff --git a/pallets/account-linker/src/tests/mod.rs b/pallets/account-linker/src/tests/mod.rs index 19b6ba1..a78d9ed 100644 --- a/pallets/account-linker/src/tests/mod.rs +++ b/pallets/account-linker/src/tests/mod.rs @@ -1,3 +1,3 @@ mod btc; mod eth; -mod polkadot; \ No newline at end of file +mod polkadot; diff --git a/pallets/account-linker/src/tests/polkadot.rs b/pallets/account-linker/src/tests/polkadot.rs index f39caac..088c047 100644 --- a/pallets/account-linker/src/tests/polkadot.rs +++ b/pallets/account-linker/src/tests/polkadot.rs @@ -1,36 +1,29 @@ -use crate::{mock::*}; +use crate::mock::*; -use frame_support::{assert_ok, assert_noop}; +use frame_support::{assert_noop, assert_ok}; use sp_runtime::AccountId32; #[test] fn test_insert_polkadot_address() { new_test_ext().execute_with(|| { + run_to_block(1); - run_to_block(1); + let origin: AccountId32 = AccountId32::from([0u8; 32]); + let target: AccountId32 = AccountId32::from([1u8; 32]); - let origin: AccountId32 = AccountId32::from([0u8; 32]); - let target: AccountId32 = AccountId32::from([1u8; 32]); + assert_ok!(AccountLinker::link_polkadot(Origin::signed(origin.clone()), target.clone(), 0)); - assert_ok!(AccountLinker::link_polkadot( - Origin::signed(origin.clone()), - target.clone(), - 0 - )); + assert_eq!(AccountLinker::polkadot_pending(&origin), (target.clone(), 0)); - assert_eq!(AccountLinker::polkadot_pending(&origin), (target.clone(), 0)); + assert_ok!(AccountLinker::accept_polkadot(Origin::signed(target.clone()), origin.clone())); - assert_ok!(AccountLinker::accept_polkadot( - Origin::signed(target.clone()), - origin.clone() - )); - - assert_eq!( - events(), - [ - Event::AccountLinker(crate::Event::PolkadotAddressLinked(origin.clone(), target.clone())), - ] - ); + assert_eq!( + events(), + [Event::AccountLinker(crate::Event::PolkadotAddressLinked( + origin.clone(), + target.clone() + )),] + ); assert_eq!(AccountLinker::polkadot_addresses(&origin), vec![target]); }); @@ -39,20 +32,19 @@ fn test_insert_polkadot_address() { #[test] fn test_no_polkadot_pending_address() { new_test_ext().execute_with(|| { + run_to_block(1); - run_to_block(1); - - let origin: AccountId32 = AccountId32::from([0u8; 32]); - let target: AccountId32 = AccountId32::from([1u8; 32]); + let origin: AccountId32 = AccountId32::from([0u8; 32]); + let target: AccountId32 = AccountId32::from([1u8; 32]); - assert_eq!(AccountLinker::polkadot_pending(&origin), (AccountId32::default(), u32::default())); + assert_eq!( + AccountLinker::polkadot_pending(&origin), + (AccountId32::default(), u32::default()) + ); assert_noop!( - AccountLinker::accept_polkadot( - Origin::signed(target.clone()), - origin.clone() - ), + AccountLinker::accept_polkadot(Origin::signed(target.clone()), origin.clone()), AccountLinkerError::WrongPendingRequest ); }); -} \ No newline at end of file +} diff --git a/pallets/account-linker/src/util_eth.rs b/pallets/account-linker/src/util_eth.rs index e7a8c88..a592906 100644 --- a/pallets/account-linker/src/util_eth.rs +++ b/pallets/account-linker/src/util_eth.rs @@ -16,8 +16,12 @@ pub fn addr_from_sig(msg: [u8; 32], sig: [u8; 65]) -> Result<[u8; 20], sp_io::Ec pub fn eth_data_hash(mut data: Vec) -> Result<[u8; 32], &'static str> { const MSG_LEN: usize = 51; if data.len() != MSG_LEN { - log::error!("Ethereum message has an unexpected length {} !!! Expected is {}.", data.len(), MSG_LEN); - return Err("Unexpected ethereum message length!"); + log::error!( + "Ethereum message has an unexpected length {} !!! Expected is {}.", + data.len(), + MSG_LEN + ); + return Err("Unexpected ethereum message length!") } let mut length_bytes = usize_to_u8_array(data.len())?; let mut eth_data = b"\x19Ethereum Signed Message:\n".encode(); @@ -39,7 +43,7 @@ fn usize_to_u8_array(length: usize) -> Result, &'static str> { let tens = length / 10; let ones = length % 10; - let mut vec_res: Vec = Vec::new(); + let mut vec_res: Vec = Vec::new(); if tens != 0 { vec_res.push(digits[tens]); } @@ -62,7 +66,6 @@ mod tests { #[test] fn correct_recover() { - let msg = decode("61626364656667").unwrap(); let msg = eth_data_hash_test_helper(msg); @@ -80,7 +83,6 @@ mod tests { #[test] fn wrong_msg() { - let msg = decode("626364656667").unwrap(); let msg = eth_data_hash_test_helper(msg); @@ -98,7 +100,6 @@ mod tests { #[test] fn sig_from_another_addr() { - let msg = decode("61626364656667").unwrap(); let msg = eth_data_hash_test_helper(msg); diff --git a/pallets/account-linker/src/weights.rs b/pallets/account-linker/src/weights.rs index a7728bf..4af3952 100644 --- a/pallets/account-linker/src/weights.rs +++ b/pallets/account-linker/src/weights.rs @@ -35,11 +35,13 @@ // --output=./pallets/account-linker/src/weights.rs // --template=./.maintain/frame-weight-template.hbs - #![allow(unused_parens)] #![allow(unused_imports)] -use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use frame_support::{ + traits::Get, + weights::{constants::RocksDbWeight, Weight}, +}; use sp_std::marker::PhantomData; /// Weight functions needed for pallet_account_linker. @@ -64,13 +66,12 @@ impl WeightInfo for SubstrateWeight { .saturating_add(T::DbWeight::get().writes(1 as Weight)) } fn link_polkadot() -> Weight { - (335_000_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) + (335_000_000 as Weight).saturating_add(T::DbWeight::get().reads(1 as Weight)) } fn accept_polkadot() -> Weight { (335_000_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + .saturating_add(T::DbWeight::get().reads(1 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) } } @@ -87,12 +88,11 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } fn link_polkadot() -> Weight { - (335_000_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) + (335_000_000 as Weight).saturating_add(RocksDbWeight::get().reads(1 as Weight)) } fn accept_polkadot() -> Weight { (335_000_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + .saturating_add(RocksDbWeight::get().reads(1 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } } diff --git a/pallets/nft/src/benchmarking.rs b/pallets/nft/src/benchmarking.rs index 0cdfc99..76633ed 100644 --- a/pallets/nft/src/benchmarking.rs +++ b/pallets/nft/src/benchmarking.rs @@ -1,12 +1,9 @@ #![cfg(feature = "runtime-benchmarks")] -use sp_std::prelude::*; -use sp_std::vec; +use crate::Pallet as NFT; use frame_benchmarking::{account, benchmarks, impl_benchmark_test_suite}; use frame_system::RawOrigin; use sp_runtime::traits::StaticLookup; -use crate::CREATION_FEE; -use crate::Pallet as NFT; pub use crate::*; @@ -17,8 +14,8 @@ benchmarks! { create_class { let alice: T::AccountId = account("alice", 0, SEED); ::Currency::make_free_balance_be(&alice, (CREATION_FEE + 10).into()); - }: _(RawOrigin::Signed(alice), - vec![1], + }: _(RawOrigin::Signed(alice), + vec![1], Properties(ClassProperty::Transferable | ClassProperty::Burnable), None, None, @@ -36,8 +33,8 @@ benchmarks! { ::Currency::make_free_balance_be(&alice, (CREATION_FEE + 10).into()); crate::Pallet::::create_class( - RawOrigin::Signed(alice.clone()).into(), - vec![1], + RawOrigin::Signed(alice.clone()).into(), + vec![1], Properties(ClassProperty::Transferable | ClassProperty::Burnable), None, None, @@ -75,8 +72,8 @@ benchmarks! { ::Currency::make_free_balance_be(&alice, (CREATION_FEE + 10).into()); crate::Pallet::::create_class( - RawOrigin::Signed(alice.clone()).into(), - vec![1], + RawOrigin::Signed(alice.clone()).into(), + vec![1], Properties(ClassProperty::Transferable | ClassProperty::Burnable), None, None, @@ -96,8 +93,8 @@ benchmarks! { ::Currency::make_free_balance_be(&alice, (3 * CREATION_FEE + 10).into()); crate::Pallet::::create_class( - RawOrigin::Signed(alice.clone()).into(), - vec![1], + RawOrigin::Signed(alice.clone()).into(), + vec![1], Properties(ClassProperty::Transferable | ClassProperty::Burnable), None, None, @@ -105,24 +102,24 @@ benchmarks! { )?; crate::Pallet::::create_class( - RawOrigin::Signed(alice.clone()).into(), - vec![1], + RawOrigin::Signed(alice.clone()).into(), + vec![1], Properties(ClassProperty::Transferable | ClassProperty::Burnable), None, None, ClassType::Simple(999999999) - )?; + )?; crate::Pallet::::mint(RawOrigin::Signed(alice.clone()).into(), bob_lookup.clone(), 0u32.into(), vec![1], total)?; crate::Pallet::::create_class( - RawOrigin::Signed(alice.clone()).into(), - vec![1], + RawOrigin::Signed(alice.clone()).into(), + vec![1], Properties(ClassProperty::Transferable | ClassProperty::Burnable), None, None, ClassType::Merge(0u32.into(), 1u32.into(), false) - )?; + )?; crate::Pallet::::mint(RawOrigin::Signed(alice).into(), bob_lookup, 1u32.into(), vec![1], total)?; @@ -138,8 +135,8 @@ benchmarks! { ::Currency::make_free_balance_be(&alice, (CREATION_FEE + 10).into()); crate::Pallet::::create_class( - RawOrigin::Signed(alice.clone()).into(), - vec![1], + RawOrigin::Signed(alice.clone()).into(), + vec![1], Properties(ClassProperty::Transferable | ClassProperty::Burnable), None, None, @@ -158,20 +155,16 @@ benchmarks! { ::Currency::make_free_balance_be(&alice, (CREATION_FEE + 10).into()); crate::Pallet::::create_class( - RawOrigin::Signed(alice.clone()).into(), - vec![1], + RawOrigin::Signed(alice.clone()).into(), + vec![1], Properties(ClassProperty::Transferable | ClassProperty::Burnable), None, None, ClassType::Simple(999999999) - )?; + )?; crate::Pallet::::mint(RawOrigin::Signed(alice).into(), bob_lookup, 0u32.into(), vec![1], 1)?; }: _(RawOrigin::Signed(bob), (0u32.into(), 0u32.into())) } -impl_benchmark_test_suite!( - NFT, - crate::mock::new_test_ext(), - crate::mock::Test, -); +impl_benchmark_test_suite!(NFT, crate::mock::new_test_ext(), crate::mock::Test,); diff --git a/pallets/nft/src/impl_nonfungibles.rs b/pallets/nft/src/impl_nonfungibles.rs index dd4725d..2f124f8 100644 --- a/pallets/nft/src/impl_nonfungibles.rs +++ b/pallets/nft/src/impl_nonfungibles.rs @@ -1,10 +1,7 @@ - //! Implementations for `nonfungibles` traits. use super::*; -use frame_support::{ - traits::tokens::nonfungibles::{Inspect, Transfer}, -}; +use frame_support::traits::tokens::nonfungibles::{Inspect, Transfer}; use sp_runtime::DispatchResult; impl Inspect<::AccountId> for Pallet { @@ -15,7 +12,7 @@ impl Inspect<::AccountId> for Pallet { class: &Self::ClassId, instance: &Self::InstanceId, ) -> Option<::AccountId> { - orml_nft::Pallet::::tokens(class, instance).map(|a| a.owner) + orml_nft::Pallet::::tokens(class, instance).map(|a| a.owner) } fn class_owner(class: &Self::ClassId) -> Option<::AccountId> { @@ -48,9 +45,9 @@ impl Inspect<::AccountId> for Pallet { fn class_attribute(class: &Self::ClassId, key: &[u8]) -> Option> { if key.is_empty() { // We make the empty key map to the instance metadata value. - orml_nft::Pallet::::classes(class).map(|a| a.metadata.into()) + orml_nft::Pallet::::classes(class).map(|a| a.metadata.into()) } else { - return None + return None } } @@ -58,10 +55,10 @@ impl Inspect<::AccountId> for Pallet { /// /// Default implementation is that all assets are transferable. fn can_transfer(class: &Self::ClassId, instance: &Self::InstanceId) -> bool { - match orml_nft::Pallet::::classes(class) { - Some(class) => class.data.properties.0.contains(ClassProperty::Transferable), - _ => false, - } + match orml_nft::Pallet::::classes(class) { + Some(class) => class.data.properties.0.contains(ClassProperty::Transferable), + _ => false, + } } } @@ -71,8 +68,10 @@ impl Transfer for Pallet { instance: &Self::InstanceId, destination: &T::AccountId, ) -> DispatchResult { - let from = orml_nft::Pallet::::tokens(class, instance).map(|a| a.owner).ok_or(Error::::TokenIdNotFound)?; + let from = orml_nft::Pallet::::tokens(class, instance) + .map(|a| a.owner) + .ok_or(Error::::TokenIdNotFound)?; Self::do_transfer(&from, &destination, (*class, *instance))?; - Ok(()) + Ok(()) } } diff --git a/pallets/nft/src/lib.rs b/pallets/nft/src/lib.rs index 5df6bfe..f10a478 100644 --- a/pallets/nft/src/lib.rs +++ b/pallets/nft/src/lib.rs @@ -15,26 +15,27 @@ //! ### Dispatchable Functions //! #### Class Issuance //! * `create_class` - Create an NFT class (think the whole CryptoKitties or Hashmask each as a class) -//! +//! //! #### Instance Generation //! * `mint` - Mint specified number of instance of `Simple(u32)` type //! * `claim` - Whitelisted user claim an instance of `Claim(HashByte32)`, with a Merkle proof whose root //! is the HashByte32 //! * `merge` - From two NFT instance, mint a new NFT instance of `Merge(ID, ID, bool)` type -//! +//! //! #### Daily User Actions //! * `transfer` - Transfer ownership of a transferable NFT //! * `burn` - Burn a burnable NFT -//! +//! //! [`Call`]: ./enum.Call.html //! [`Config`]: ./trait.Config.html #![cfg_attr(not(feature = "std"), no_std)] use enumflags2::BitFlags; -use frame_support::{pallet_prelude::*, transactional}; -use frame_support::traits::{ - Currency, Get, ExistenceRequirement::KeepAlive, +use frame_support::{ + pallet_prelude::*, + traits::{Currency, ExistenceRequirement::KeepAlive, Get}, + transactional, }; use frame_system::pallet_prelude::*; use orml_traits::NFT; @@ -47,10 +48,10 @@ use sp_std::vec::Vec; #[cfg(test)] mod mock; +pub mod benchmarking; #[cfg(test)] mod tests; pub mod weights; -pub mod benchmarking; mod impl_nonfungibles; pub mod merkle_proof; @@ -86,9 +87,7 @@ impl Encode for Properties { impl Decode for Properties { fn decode(input: &mut I) -> sp_std::result::Result { let field = u8::decode(input)?; - Ok(Self( - >::from_bits(field as u8).map_err(|_| "invalid value")?, - )) + Ok(Self(>::from_bits(field as u8).map_err(|_| "invalid value")?)) } } @@ -125,7 +124,7 @@ pub enum ClassType { Claim(HashByte32), /// A class that is merged from two class ID and ID /// if true, burn the two instances - Merge(ID, ID, bool), + Merge(ID, ID, bool), } pub type TokenIdOf = ::TokenId; @@ -146,7 +145,6 @@ pub mod pallet { TokenData = TokenData, > { - /// The the currency to pay NFT class creation fee. type Currency: Currency; @@ -211,7 +209,7 @@ pub mod pallet { /// Claimed NFT token. \[claimer, class_id\] ClaimedToken(T::AccountId, ClassIdOf), /// Merged NFT token. \[owner, class_id\] - MergedToken(T::AccountId, ClassIdOf), + MergedToken(T::AccountId, ClassIdOf), /// Transferred NFT token. \[from, to, class_id, token_id\] TransferredToken(T::AccountId, T::AccountId, ClassIdOf, TokenIdOf), /// Burned NFT token. \[owner, class_id, token_id\] @@ -227,7 +225,7 @@ pub mod pallet { #[pallet::storage] #[pallet::getter(fn claimed_list)] - /// Claimed index vec for `Claim(HashByte32)` type NFT class, + /// Claimed index vec for `Claim(HashByte32)` type NFT class, /// to guarantee each user claims once. /// maximal index of claiming user is 2^16 which is more than enough pub(super) type ClaimedList = @@ -243,14 +241,14 @@ pub mod pallet { /// 1. Each instance is directly issued by the corresponding third party: Simple(u32) /// 2. At issuance, a list of user is provided and only these users may claim: Claim(HashByte32) /// 3. Can be minted only when the user have 2 specific base non fungible assets: Merge(ID, ID, bool) - /// + /// /// Parameters: /// - `metadata`: CID identifier of the class's metadata /// - `properties`: Class property, include `Transferable` `Burnable` /// - `start_block`: From when the instances can be minted (None if no restriction) /// - `end_block`: Till when the instances can be minted (None if no restriction) /// - `class_type`: Type of this class (refer to `ClassType`) - /// + /// /// Emits `CreatedClass` event when successful. #[pallet::weight(::WeightInfo::create_class())] #[transactional] @@ -270,7 +268,7 @@ pub mod pallet { .map_err(|_| Error::::CreationFeeNotPaid)?; match class_type { - ClassType::Merge(id1, id2, burn) => { + ClassType::Merge(id1, id2, burn) => if !burn { ensure!( >::classes(id1).is_some(), @@ -296,20 +294,14 @@ pub mod pallet { data2.properties.0.contains(ClassProperty::Burnable), Error::::NonBurnable ); - } - } + }, ClassType::Claim(_) => { ClaimedList::::insert(next_id, Vec::::new()); - } - _ => {} + }, + _ => {}, } - let data = ClassData { - properties, - start_block, - end_block, - class_type, - }; + let data = ClassData { properties, start_block, end_block, class_type }; orml_nft::Pallet::::create_class(&who, metadata.to_vec(), data)?; Self::deposit_event(Event::CreatedClass(who, next_id)); @@ -317,13 +309,13 @@ pub mod pallet { } /// Mint `Simple(u32)` NFT instances from the class owner - /// + /// /// Parameters: /// - `to`: The receiver of the minted NFTs /// - `class_id`: Identifier of the NFT class to mint /// - `metadata`: CID identifier of the instance's metadata /// - `quantity`: number of NFT to mint - /// + /// /// Emits `MintedToken` event when successful #[pallet::weight(::WeightInfo::mint(*quantity))] #[transactional] @@ -340,10 +332,7 @@ pub mod pallet { let class_info = orml_nft::Pallet::::classes(class_id).ok_or(Error::::ClassIdNotFound)?; ensure!(who == class_info.owner, Error::::NoPermission); - ensure!( - Self::check_time(&class_info.data), - Error::::OutOfCampaignPeriod - ); + ensure!(Self::check_time(&class_info.data), Error::::OutOfCampaignPeriod); match class_info.data.class_type { ClassType::Simple(max_num) => { @@ -351,15 +340,12 @@ pub mod pallet { if TokenIdOf::::from(quantity) + issued > TokenIdOf::::from(max_num) { Err(Error::::QuantityOverflow)? } - } + }, _ => Err(Error::::WrongClassType)?, } // TODO: adjustible rarity - let data = TokenData { - used: false, - rarity: 0, - }; + let data = TokenData { used: false, rarity: 0 }; for _ in 0..quantity { orml_nft::Pallet::::mint(&to, class_id, metadata.clone(), data.clone())?; } @@ -371,12 +357,12 @@ pub mod pallet { /// Claim a `Claim(HashByte32)` by a whitelisted user, /// with a Merkle proof that proves the user's account /// is in the Merkle tree of the given root - /// + /// /// Parameters: /// - `index`: Index of user's Merkle proof /// - `class_id`: Identifier of the NFT class to mint /// - `proof`: Merkle proof - /// + /// /// Emits `ClaimedToken` event when successful #[pallet::weight(::WeightInfo::mint(1))] #[transactional] @@ -390,15 +376,9 @@ pub mod pallet { let class_info = orml_nft::Pallet::::classes(class_id).ok_or(Error::::ClassIdNotFound)?; - ensure!( - ClaimedList::::contains_key(class_id), - Error::::ClassClaimedListNotFound - ); + ensure!(ClaimedList::::contains_key(class_id), Error::::ClassClaimedListNotFound); - ensure!( - Self::check_time(&class_info.data), - Error::::OutOfCampaignPeriod - ); + ensure!(Self::check_time(&class_info.data), Error::::OutOfCampaignPeriod); match class_info.data.class_type { ClassType::Claim(merkle_root) => { @@ -423,16 +403,13 @@ pub mod pallet { merkle_proof::proof_verify(&computed_hash, &proof, &merkle_root), Error::::UserNotInClaimList ); - } + }, _ => Err(Error::::WrongClassType)?, } // TODO: adjustable rarity - let data = TokenData { - used: false, - rarity: 0, - }; + let data = TokenData { used: false, rarity: 0 }; // TODO: if metadata can change? let metadata = class_info.metadata; @@ -444,12 +421,12 @@ pub mod pallet { /// Merge from two NFT instances and generate a new NFT /// of type `Merge(ID, ID, bool)` - /// + /// /// Parameters: /// - `class_id`: Identifier of the NFT class to mint /// - `token1`: First NFT of the merge base /// - `token2`: Seconde NFT of the merge base - /// + /// /// Emits `MergedToken` event when successful #[pallet::weight(::WeightInfo::mint(1))] #[transactional] @@ -463,17 +440,14 @@ pub mod pallet { let merged_class_info = orml_nft::Pallet::::classes(class_id).ok_or(Error::::ClassIdNotFound)?; - ensure!( - Self::check_time(&merged_class_info.data), - Error::::OutOfCampaignPeriod - ); + ensure!(Self::check_time(&merged_class_info.data), Error::::OutOfCampaignPeriod); let mut burn = false; if let ClassType::Merge(id1, id2, b) = merged_class_info.data.class_type { ensure!( - ((id1 == token1.0) && (id2 == token2.0)) - || ((id1 == token2.0) && (id2 == token1.0)), + ((id1 == token1.0) && (id2 == token2.0)) || + ((id1 == token2.0) && (id2 == token1.0)), Error::::WrongMergeBase, ); burn = b; @@ -492,10 +466,7 @@ pub mod pallet { Self::do_burn(&who, token1)?; Self::do_burn(&who, token2)?; } else { - ensure!( - !token_info1.data.used && !token_info2.data.used, - Error::::TokenUsed - ); + ensure!(!token_info1.data.used && !token_info2.data.used, Error::::TokenUsed); token_info1.data.used = true; token_info2.data.used = true; orml_nft::Tokens::::insert(token1.0, token1.1, token_info1); @@ -504,10 +475,7 @@ pub mod pallet { // mint new token // TODO: adjustible rarity - let data = TokenData { - used: false, - rarity: 0, - }; + let data = TokenData { used: false, rarity: 0 }; // TODO: if metadata can change? let metadata = merged_class_info.metadata; @@ -523,7 +491,7 @@ pub mod pallet { /// Parameters: /// - `to`: Receiver of the token /// - `token`: NFT instance to transfer - /// + /// /// Emits `TransferredToken` event when successful #[pallet::weight(::WeightInfo::transfer())] #[transactional] @@ -542,7 +510,7 @@ pub mod pallet { /// /// Parameters: /// - `token`: NFT instance to burn - /// + /// /// Emits `BurnedToken` event when successful #[pallet::weight(::WeightInfo::burn())] #[transactional] @@ -576,12 +544,7 @@ impl Pallet { orml_nft::Pallet::::transfer(from, to, token)?; - Self::deposit_event(Event::TransferredToken( - from.clone(), - to.clone(), - token.0, - token.1, - )); + Self::deposit_event(Event::TransferredToken(from.clone(), to.clone(), token.0, token.1)); Ok(()) } @@ -591,10 +554,7 @@ impl Pallet { let class_info = orml_nft::Pallet::::classes(token.0).ok_or(Error::::ClassIdNotFound)?; let data = class_info.data; - ensure!( - data.properties.0.contains(ClassProperty::Burnable), - Error::::NonBurnable - ); + ensure!(data.properties.0.contains(ClassProperty::Burnable), Error::::NonBurnable); let token_info = orml_nft::Pallet::::tokens(token.0, token.1).ok_or(Error::::TokenIdNotFound)?; @@ -607,18 +567,18 @@ impl Pallet { } impl Pallet { - /// check if current block time is in the range of the time span given by the + /// check if current block time is in the range of the time span given by the /// token class info fn check_time(token_info: &ClassData, ClassIdOf>) -> bool { let current_block_number = >::block_number(); if let Some(start_block) = token_info.start_block { if start_block > current_block_number { - return false; + return false } } if let Some(end_block) = token_info.end_block { if end_block < current_block_number { - return false; + return false } } true diff --git a/pallets/nft/src/merkle_proof.rs b/pallets/nft/src/merkle_proof.rs index 73fbbc8..1e391fa 100644 --- a/pallets/nft/src/merkle_proof.rs +++ b/pallets/nft/src/merkle_proof.rs @@ -6,7 +6,11 @@ use sp_std::vec::Vec; /// Verify the given Merkle proof and Merkle root /// - Each pair of leaves and each pair of pre-images are assumed to be sorted. /// - With reference of https://docs.openzeppelin.com/contracts/4.x/api/utils#MerkleProof -pub fn proof_verify(computed_hash: &HashByte32, proof: &Vec, root: &HashByte32) -> bool { +pub fn proof_verify( + computed_hash: &HashByte32, + proof: &Vec, + root: &HashByte32, +) -> bool { let mut next_hash = computed_hash.clone(); for iter in proof { diff --git a/pallets/nft/src/mock.rs b/pallets/nft/src/mock.rs index ec3e3fb..0692c6e 100644 --- a/pallets/nft/src/mock.rs +++ b/pallets/nft/src/mock.rs @@ -1,15 +1,15 @@ use super::*; +use crate as nft; use frame_support::{ parameter_types, traits::{OnFinalize, OnInitialize}, }; use frame_system as system; -use crate as nft; use sp_core::H256; use sp_runtime::{ + generic, traits::{BlakeTwo256, IdentityLookup}, AccountId32, - generic, }; type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; @@ -88,7 +88,6 @@ impl nft::Config for Test { type WeightInfo = (); type ClassCreationFee = ClassCreationFee; type Pot = Pot; - } parameter_types! { @@ -109,19 +108,16 @@ pub type NftError = nft::Error; // Build genesis storage according to the mock runtime. pub fn new_test_ext() -> sp_io::TestExternalities { - system::GenesisConfig::default() - .build_storage::() - .unwrap() - .into() + system::GenesisConfig::default().build_storage::().unwrap().into() } pub fn run_to_block(n: u32) { while System::block_number() < n { - >::on_finalize(System::block_number()); - >::on_finalize(System::block_number()); + >::on_finalize(System::block_number()); + >::on_finalize(System::block_number()); System::set_block_number(System::block_number() + 1); - >::on_initialize(System::block_number()); - >::on_initialize(System::block_number()); + >::on_initialize(System::block_number()); + >::on_initialize(System::block_number()); } } @@ -131,4 +127,4 @@ pub fn events() -> Vec { System::reset_events(); evt -} \ No newline at end of file +} diff --git a/pallets/nft/src/tests.rs b/pallets/nft/src/tests.rs index 9f3beb3..d37f6dc 100644 --- a/pallets/nft/src/tests.rs +++ b/pallets/nft/src/tests.rs @@ -1,6 +1,5 @@ use super::*; -use crate::mock::Event; -use crate::mock::*; +use crate::mock::{Event, *}; use frame_support::{assert_noop, assert_ok}; use sp_runtime::AccountId32; @@ -85,12 +84,7 @@ fn test_issue_and_claim_eth() { ); // alice claims with alice's proof - assert_ok!(Nft::claim( - Origin::signed(alice_account.clone()), - 0, - 0, - alice_proof.clone(), - )); + assert_ok!(Nft::claim(Origin::signed(alice_account.clone()), 0, 0, alice_proof.clone(),)); // alice claims again assert_noop!( @@ -155,11 +149,6 @@ fn test_issue_and_merge_eth() { )); // claim with proof - assert_ok!(Nft::merge( - Origin::signed(other_account.clone()), - 2, - (0, 0), - (1, 0), - )); + assert_ok!(Nft::merge(Origin::signed(other_account.clone()), 2, (0, 0), (1, 0),)); }) } diff --git a/pallets/offchain-worker/src/benchmarking.rs b/pallets/offchain-worker/src/benchmarking.rs index 7d8eb5d..43fe401 100644 --- a/pallets/offchain-worker/src/benchmarking.rs +++ b/pallets/offchain-worker/src/benchmarking.rs @@ -2,25 +2,24 @@ use super::*; -use frame_benchmarking::{benchmarks, account}; +use frame_benchmarking::{account, benchmarks}; use frame_system::RawOrigin; use sp_std::prelude::*; -benchmarks!{ +benchmarks! { - asset_claim { - let caller = account("caller", 0, 0); - - }: asset_claim(RawOrigin::Signed(caller)) + asset_claim { + let caller = account("caller", 0, 0); - submit_balance { - let caller = account("caller", 0, 0); - let account_id = account("Alice", 0, 0); - >::insert(&account_id, Some(0_u32)); - let block_number = 1_u32; - let data_source = urls::DataSource::EthEtherScan; - let balance = 0_u128; - - }: submit_balance(RawOrigin::Signed(caller), account_id, block_number.into(), data_source.into(), balance) -} + }: asset_claim(RawOrigin::Signed(caller)) + + submit_balance { + let caller = account("caller", 0, 0); + let account_id = account("Alice", 0, 0); + >::insert(&account_id, Some(0_u32)); + let block_number = 1_u32; + let data_source = urls::DataSource::EthEtherScan; + let balance = 0_u128; + }: submit_balance(RawOrigin::Signed(caller), account_id, block_number.into(), data_source.into(), balance) +} diff --git a/pallets/offchain-worker/src/lib.rs b/pallets/offchain-worker/src/lib.rs index 02acc94..3874993 100644 --- a/pallets/offchain-worker/src/lib.rs +++ b/pallets/offchain-worker/src/lib.rs @@ -12,8 +12,8 @@ #![cfg_attr(not(feature = "std"), no_std)] // everything define in pallet mod must be public +use codec::{Codec, Decode, Encode}; pub use pallet::*; -use codec::{Codec, Encode, Decode}; use sp_core::crypto::KeyTypeId; pub mod urls; @@ -37,24 +37,30 @@ pub mod pallet { account: AccountId, data_source: urls::DataSource, } - + pub mod crypto { use super::KEY_TYPE; + use sp_core::sr25519::Signature as Sr25519Signature; use sp_runtime::{ app_crypto::{app_crypto, sr25519}, - traits::Verify, MultiSignature, MultiSigner, + traits::Verify, + MultiSignature, MultiSigner, }; - use sp_core::sr25519::Signature as Sr25519Signature; app_crypto!(sr25519, KEY_TYPE); - + pub struct TestAuthId; impl frame_system::offchain::AppCrypto for TestAuthId { type RuntimeAppPublic = Public; type GenericSignature = sp_core::sr25519::Signature; type GenericPublic = sp_core::sr25519::Public; } - - impl frame_system::offchain::AppCrypto<::Signer, Sr25519Signature> for TestAuthId { + + impl + frame_system::offchain::AppCrypto< + ::Signer, + Sr25519Signature, + > for TestAuthId + { type RuntimeAppPublic = Public; type GenericSignature = sp_core::sr25519::Signature; type GenericPublic = sp_core::sr25519::Public; @@ -62,28 +68,44 @@ pub mod pallet { } use crate::*; - use frame_system::pallet_prelude::*; - use core::{convert::TryInto,}; - use sp_std::{prelude::*, fmt::Debug, collections::btree_map::{BTreeMap, Entry,}}; + use core::convert::TryInto; + use frame_support::{dispatch::DispatchResultWithPostInfo, pallet_prelude::*}; use frame_system::{ - ensure_signed, - offchain::{CreateSignedTransaction, Signer, AppCrypto, SendSignedTransaction,}, + ensure_signed, + offchain::{AppCrypto, CreateSignedTransaction, SendSignedTransaction, Signer}, + pallet_prelude::*, + }; + use sp_std::{ + collections::btree_map::{BTreeMap, Entry}, + fmt::Debug, + prelude::*, + }; + + use frame_support::{ + dispatch, + traits::{Currency, Imbalance, OnUnbalanced}, + }; + use sp_runtime::{ + offchain::storage::StorageValueRef, + traits::{AtLeast32BitUnsigned, MaybeSerializeDeserialize, Member}, }; - use frame_support::{dispatch::DispatchResultWithPostInfo, pallet_prelude::*, - }; - - use frame_support::{dispatch, traits::{Currency, OnUnbalanced, Imbalance},}; - use sp_runtime::offchain::{storage::StorageValueRef,}; - use sp_runtime::traits::{AtLeast32BitUnsigned, Member, MaybeSerializeDeserialize,}; use weights::WeightInfo; - type PositiveImbalanceOf = - <::Currency as Currency<::AccountId>>::PositiveImbalance; + type PositiveImbalanceOf = <::Currency as Currency< + ::AccountId, + >>::PositiveImbalance; #[pallet::config] - pub trait Config: frame_system::Config + account_linker::Config + CreateSignedTransaction> { - type Balance: Parameter + Member + AtLeast32BitUnsigned + Codec + Default + Copy + - MaybeSerializeDeserialize; + pub trait Config: + frame_system::Config + account_linker::Config + CreateSignedTransaction> + { + type Balance: Parameter + + Member + + AtLeast32BitUnsigned + + Codec + + Default + + Copy + + MaybeSerializeDeserialize; type Event: From> + IsType<::Event>; type Call: From>; type AuthorityId: AppCrypto; @@ -112,7 +134,8 @@ pub mod pallet { log::info!("ocw on_finalize {:?}.", block_number); let query_session_length: usize = T::QuerySessionLength::get() as usize; - let index_in_session = TryInto::::try_into(block_number).map_or(query_session_length, |bn| bn % query_session_length); + let index_in_session = TryInto::::try_into(block_number) + .map_or(query_session_length, |bn| bn % query_session_length); let last_block_number = query_session_length - 1; // Clear claim at the first block of a session @@ -131,7 +154,8 @@ pub mod pallet { let query_session_length: usize = T::QuerySessionLength::get() as usize; - let index_in_session = TryInto::::try_into(block_number).map_or(query_session_length, |bn| bn % query_session_length); + let index_in_session = TryInto::::try_into(block_number) + .map_or(query_session_length, |bn| bn % query_session_length); // Start query at second block of a session if index_in_session == 1 { @@ -147,7 +171,7 @@ pub mod pallet { pub enum Event { BalanceGot(T::AccountId, T::BlockNumber, Option, Option), } - + // Errors inform users that something went wrong. #[pallet::error] pub enum Error { @@ -178,34 +202,45 @@ pub mod pallet { #[pallet::storage] #[pallet::getter(fn total_claims)] pub(super) type TotalClaims = StorageValue<_, u64>; - + #[pallet::storage] #[pallet::getter(fn query_account_set)] - pub(super) type ClaimAccountSet = StorageMap<_, Blake2_128Concat, T::AccountId, (), ValueQuery>; + pub(super) type ClaimAccountSet = + StorageMap<_, Blake2_128Concat, T::AccountId, (), ValueQuery>; #[pallet::storage] #[pallet::getter(fn claim_account_index)] - pub(super) type ClaimAccountIndex = StorageMap<_, Blake2_128Concat, T::AccountId, Option, ValueQuery>; - + pub(super) type ClaimAccountIndex = + StorageMap<_, Blake2_128Concat, T::AccountId, Option, ValueQuery>; + #[pallet::storage] #[pallet::getter(fn account_balance)] - pub(super) type AccountBalance = StorageMap<_, Blake2_128Concat, T::AccountId, (Option, Option), ValueQuery>; + pub(super) type AccountBalance = + StorageMap<_, Blake2_128Concat, T::AccountId, (Option, Option), ValueQuery>; /// Record account's btc and ethereum balance #[pallet::storage] #[pallet::getter(fn commit_account_balance)] - pub(super) type CommitAccountBalance = StorageDoubleMap<_, Blake2_128Concat, T::AccountId, Blake2_128Concat, QueryKey, Option, ValueQuery>; + pub(super) type CommitAccountBalance = StorageDoubleMap< + _, + Blake2_128Concat, + T::AccountId, + Blake2_128Concat, + QueryKey, + Option, + ValueQuery, + >; #[pallet::storage] #[pallet::getter(fn ocw_account_index)] - pub(super) type OcwAccountIndex = StorageMap<_, Blake2_128Concat, T::AccountId, Option, ValueQuery>; - + pub(super) type OcwAccountIndex = + StorageMap<_, Blake2_128Concat, T::AccountId, Option, ValueQuery>; #[pallet::call] - impl Pallet { + impl Pallet { /// Request the Litentry to query balances of linked Eth and BTC accounts. /// - /// This will alter `ClaimAccountSet` in storage. + /// This will alter `ClaimAccountSet` in storage. /// /// The dispatch origin for this call is `account`. /// @@ -218,13 +253,16 @@ pub mod pallet { /// - Killing: 35.11 µs /// - DB Weight: 1 Read, 1 Write /// # - /// + /// #[pallet::weight(::WeightInfo::asset_claim())] - pub fn asset_claim(origin: OriginFor,) -> DispatchResultWithPostInfo { + pub fn asset_claim(origin: OriginFor) -> DispatchResultWithPostInfo { let account = ensure_signed(origin)?; // If the same claim already in set - ensure!(!>::contains_key(&account), Error::::AccountAlreadyInClaimlist); + ensure!( + !>::contains_key(&account), + Error::::AccountAlreadyInClaimlist + ); >::insert(&account, ()); @@ -234,12 +272,12 @@ pub mod pallet { /// Offchain worker submit linked Eth and BTC balance via extrinsic. /// /// Extrinsic Arguments. - /// account: the target account offchain-worker query data for. + /// account: the target account offchain-worker query data for. /// block_number: the block number for offchain-worker trigger the query. /// data_source: the enum for different data source defined in urls.rs. /// balance: the balance returned from data source. - /// - /// This will alter `CommitAccountBalance` in storage. + /// + /// This will alter `CommitAccountBalance` in storage. /// /// The dispatch origin for this call is `account`. /// @@ -252,22 +290,39 @@ pub mod pallet { /// - Killing: 35.11 µs /// - DB Weight: 1 Read, 1 Write /// # - /// + /// #[pallet::weight(::WeightInfo::submit_balance())] - pub fn submit_balance(origin: OriginFor, account: T::AccountId, block_number: T::BlockNumber, data_source: urls::DataSource, balance: u128)-> DispatchResultWithPostInfo { + pub fn submit_balance( + origin: OriginFor, + account: T::AccountId, + block_number: T::BlockNumber, + data_source: urls::DataSource, + balance: u128, + ) -> DispatchResultWithPostInfo { let sender = ensure_signed(origin)?; // Check data source Self::valid_data_source(data_source)?; // Check block number - Self::valid_commit_block_number(block_number, >::block_number())?; + Self::valid_commit_block_number( + block_number, + >::block_number(), + )?; // Check the commit slot - Self::valid_commit_slot(account.clone(), Self::get_ocw_index(Some(&account)), data_source)?; + Self::valid_commit_slot( + account.clone(), + Self::get_ocw_index(Some(&account)), + data_source, + )?; // put query result on chain - CommitAccountBalance::::insert(&sender, &QueryKey{account, data_source}, Some(balance)); + CommitAccountBalance::::insert( + &sender, + &QueryKey { account, data_source }, + Some(balance), + ); Ok(().into()) } @@ -297,47 +352,61 @@ pub mod pallet { let account: T::AccountId = item.0; match item.1 { Some(account_index) => { - let mut source_index = 0; for source in &urls::DATA_SOURCE_LIST { - let task_index = urls::TOTAL_DATA_SOURCE_NUMBER * account_index + source_index; + let task_index = + urls::TOTAL_DATA_SOURCE_NUMBER * account_index + source_index; if task_index % ocw_length == ocw_account_index { match source { - urls::DataSource::EthEtherScan => { + urls::DataSource::EthEtherScan => match Self::get_balance_from_etherscan(&account, info) { - Some(balance) => Self::offchain_signed_tx(account.clone(), block_number, urls::DataSource::EthEtherScan, balance), - None => () - } - }, - urls::DataSource::EthInfura => { + Some(balance) => Self::offchain_signed_tx( + account.clone(), + block_number, + urls::DataSource::EthEtherScan, + balance, + ), + None => (), + }, + urls::DataSource::EthInfura => match Self::get_balance_from_infura(&account, info) { - Some(balance) => Self::offchain_signed_tx(account.clone(), block_number, urls::DataSource::EthInfura, balance), - None => () - } - }, - urls::DataSource::BtcBlockChain => { - match Self::get_balance_from_blockchain_info(&account, info) { - Some(balance) => Self::offchain_signed_tx(account.clone(), block_number, urls::DataSource::BtcBlockChain, balance), - None => () - } - }, + Some(balance) => Self::offchain_signed_tx( + account.clone(), + block_number, + urls::DataSource::EthInfura, + balance, + ), + None => (), + }, + urls::DataSource::BtcBlockChain => + match Self::get_balance_from_blockchain_info(&account, info) + { + Some(balance) => Self::offchain_signed_tx( + account.clone(), + block_number, + urls::DataSource::BtcBlockChain, + balance, + ), + None => (), + }, _ => (), }; } source_index = source_index + 1; - } - }, - None => (), + } + }, + None => (), + } } } - } // Clear claim accounts in last session fn clear_claim() { // Remove all account index in last session >::remove_all(None); - let accounts: Vec = >::iter().map(|(k, _)| k).collect(); + let accounts: Vec = + >::iter().map(|(k, _)| k).collect(); // Set account index for (index, account) in accounts.iter().enumerate() { @@ -345,7 +414,7 @@ pub mod pallet { } // Remove all claimed accounts - >::remove_all(None); + >::remove_all(None); } // Start new round of offchain worker @@ -381,28 +450,32 @@ pub mod pallet { /// use vector's index as new off-chain worker index, make it variable and random /// 7. finally, remove all intermediate on-chain storage, make it empty for next round query fn aggregate_query_result() { - let mut result_map: BTreeMap<(T::AccountId, urls::BlockChainType, u128), u32> = BTreeMap::new(); - let mut result_key: BTreeMap<(T::AccountId, urls::BlockChainType), Vec> = BTreeMap::new(); + let mut result_map: BTreeMap<(T::AccountId, urls::BlockChainType, u128), u32> = + BTreeMap::new(); + let mut result_key: BTreeMap<(T::AccountId, urls::BlockChainType), Vec> = + BTreeMap::new(); // Statistics for result for result in >::iter() { - let account: T::AccountId = result.1.account; let data_source: urls::DataSource = result.1.data_source; - let block_type: urls::BlockChainType = urls::data_source_to_block_chain_type(data_source); + let block_type: urls::BlockChainType = + urls::data_source_to_block_chain_type(data_source); match result.2 { Some(balance) => { let map_key = (account.clone(), block_type, balance); result_map.entry(map_key.clone()).or_insert(1_32); - + match result_map.entry(map_key.clone()) { Entry::Occupied(mut entry) => { *entry.get_mut() = entry.get() + 1; }, - Entry::Vacant(v) => {v.insert(1_u32);} , + Entry::Vacant(v) => { + v.insert(1_u32); + }, }; - + let key_key = (account, block_type); match result_key.get(&key_key) { Some(balance_vec) => { @@ -410,7 +483,7 @@ pub mod pallet { for item in balance_vec.iter() { if *item == balance { found = true; - break; + break } } if !found { @@ -419,7 +492,9 @@ pub mod pallet { result_key.insert(key_key, new_balance_vec); } }, - None => {result_key.insert(key_key, vec![balance]);}, + None => { + result_key.insert(key_key, vec![balance]); + }, }; }, None => (), @@ -427,10 +502,11 @@ pub mod pallet { } // Store on chain, record_map will used to reward ocw. - let mut record_map: BTreeMap<(T::AccountId, urls::BlockChainType), u128> = BTreeMap::new(); + let mut record_map: BTreeMap<(T::AccountId, urls::BlockChainType), u128> = + BTreeMap::new(); for result in result_key.iter() { - let account: T::AccountId = result.0.0.clone(); - let block_type: urls::BlockChainType = result.0.1; + let account: T::AccountId = result.0 .0.clone(); + let block_type: urls::BlockChainType = result.0 .1; let mut most_value = 0_u128; let mut most_times = 0_u32; @@ -438,12 +514,11 @@ pub mod pallet { for balance in result.1 { let key = (account.clone(), block_type, *balance); match result_map.get(&key) { - Some(frequence) => { + Some(frequence) => if *frequence > most_times { most_times = *frequence; most_value = *balance; - } - }, + }, None => {}, } } @@ -451,14 +526,10 @@ pub mod pallet { // Update balance on chain if block_type == urls::BlockChainType::ETH { - >::mutate(account, - |value| value.1 = Some(most_value) - ); + >::mutate(account, |value| value.1 = Some(most_value)); Self::increment_total_claims(); } else if block_type == urls::BlockChainType::BTC { - >::mutate(account, - |value| value.0 = Some(most_value) - ); + >::mutate(account, |value| value.0 = Some(most_value)); Self::increment_total_claims(); } } @@ -474,7 +545,8 @@ pub mod pallet { let ocw_account: T::AccountId = result.0; let query_account: T::AccountId = result.1.account; let data_source: urls::DataSource = result.1.data_source; - let block_type: urls::BlockChainType = urls::data_source_to_block_chain_type(data_source); + let block_type: urls::BlockChainType = + urls::data_source_to_block_chain_type(data_source); match result.2 { Some(committed_balance) => { @@ -483,7 +555,11 @@ pub mod pallet { Some(balance) => { // balance matched if *balance == committed_balance { - let r = T::Currency::deposit_into_existing(&ocw_account, T::OcwQueryReward::get()).ok(); + let r = T::Currency::deposit_into_existing( + &ocw_account, + T::OcwQueryReward::get(), + ) + .ok(); total_imbalance.maybe_subsume(r); } }, @@ -499,7 +575,7 @@ pub mod pallet { } }, None => (), - } + } } T::Reward::on_unbalanced(total_imbalance); @@ -520,7 +596,11 @@ pub mod pallet { /// Each off-chain worker has index in the off-chain worker queue /// Each query also has index in the query task queue /// The method used to check if the both index are matched or not - fn valid_commit_slot(account: T::AccountId, ocw_index: u32, data_source: urls::DataSource) -> dispatch::DispatchResult { + fn valid_commit_slot( + account: T::AccountId, + ocw_index: u32, + data_source: urls::DataSource, + ) -> dispatch::DispatchResult { // account claimed the asset query let ocw_account_index = Self::get_account_index(account)?; @@ -544,10 +624,12 @@ pub mod pallet { let query_task_redudancy: u32 = T::QueryTaskRedundancy::get(); // task number per round - let total_task_per_round = urls::TOTAL_DATA_SOURCE_NUMBER * Self::get_claim_account_length(); + let total_task_per_round = + urls::TOTAL_DATA_SOURCE_NUMBER * Self::get_claim_account_length(); // task index in the first round - let task_base_index = data_source_index + ocw_account_index * urls::TOTAL_DATA_SOURCE_NUMBER; + let task_base_index = + data_source_index + ocw_account_index * urls::TOTAL_DATA_SOURCE_NUMBER; let mut round: u32 = 0; while round < query_task_redudancy { @@ -584,23 +666,31 @@ pub mod pallet { } // Check the block number - fn valid_commit_block_number(commit_block_number: T::BlockNumber, current_block_number: T::BlockNumber) -> dispatch::DispatchResult { + fn valid_commit_block_number( + commit_block_number: T::BlockNumber, + current_block_number: T::BlockNumber, + ) -> dispatch::DispatchResult { let zero_block: u32 = 0; - let commit_block_number: u32 = TryInto::::try_into(commit_block_number).map_or(zero_block, |block_number| block_number as u32); - let current_block_number: u32 = TryInto::::try_into(current_block_number).map_or(zero_block, |block_number| block_number as u32); + let commit_block_number: u32 = TryInto::::try_into(commit_block_number) + .map_or(zero_block, |block_number| block_number as u32); + let current_block_number: u32 = TryInto::::try_into(current_block_number) + .map_or(zero_block, |block_number| block_number as u32); // Basic check for both block number if commit_block_number == 0 || current_block_number == 0 { - return Err(>::InvalidCommitBlockNumber.into()); + return Err(>::InvalidCommitBlockNumber.into()) } // Compute the scope of session - let sesseion_start_block = commit_block_number - commit_block_number % T::QuerySessionLength::get() ; + let sesseion_start_block = + commit_block_number - commit_block_number % T::QuerySessionLength::get(); let sesseion_end_block = sesseion_start_block + T::QuerySessionLength::get(); // If commit block number out of the scope of session. - if current_block_number >= sesseion_end_block || current_block_number <= sesseion_start_block { - return Err(>::InvalidCommitBlockNumber.into()); + if current_block_number >= sesseion_end_block || + current_block_number <= sesseion_start_block + { + return Err(>::InvalidCommitBlockNumber.into()) } Ok(()) @@ -619,15 +709,18 @@ pub mod pallet { // Get the length of accounts fn get_ocw_length() -> u32 { - >::iter().collect::>().len() as u32 + >::iter().collect::>().len() as u32 } // Get the length of accounts fn get_claim_account_length() -> u32 { - >::iter().collect::>().len() as u32 + >::iter().collect::>().len() as u32 } - fn get_balance_from_etherscan(account: &T::AccountId, info: &urls::TokenInfo) -> Option { + fn get_balance_from_etherscan( + account: &T::AccountId, + info: &urls::TokenInfo, + ) -> Option { if info.etherscan.len() == 0 { None } else { @@ -644,7 +737,9 @@ pub mod pallet { Self::fetch_balances( >::eth_addresses(account), urls::HttpRequest::GET(get), - &urls::parse_etherscan_balances).ok() + &urls::parse_etherscan_balances, + ) + .ok() }, Err(_) => None, } @@ -652,7 +747,6 @@ pub mod pallet { } fn get_balance_from_infura(account: &T::AccountId, info: &urls::TokenInfo) -> Option { - if info.infura.len() == 0 { None } else { @@ -669,7 +763,9 @@ pub mod pallet { Self::fetch_balances( >::eth_addresses(account), urls::HttpRequest::POST(post), - &urls::parse_blockchain_info_balances).ok() + &urls::parse_blockchain_info_balances, + ) + .ok() }, Err(_) => None, } @@ -677,22 +773,28 @@ pub mod pallet { } // TODO account not input request parameter - fn get_balance_from_blockchain_info(_account: &T::AccountId, info: &urls::TokenInfo) -> Option { + fn get_balance_from_blockchain_info( + _account: &T::AccountId, + info: &urls::TokenInfo, + ) -> Option { if info.blockchain.len() == 0 { None } else { match core::str::from_utf8(&info.blockchain) { Ok(token) => { let get = urls::HttpGet { - blockchain: urls::BlockChainType::BTC, - prefix: "https://blockchain.info/balance?active=", - delimiter: "%7C", - postfix: "", - api_token: token, + blockchain: urls::BlockChainType::BTC, + prefix: "https://blockchain.info/balance?active=", + delimiter: "%7C", + postfix: "", + api_token: token, }; - Self::fetch_balances(Vec::new(), + Self::fetch_balances( + Vec::new(), urls::HttpRequest::GET(get), - &urls::parse_blockchain_info_balances).ok() + &urls::parse_blockchain_info_balances, + ) + .ok() }, Err(_) => None, } @@ -700,23 +802,36 @@ pub mod pallet { } // Sign the query result - fn offchain_signed_tx(account: T::AccountId, block_number: T::BlockNumber, data_source: urls::DataSource, balance: u128) { - log::info!("ocw sign tx: account {:?}, block number {:?}, data_source {:?}, balance {:?}", - account.clone(), block_number, data_source, balance); + fn offchain_signed_tx( + account: T::AccountId, + block_number: T::BlockNumber, + data_source: urls::DataSource, + balance: u128, + ) { + log::info!( + "ocw sign tx: account {:?}, block number {:?}, data_source {:?}, balance {:?}", + account.clone(), + block_number, + data_source, + balance + ); // Get signer from ocw let signer = Signer::::any_account(); let result = signer.send_signed_transaction(|_acct| // This is the on-chain function - Call::submit_balance(account.clone(), block_number, data_source, balance) - ); + Call::submit_balance(account.clone(), block_number, data_source, balance)); // Display error if the signed tx fails. if let Some((acc, res)) = result { if res.is_err() { log::error!("failure: offchain_signed_tx: tx sent: {:?}", acc.id); } else { - log::info!("successful: offchain_signed_tx: tx sent: {:?} index is {:?}", acc.id, acc.index); + log::info!( + "successful: offchain_signed_tx: tx sent: {:?} index is {:?}", + acc.id, + acc.index + ); } // Record the account in local storage then we can know my index @@ -728,8 +843,11 @@ pub mod pallet { } // Generic function to fetch balance for specific link type - pub fn fetch_balances(wallet_accounts: Vec<[u8; 20]>, request: urls::HttpRequest, - parser: &dyn Fn(&str) -> Option>) -> Result> { + pub fn fetch_balances( + wallet_accounts: Vec<[u8; 20]>, + request: urls::HttpRequest, + parser: &dyn Fn(&str) -> Option>, + ) -> Result> { // Return if no account linked if wallet_accounts.len() == 0 { return Ok(0_u128) @@ -743,7 +861,7 @@ pub mod pallet { for (i, each_account) in wallet_accounts.iter().enumerate() { // Append delimiter if there are more than one accounts in the account_vec - if i >=1 { + if i >= 1 { link.extend(get_req.delimiter.as_bytes()); }; @@ -768,7 +886,7 @@ pub mod pallet { for (i, each_account) in wallet_accounts.iter().enumerate() { // Append delimiter if there are more than one accounts in the account_vec - if i >=1 { + if i >= 1 { body.extend(post_req.delimiter.as_bytes()); }; @@ -777,11 +895,13 @@ pub mod pallet { body.extend(post_req.postfix.as_bytes()); // Fetch json response via http post - urls::fetch_json_http_post(&link[..], &body[..]).map_err(|_| Error::::InvalidNumber)? + urls::fetch_json_http_post(&link[..], &body[..]) + .map_err(|_| Error::::InvalidNumber)? }, }; - let response = sp_std::str::from_utf8(&result).map_err(|_| Error::::InvalidNumber)?; + let response = + sp_std::str::from_utf8(&result).map_err(|_| Error::::InvalidNumber)?; let balances = parser(response); match balances { diff --git a/pallets/offchain-worker/src/tests.rs b/pallets/offchain-worker/src/tests.rs index fc793d6..2bacbef 100644 --- a/pallets/offchain-worker/src/tests.rs +++ b/pallets/offchain-worker/src/tests.rs @@ -15,16 +15,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::*; use crate as offchain_worker; +use crate::*; use frame_support::parameter_types; -use sp_core::{ H256, sr25519::Signature,}; +use sp_core::{sr25519::Signature, H256}; use sp_runtime::{ testing::{Header, TestXt}, - traits::{ - BlakeTwo256, IdentityLookup, Extrinsic as ExtrinsicT, - IdentifyAccount, Verify, - }, + traits::{BlakeTwo256, Extrinsic as ExtrinsicT, IdentifyAccount, IdentityLookup, Verify}, }; type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; @@ -101,14 +98,16 @@ impl frame_system::offchain::SigningTypes for Test { type Signature = Signature; } -impl frame_system::offchain::SendTransactionTypes for Test where +impl frame_system::offchain::SendTransactionTypes for Test +where Call: From, { type OverarchingCall = Call; type Extrinsic = Extrinsic; } -impl frame_system::offchain::CreateSignedTransaction for Test where +impl frame_system::offchain::CreateSignedTransaction for Test +where Call: From, { fn create_transaction>( @@ -145,10 +144,11 @@ impl Config for Test { type WeightInfo = (); } - #[test] fn test_chars_to_u128() { - let correct_balance = vec!['5', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0']; + let correct_balance = vec![ + '5', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', + ]; assert_eq!(Ok(500000000000000000_u128), utils::chars_to_u128(&correct_balance)); let correct_balance = vec!['a', '2']; @@ -162,7 +162,6 @@ fn test_chars_to_u128() { assert_eq!(Ok(0_u128), utils::chars_to_u128(&correct_balance)); } - #[test] fn test_parse_etherscan_balances() { let double_balances = r#" @@ -245,12 +244,11 @@ fn test_parse_infura_balances_2() { "#; let token_info: Vec = serde_json::from_str(double_balances).unwrap(); assert_eq!(token_info[0].id, 1); - } // fetch_balances only executed in offchain worker context, need investigate how to call it in test // #[test] -// fn test_fetch_balances() { +// fn test_fetch_balances() { // let get = urls::HttpGet { // blockchain: urls::BlockChainType::ETH, // prefix: "https://api-ropsten.etherscan.io/api?module=account&action=balancemulti&address=0x", @@ -262,7 +260,7 @@ fn test_parse_infura_balances_2() { // let test_account = "4d88dc5D528A33E4b8bE579e9476715F60060582".as_bytes(); // let mut test_account_byte_array = [0u8; 20]; // test_account_byte_array.copy_from_slice(&test_account[0..20]); - + // let mut accounts: Vec<[u8; 20]> = Vec::new(); // accounts.push(test_account_byte_array); @@ -272,4 +270,4 @@ fn test_parse_infura_balances_2() { // Err(_) => panic!("Error occurs in test_fetch_balance!!"), // }; // }); -// } \ No newline at end of file +// } diff --git a/pallets/offchain-worker/src/urls.rs b/pallets/offchain-worker/src/urls.rs index 138b955..48f5bb5 100644 --- a/pallets/offchain-worker/src/urls.rs +++ b/pallets/offchain-worker/src/urls.rs @@ -1,95 +1,96 @@ -use sp_std::{prelude::*}; -use core::{fmt}; -use sp_runtime::offchain::{http, storage::StorageValueRef,}; -use codec::{Encode, Decode}; -use alt_serde::{Deserialize, Deserializer}; use super::utils; +use alt_serde::{Deserialize, Deserializer}; +use codec::{Decode, Encode}; +use core::fmt; +use sp_runtime::offchain::{http, storage::StorageValueRef}; +use sp_std::prelude::*; /// Asset type #[derive(Encode, Decode, Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] pub enum BlockChainType { - /// invalid - Invalid, - /// eth token - ETH, - /// bitcoin - BTC, + /// invalid + Invalid, + /// eth token + ETH, + /// bitcoin + BTC, } impl Default for BlockChainType { - fn default() -> Self {BlockChainType::Invalid} + fn default() -> Self { + BlockChainType::Invalid + } } /// Eth source enum #[derive(Encode, Decode, Copy, Clone, Debug, PartialEq)] pub enum DataSource { - /// invalid - Invalid, - /// etherscan - EthEtherScan, - /// infura - EthInfura, - /// blockchain - BtcBlockChain, + /// invalid + Invalid, + /// etherscan + EthEtherScan, + /// infura + EthInfura, + /// blockchain + BtcBlockChain, } pub const TOTAL_DATA_SOURCE_NUMBER: u32 = 3; -pub const DATA_SOURCE_LIST: [DataSource; TOTAL_DATA_SOURCE_NUMBER as usize] = [ - DataSource::EthEtherScan, - DataSource::EthInfura, - DataSource::BtcBlockChain, - ]; +pub const DATA_SOURCE_LIST: [DataSource; TOTAL_DATA_SOURCE_NUMBER as usize] = + [DataSource::EthEtherScan, DataSource::EthInfura, DataSource::BtcBlockChain]; impl Default for DataSource { - fn default() -> Self {DataSource::Invalid} + fn default() -> Self { + DataSource::Invalid + } } /// Data source to blockchain type pub fn data_source_to_index(data_source: DataSource) -> u32 { - match data_source { - DataSource::Invalid => u32::MAX, - DataSource::EthEtherScan => 0, - DataSource::EthInfura => 1, - DataSource::BtcBlockChain => 2, - } + match data_source { + DataSource::Invalid => u32::MAX, + DataSource::EthEtherScan => 0, + DataSource::EthInfura => 1, + DataSource::BtcBlockChain => 2, + } } /// Data source to blockchain type pub fn data_source_to_block_chain_type(data_source: DataSource) -> BlockChainType { - match data_source { - DataSource::Invalid => BlockChainType::Invalid, - DataSource::EthEtherScan => BlockChainType::ETH, - DataSource::EthInfura => BlockChainType::ETH, - DataSource::BtcBlockChain => BlockChainType::BTC, - } + match data_source { + DataSource::Invalid => BlockChainType::Invalid, + DataSource::EthEtherScan => BlockChainType::ETH, + DataSource::EthInfura => BlockChainType::ETH, + DataSource::BtcBlockChain => BlockChainType::BTC, + } } /// Http Get URL structure pub struct HttpGet<'a> { - pub blockchain: BlockChainType, - // URL affix - pub prefix: &'a str, - pub delimiter: &'a str, - pub postfix: &'a str, - pub api_token: &'a str, + pub blockchain: BlockChainType, + // URL affix + pub prefix: &'a str, + pub delimiter: &'a str, + pub postfix: &'a str, + pub api_token: &'a str, } /// Http Post URL structure pub struct HttpPost<'a> { - pub blockchain: BlockChainType, - // URL affix - pub url_main: &'a str, - pub api_token: &'a str, - // Body affix - pub prefix: &'a str, - pub delimiter: &'a str, - pub postfix: &'a str, + pub blockchain: BlockChainType, + // URL affix + pub url_main: &'a str, + pub api_token: &'a str, + // Body affix + pub prefix: &'a str, + pub delimiter: &'a str, + pub postfix: &'a str, } /// Request enum to wrap up both get and post method pub enum HttpRequest<'a> { - GET(HttpGet<'a>), - POST(HttpPost<'a>), + GET(HttpGet<'a>), + POST(HttpPost<'a>), } /// Store all API tokens for offchain worker to send request to website @@ -179,70 +180,73 @@ impl fmt::Debug for TokenInfo { // Fetch json result from remote URL with get method pub fn fetch_json_http_get<'a>(remote_url: &'a [u8]) -> Result, &'static str> { - let remote_url_str = core::str::from_utf8(remote_url) - .map_err(|_| "Error in converting remote_url to string")?; + let remote_url_str = + core::str::from_utf8(remote_url).map_err(|_| "Error in converting remote_url to string")?; - let pending = http::Request::get(remote_url_str).send() - .map_err(|_| "Error in sending http GET request")?; + let pending = http::Request::get(remote_url_str) + .send() + .map_err(|_| "Error in sending http GET request")?; - let response = pending.wait() - .map_err(|_| "Error in waiting http response back")?; + let response = pending.wait().map_err(|_| "Error in waiting http response back")?; - if response.code != 200 { - log::warn!("Unexpected status code: {}", response.code); - return Err("Non-200 status code returned from http request"); - } + if response.code != 200 { + log::warn!("Unexpected status code: {}", response.code); + return Err("Non-200 status code returned from http request") + } - let json_result: Vec = response.body().collect::>(); + let json_result: Vec = response.body().collect::>(); - let balance = - core::str::from_utf8(&json_result).map_err(|_| "JSON result cannot convert to string")?; + let balance = + core::str::from_utf8(&json_result).map_err(|_| "JSON result cannot convert to string")?; - Ok(balance.as_bytes().to_vec()) + Ok(balance.as_bytes().to_vec()) } // Fetch json result from remote URL with post method -pub fn fetch_json_http_post<'a>(remote_url: &'a [u8], body: &'a [u8]) -> Result, &'static str> { - let remote_url_str = core::str::from_utf8(remote_url) - .map_err(|_| "Error in converting remote_url to string")?; +pub fn fetch_json_http_post<'a>( + remote_url: &'a [u8], + body: &'a [u8], +) -> Result, &'static str> { + let remote_url_str = + core::str::from_utf8(remote_url).map_err(|_| "Error in converting remote_url to string")?; - log::info!("Offchain Worker post request url is {}.", remote_url_str); + log::info!("Offchain Worker post request url is {}.", remote_url_str); - let pending = http::Request::post(remote_url_str, vec![body]).send() - .map_err(|_| "Error in sending http POST request")?; + let pending = http::Request::post(remote_url_str, vec![body]) + .send() + .map_err(|_| "Error in sending http POST request")?; - let response = pending.wait() - .map_err(|_| "Error in waiting http response back")?; + let response = pending.wait().map_err(|_| "Error in waiting http response back")?; - if response.code != 200 { - log::warn!("Unexpected status code: {}", response.code); - return Err("Non-200 status code returned from http request"); - } + if response.code != 200 { + log::warn!("Unexpected status code: {}", response.code); + return Err("Non-200 status code returned from http request") + } - let json_result: Vec = response.body().collect::>(); + let json_result: Vec = response.body().collect::>(); - let balance = - core::str::from_utf8(&json_result).map_err(|_| "JSON result cannot convert to string")?; + let balance = + core::str::from_utf8(&json_result).map_err(|_| "JSON result cannot convert to string")?; - Ok(balance.as_bytes().to_vec()) + Ok(balance.as_bytes().to_vec()) } // Send request to local server for query api tokens pub fn send_get_token() -> Result, &'static str> { - let pending = http::Request::get(super::TOKEN_SERVER_URL).send() - .map_err(|_| "Error in sending http GET request")?; + let pending = http::Request::get(super::TOKEN_SERVER_URL) + .send() + .map_err(|_| "Error in sending http GET request")?; - let response = pending.wait() - .map_err(|_| "Error in waiting http response back")?; + let response = pending.wait().map_err(|_| "Error in waiting http response back")?; - if response.code != 200 { - log::warn!("Unexpected status code: {}", response.code); - return Err("Non-200 status code returned from http request"); - } + if response.code != 200 { + log::warn!("Unexpected status code: {}", response.code); + return Err("Non-200 status code returned from http request") + } - let json_result: Vec = response.body().collect::>(); + let json_result: Vec = response.body().collect::>(); - Ok(json_result) + Ok(json_result) } // Get the API tokens from local server @@ -257,65 +261,73 @@ pub fn get_token() -> Result<(), &'static str> { #[allow(dead_code)] // Parse the balance from etherscan response pub fn parse_etherscan_balances(price_str: &str) -> Option> { - // { - // "status": "1", - // "message": "OK", - // "result": - // [ - // {"account":"0x742d35Cc6634C0532925a3b844Bc454e4438f44e","balance":"3804372455842738500000001"}, - // {"account":"0xBE0eB53F46cd790Cd13851d5EFf43D12404d33E8","balance":"2571179226430511381996287"} - // ] - // } - log::info!("Offchain Worker response from etherscan is {:?}", price_str); - - let token_info: EtherScanResponse = serde_json::from_str(price_str).ok()?; - let result: Vec = token_info.result.iter().map(|item| match utils::chars_to_u128(&item.balance.iter().map(|i| *i as char).collect()) { - Ok(balance) => balance, - Err(_) => 0_u128, - }).collect(); - Some(result) + // { + // "status": "1", + // "message": "OK", + // "result": + // [ + // {"account":"0x742d35Cc6634C0532925a3b844Bc454e4438f44e","balance":"3804372455842738500000001"}, + // {"account":"0xBE0eB53F46cd790Cd13851d5EFf43D12404d33E8","balance":"2571179226430511381996287"} + // ] + // } + log::info!("Offchain Worker response from etherscan is {:?}", price_str); + + let token_info: EtherScanResponse = serde_json::from_str(price_str).ok()?; + let result: Vec = token_info + .result + .iter() + .map(|item| { + match utils::chars_to_u128(&item.balance.iter().map(|i| *i as char).collect()) { + Ok(balance) => balance, + Err(_) => 0_u128, + } + }) + .collect(); + Some(result) } #[allow(dead_code)] // Parse balances from blockchain info response -pub fn parse_blockchain_info_balances(price_str: &str) -> Option>{ - // { - // "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa":{"final_balance":6835384571,"n_tx":2635,"total_received":6835384571}, - // "15EW3AMRm2yP6LEF5YKKLYwvphy3DmMqN6":{"final_balance":0,"n_tx":4,"total_received":310925609} - // } - let mut balance_vec: Vec = Vec::new(); - - let value: serde_json::Value = serde_json::from_str(price_str).ok()?; - - match value { - serde_json::Value::Object(map_data) => { - for (_, v) in map_data.iter() { - match v["final_balance"].as_u64() { - Some(balance) => balance_vec.push(balance as u128), - None => (), - } - } - }, - _ => (), - }; - - Some(balance_vec) +pub fn parse_blockchain_info_balances(price_str: &str) -> Option> { + // { + // "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa":{"final_balance":6835384571,"n_tx":2635,"total_received":6835384571}, + // "15EW3AMRm2yP6LEF5YKKLYwvphy3DmMqN6":{"final_balance":0,"n_tx":4,"total_received":310925609} + // } + let mut balance_vec: Vec = Vec::new(); + + let value: serde_json::Value = serde_json::from_str(price_str).ok()?; + + match value { + serde_json::Value::Object(map_data) => + for (_, v) in map_data.iter() { + match v["final_balance"].as_u64() { + Some(balance) => balance_vec.push(balance as u128), + None => (), + } + }, + _ => (), + }; + + Some(balance_vec) } #[allow(dead_code)] // Parse the balance from infura response pub fn parse_infura_balances(price_str: &str) -> Option> { - //[ - // {"jsonrpc":"2.0","id":1,"result":"0x4563918244f40000"}, - // {"jsonrpc":"2.0","id":1,"result":"0xff"} - //] - - let token_info: Vec = serde_json::from_str(price_str).ok()?; - let result: Vec = token_info.iter().map(|item| match utils::chars_to_u128(&item.result.iter().map(|i| *i as char).collect()) { - Ok(balance) => balance, - Err(_) => 0_u128, - }).collect(); - Some(result) + //[ + // {"jsonrpc":"2.0","id":1,"result":"0x4563918244f40000"}, + // {"jsonrpc":"2.0","id":1,"result":"0xff"} + //] + + let token_info: Vec = serde_json::from_str(price_str).ok()?; + let result: Vec = token_info + .iter() + .map(|item| match utils::chars_to_u128(&item.result.iter().map(|i| *i as char).collect()) { + Ok(balance) => balance, + Err(_) => 0_u128, + }) + .collect(); + Some(result) } // Parse the token from local server @@ -334,4 +346,4 @@ pub fn parse_store_tokens(resp_str: &str) -> Result<(), &'static str> { Err("Error occurred while parsing json string") }, } -} \ No newline at end of file +} diff --git a/pallets/offchain-worker/src/utils.rs b/pallets/offchain-worker/src/utils.rs index 64a6263..44b7002 100644 --- a/pallets/offchain-worker/src/utils.rs +++ b/pallets/offchain-worker/src/utils.rs @@ -2,7 +2,7 @@ use sp_std::prelude::*; // u128 number string to u128 pub fn chars_to_u128(vec: &Vec) -> Result { - // Check if the number string is decimal or hexadecimal (whether starting with 0x or not) + // Check if the number string is decimal or hexadecimal (whether starting with 0x or not) let base = if vec.len() >= 2 && vec[0] == '0' && vec[1] == 'x' { // This is a hexadecimal number 16 @@ -13,19 +13,19 @@ pub fn chars_to_u128(vec: &Vec) -> Result { let mut result: u128 = 0; for (i, item) in vec.iter().enumerate() { - // Skip the 0 and x digit for hex. + // Skip the 0 and x digit for hex. // Using skip here instead of a new vec build to avoid an unnecessary copy operation if base == 16 && i < 2 { - continue; + continue } let n = item.to_digit(base); match n { Some(i) => { - let i_64 = i as u128; + let i_64 = i as u128; result = result * base as u128 + i_64; if result < i_64 { - return Err("Wrong u128 balance data format"); + return Err("Wrong u128 balance data format") } }, None => return Err("Wrong u128 balance data format"), @@ -35,18 +35,16 @@ pub fn chars_to_u128(vec: &Vec) -> Result { } // number byte to string byte -pub fn u8_to_str_byte(a: u8) -> u8{ +pub fn u8_to_str_byte(a: u8) -> u8 { if a < 10 { - return a + 48 as u8; - } - else { - return a + 87 as u8; + return a + 48 as u8 + } else { + return a + 87 as u8 } } // address to string bytes pub fn address_to_string(address: &[u8; 20]) -> Vec { - let mut vec_result: Vec = Vec::new(); for item in address { let a: u8 = item & 0x0F; @@ -54,5 +52,5 @@ pub fn address_to_string(address: &[u8; 20]) -> Vec { vec_result.push(u8_to_str_byte(b)); vec_result.push(u8_to_str_byte(a)); } - return vec_result; + return vec_result } diff --git a/pallets/offchain-worker/src/weights.rs b/pallets/offchain-worker/src/weights.rs index 6c70f6d..560e1fa 100644 --- a/pallets/offchain-worker/src/weights.rs +++ b/pallets/offchain-worker/src/weights.rs @@ -35,11 +35,13 @@ // --output=./pallets/offchain-worker/src/weights.rs // --template=./.maintain/frame-weight-template.hbs - #![allow(unused_parens)] #![allow(unused_imports)] -use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use frame_support::{ + traits::Get, + weights::{constants::RocksDbWeight, Weight}, +}; use sp_std::marker::PhantomData; /// Weight functions needed for pallet_offchain_worker. diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..6005fb8 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,20 @@ +# Basic +hard_tabs = true +max_width = 100 +use_small_heuristics = "Max" +# Imports +imports_granularity = "Crate" +reorder_imports = true +# Consistency +newline_style = "Unix" +# Misc +chain_width = 80 +spaces_around_ranges = false +binop_separator = "Back" +reorder_impl_items = false +match_arm_leading_pipes = "Preserve" +match_arm_blocks = false +match_block_trailing_comma = true +trailing_comma = "Vertical" +trailing_semicolon = false +use_field_init_shorthand = true \ No newline at end of file