From 940bbf39dd8d0f386a3037170aa63f6885cb7a35 Mon Sep 17 00:00:00 2001 From: Juan Ignacio Rios <54085674+JuaniRios@users.noreply.github.com> Date: Thu, 13 Oct 2022 14:50:56 +0200 Subject: [PATCH] feat(job-payment): payments working (#9) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Feat/manual job assignment (#8) * feat(manual-job-assignment): added extrinsic call to manually assign a job * chore(): updated docs * feat(job-payment): payments working feat(job-payment): first commit feat(statemint-backed-native-asset): all working as intended. At some point we need acurast to statemint transfers by burning native token, but not needed for now fix(xcmp-panic): statemint not compiling chore(proxy-pallet): refactoring, new events and errors chore(proxy-pallet): removed unnecessary xcm-emulator folder feat(proxy-pallet): emulator test added but ran with simulator (mock runtimes added in mock.rs instead of using real runtimes) emulator fix(attestation-origin-validation): added origin validation when submitting an attestation (#6) doc(wasm-contract-integration): added section for example wasm smart … (#5) * doc(wasm-contract-integration): added section for example wasm smart contract integration * Update pallets/acurast/README.md Co-authored-by: Rodrigo Quelhas <22591718+RomarQ@users.noreply.github.com> Co-authored-by: Rodrigo Quelhas <22591718+RomarQ@users.noreply.github.com> fix(imports): fixed imports (#4) Feat/unit tests (#3) * feat(unit-tests): started to add unit tests * feat(unit-tests): added more unit tests * feat(unit-tests): moved tests to their own file * feat(unit-tests): restructured source code * chore(): removed unused dependency readme pallet proxy working * feat(job-payment): simulation working after rebase * feat(job-payment): rebase * task(job-payment): cargo fmt and node setup tutorial added on examples folder * chore(feat/job-payment): removed cargo patch * chore(): fixed tests * chore(): removed emulations and examples Co-authored-by: Mike Godenzi Co-authored-by: Juan --- Cargo.toml | 2 +- p256-crypto/src/application_crypto.rs | 74 +- p256-crypto/src/core.rs | 1174 +++++++++++++------------ p256-crypto/src/multi_signature.rs | 343 ++++---- pallets/acurast/Cargo.toml | 16 + pallets/acurast/README.md | 10 +- pallets/acurast/src/lib.rs | 189 +++- pallets/acurast/src/mock.rs | 101 ++- pallets/acurast/src/payments.rs | 67 ++ pallets/acurast/src/tests.rs | 186 ++-- pallets/acurast/src/types.rs | 25 +- pallets/acurast/src/utils.rs | 10 +- pallets/acurast/src/xcm_adapters.rs | 163 ++++ pallets/proxy/Cargo.toml | 1 + pallets/proxy/src/lib.rs | 361 ++++---- pallets/proxy/src/mock.rs | 261 +++--- pallets/proxy/src/tests.rs | 1094 +++++++++++------------ 17 files changed, 2362 insertions(+), 1715 deletions(-) create mode 100644 pallets/acurast/src/payments.rs create mode 100644 pallets/acurast/src/xcm_adapters.rs diff --git a/Cargo.toml b/Cargo.toml index 057d21fb..0ced0fba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,4 +5,4 @@ panic = "unwind" members = [ "pallets/*", "p256-crypto", -] +] \ No newline at end of file diff --git a/p256-crypto/src/application_crypto.rs b/p256-crypto/src/application_crypto.rs index 08725f14..8293745a 100644 --- a/p256-crypto/src/application_crypto.rs +++ b/p256-crypto/src/application_crypto.rs @@ -1,53 +1,53 @@ pub mod p256 { - use sp_application_crypto::RuntimePublic; - use sp_core::crypto::KeyTypeId; - use sp_runtime::traits::Verify; - use sp_std::prelude::*; + use sp_application_crypto::RuntimePublic; + use sp_core::crypto::KeyTypeId; + use sp_runtime::traits::Verify; + use sp_std::prelude::*; - pub use crate::core::p256::*; + pub use crate::core::p256::*; - pub const P256: KeyTypeId = KeyTypeId(*b"p256"); + pub const P256: KeyTypeId = KeyTypeId(*b"p256"); - mod app { - use sp_application_crypto::app_crypto; - use sp_runtime::BoundToRuntimeAppPublic; + mod app { + use sp_application_crypto::app_crypto; + use sp_runtime::BoundToRuntimeAppPublic; - use crate::core::p256; + use crate::core::p256; - use super::P256; + use super::P256; - app_crypto!(p256, P256); + app_crypto!(p256, P256); - impl BoundToRuntimeAppPublic for Public { - type Public = Self; - } - } + impl BoundToRuntimeAppPublic for Public { + type Public = Self; + } + } - pub use app::{Public as AppPublic, Signature as AppSignature}; + pub use app::{Public as AppPublic, Signature as AppSignature}; - impl RuntimePublic for Public { - type Signature = Signature; + impl RuntimePublic for Public { + type Signature = Signature; - fn all(_key_type: KeyTypeId) -> Vec { - vec![] - } + fn all(_key_type: KeyTypeId) -> Vec { + vec![] + } - fn generate_pair(_key_type: KeyTypeId, seed: Option>) -> Self { - Pair::generate_from_seed_bytes(&seed.expect("seed needs to be provided")) - .expect("Pair generation") - .get_public() - } + fn generate_pair(_key_type: KeyTypeId, seed: Option>) -> Self { + Pair::generate_from_seed_bytes(&seed.expect("seed needs to be provided")) + .expect("Pair generation") + .get_public() + } - fn sign>(&self, _key_type: KeyTypeId, _msg: &M) -> Option { - None - } + fn sign>(&self, _key_type: KeyTypeId, _msg: &M) -> Option { + None + } - fn verify>(&self, msg: &M, signature: &Self::Signature) -> bool { - signature.verify(msg.as_ref(), &self) - } + fn verify>(&self, msg: &M, signature: &Self::Signature) -> bool { + signature.verify(msg.as_ref(), &self) + } - fn to_raw_vec(&self) -> Vec { - sp_core::crypto::ByteArray::to_raw_vec(self) - } - } + fn to_raw_vec(&self) -> Vec { + sp_core::crypto::ByteArray::to_raw_vec(self) + } + } } diff --git a/p256-crypto/src/core.rs b/p256-crypto/src/core.rs index 660a4505..7800c25d 100644 --- a/p256-crypto/src/core.rs +++ b/p256-crypto/src/core.rs @@ -1,588 +1,598 @@ pub mod p256 { - #[cfg(feature = "std")] - use bip39::{Language, Mnemonic, MnemonicType}; - use codec::{Decode, Encode, MaxEncodedLen}; - - use p256::{ - ecdsa::{recoverable, signature::Signer, SigningKey, VerifyingKey}, - EncodedPoint, PublicKey, SecretKey, - }; - use scale_info::TypeInfo; - #[cfg(feature = "std")] - use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; - use sp_runtime::traits::{IdentifyAccount, Lazy, Verify}; - use sp_runtime_interface::pass_by::PassByInner; - - #[cfg(feature = "std")] - use sp_core::crypto::Ss58Codec; - use sp_core::crypto::{ - ByteArray, CryptoType, CryptoTypeId, CryptoTypePublicPair, Derive, Public as TraitPublic, - UncheckedFrom, - }; - #[cfg(feature = "full_crypto")] - use sp_core::{ - crypto::{DeriveJunction, Pair as TraitPair, SecretStringError}, - hashing::blake2_256, - }; - - /// An identifier used to match public keys against ecdsa keys - pub const CRYPTO_ID: CryptoTypeId = CryptoTypeId(*b"p256"); - - /// The ECDSA compressed public key. - #[cfg_attr(feature = "full_crypto", derive(Hash))] - #[derive( - Clone, - Copy, - Encode, - Decode, - PassByInner, - MaxEncodedLen, - TypeInfo, - Eq, - PartialEq, - PartialOrd, - Ord, - )] - pub struct Public(pub [u8; 33]); - - impl Public { - /// A new instance from the given 33-byte `data`. - /// - /// NOTE: No checking goes on to ensure this is a real public key. Only use it if - /// you are certain that the array actually is a pubkey. GIGO! - pub fn from_raw(data: [u8; 33]) -> Self { - Self(data) - } - } - - impl Derive for Public {} - - impl AsRef<[u8]> for Public { - fn as_ref(&self) -> &[u8] { - &self.0[..] - } - } - - impl AsMut<[u8]> for Public { - fn as_mut(&mut self) -> &mut [u8] { - &mut self.0[..] - } - } - - impl TryFrom<&[u8]> for Public { - type Error = (); - - fn try_from(data: &[u8]) -> Result { - if data.len() != Self::LEN { - return Err(()) - } - let mut r = [0u8; Self::LEN]; - r.copy_from_slice(data); - Ok(Self::unchecked_from(r)) - } - } - - impl TraitPublic for Public { - fn to_public_crypto_pair(&self) -> CryptoTypePublicPair { - CryptoTypePublicPair(CRYPTO_ID, self.to_raw_vec()) - } - } - - impl From for CryptoTypePublicPair { - fn from(key: Public) -> Self { - (&key).into() - } - } - - impl From<&Public> for CryptoTypePublicPair { - fn from(key: &Public) -> Self { - CryptoTypePublicPair(CRYPTO_ID, key.to_raw_vec()) - } - } - - impl ByteArray for Public { - const LEN: usize = 33; - } - - impl CryptoType for Public { - #[cfg(feature = "full_crypto")] - type Pair = Pair; - } - - impl UncheckedFrom<[u8; 33]> for Public { - fn unchecked_from(x: [u8; 33]) -> Self { - Public(x) - } - } - - impl From for [u8; 33] { - fn from(x: Public) -> [u8; 33] { - x.0 - } - } - - #[cfg(feature = "std")] - impl std::fmt::Display for Public { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "{}", self.to_ss58check()) - } - } - - impl sp_std::fmt::Debug for Public { - #[cfg(feature = "std")] - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - let s = self.to_ss58check(); - write!(f, "{} ({}...)", sp_core::hexdisplay::HexDisplay::from(&self.as_ref()), &s[0..8]) - } - - #[cfg(not(feature = "std"))] - fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result { - Ok(()) - } - } - - #[cfg(feature = "std")] - impl Serialize for Public { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_str(&self.to_ss58check()) - } - } - - #[cfg(feature = "std")] - impl<'de> Deserialize<'de> for Public { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - Public::from_ss58check(&String::deserialize(deserializer)?) - .map_err(|e| de::Error::custom(format!("{:?}", e))) - } - } - - impl IdentifyAccount for Public { - type AccountId = Self; - fn into_account(self) -> Self { - self - } - } - - #[cfg(feature = "full_crypto")] - type Seed = [u8; 32]; - - /// A signature (a 512-bit value, plus 8 bits for recovery ID). - #[cfg_attr(any(feature = "std", feature = "full_crypto"), derive(Hash))] - #[derive(Encode, Decode, MaxEncodedLen, PassByInner, TypeInfo, PartialEq, Eq)] - pub struct Signature(pub [u8; 65]); - - impl TryFrom for Signature { - type Error = (); - - fn try_from(data: recoverable::Signature) -> Result { - let signature_bytes = p256::ecdsa::signature::Signature::as_bytes(&data); - Signature::try_from(signature_bytes) - } - } - - impl TryFrom<&[u8]> for Signature { - type Error = (); - - fn try_from(data: &[u8]) -> Result { - if data.len() == 65 { - let mut inner = [0u8; 65]; - inner.copy_from_slice(data); - Ok(Signature(inner)) - } else { - Err(()) - } - } - } - - #[cfg(feature = "std")] - impl Serialize for Signature { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_str(&hex::encode(self)) - } - } - - #[cfg(feature = "std")] - impl<'de> Deserialize<'de> for Signature { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let signature_hex = hex::decode(&String::deserialize(deserializer)?) - .map_err(|e| de::Error::custom(format!("{:?}", e)))?; - Signature::try_from(signature_hex.as_ref()) - .map_err(|e| de::Error::custom(format!("{:?}", e))) - } - } - - impl Clone for Signature { - fn clone(&self) -> Self { - let mut r = [0u8; 65]; - r.copy_from_slice(&self.0[..]); - Signature(r) - } - } - - impl Default for Signature { - fn default() -> Self { - Signature([0u8; 65]) - } - } - - impl From for [u8; 65] { - fn from(v: Signature) -> [u8; 65] { - v.0 - } - } - - impl AsRef<[u8; 65]> for Signature { - fn as_ref(&self) -> &[u8; 65] { - &self.0 - } - } - - impl AsRef<[u8]> for Signature { - fn as_ref(&self) -> &[u8] { - &self.0[..] - } - } - - impl AsMut<[u8]> for Signature { - fn as_mut(&mut self) -> &mut [u8] { - &mut self.0[..] - } - } - - impl sp_std::fmt::Debug for Signature { - fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result { - write!(f, "{}", sp_core::hexdisplay::HexDisplay::from(&self.0)) - } - } - - impl UncheckedFrom<[u8; 65]> for Signature { - fn unchecked_from(data: [u8; 65]) -> Signature { - Signature(data) - } - } - - impl CryptoType for Signature { - #[cfg(feature = "full_crypto")] - type Pair = Pair; - } - - impl Verify for Signature { - type Signer = Public; - - fn verify>(&self, mut msg: L, signer: &Self::Signer) -> bool { - match PublicKey::from_sec1_bytes(signer.as_ref()) { - Ok(public_key) => { - let message = msg.get(); - let signature_bytes: &[u8] = self.as_ref(); - let verifying_key = VerifyingKey::from(public_key); - let verifying_key_from_signature = - recoverable::Signature::try_from(signature_bytes.as_ref()) - .unwrap() - .recover_verify_key(message) - .unwrap(); - - verifying_key == verifying_key_from_signature - }, - Err(_) => false, - } - } - } - - /// A key pair. - #[derive(Clone)] - pub struct Pair { - public: Public, - #[cfg(feature = "full_crypto")] - secret: SecretKey, - } - - impl Pair { - pub fn generate_from_seed_bytes(bytes: &[u8]) -> Result { - let secret = SecretKey::from_be_bytes(bytes).map_err(|_error| ())?; - let public = secret.public_key(); - let pub_bytes = public.to_bytes(); - Ok(Pair { - public: Public(pub_bytes), - #[cfg(feature = "full_crypto")] - secret, - }) - } - - pub fn get_public(&self) -> Public { - self.public - } - } - - #[cfg(feature = "full_crypto")] - impl CryptoType for Pair { - type Pair = Pair; - } - - /// An error when deriving a key. - #[cfg(feature = "full_crypto")] - pub enum DeriveError { - /// A soft key was found in the path (and is unsupported). - SoftKeyInPath, - } - - /// Derive a single hard junction. - #[cfg(feature = "full_crypto")] - fn derive_hard_junction(secret_seed: &Seed, cc: &[u8; 32]) -> Seed { - ("Secp256r1", secret_seed, cc).using_encoded(blake2_256) - } - - trait ToBytes33 { - fn to_bytes(&self) -> [u8; 33]; - } - - impl ToBytes33 for p256::PublicKey { - fn to_bytes(&self) -> [u8; 33] { - let encoded_point = EncodedPoint::from(self); - let compressed_point = encoded_point.compress(); - compressed_point.as_bytes().try_into().unwrap() - } - } - - #[cfg(feature = "full_crypto")] - impl TraitPair for Pair { - type Public = Public; - type Seed = Seed; - type Signature = Signature; - type DeriveError = DeriveError; - - // Using default fn generate() - - /// Generate new secure (random) key pair and provide the recovery phrase. - /// - /// You can recover the same key later with `from_phrase`. - fn generate_with_phrase(password: Option<&str>) -> (Pair, String, Seed) { - let mnemonic = Mnemonic::new(MnemonicType::Words12, Language::English); - let phrase = mnemonic.phrase(); - let (pair, seed) = Self::from_phrase(phrase, password) - .expect("All phrases generated by Mnemonic are valid; qed"); - (pair, phrase.to_owned(), seed) - } - - /// Generate key pair from given recovery phrase and password. - fn from_phrase( - phrase: &str, - password: Option<&str>, - ) -> Result<(Pair, Seed), SecretStringError> { - let big_seed = substrate_bip39::seed_from_entropy( - Mnemonic::from_phrase(phrase, Language::English) - .map_err(|_| SecretStringError::InvalidPhrase)? - .entropy(), - password.unwrap_or(""), - ) - .map_err(|_| SecretStringError::InvalidSeed)?; - let mut seed = Seed::default(); - seed.copy_from_slice(&big_seed[0..32]); - Self::from_seed_slice(&big_seed[0..32]).map(|x| (x, seed)) - } - - /// Make a new key pair from secret seed material. - /// - /// You should never need to use this; generate(), generate_with_phrase - fn from_seed(seed: &Seed) -> Pair { - Self::from_seed_slice(&seed[..]).expect("seed has valid length; qed") - } - - /// Make a new key pair from secret seed material. The slice must be 32 bytes long or it - /// will return `None`. - /// - /// You should never need to use this; generate(), generate_with_phrase - fn from_seed_slice(seed_slice: &[u8]) -> Result { - Self::generate_from_seed_bytes(seed_slice).map_err(|_| SecretStringError::InvalidSeed) - } - - /// Derive a child key from a series of given junctions. - fn derive>( - &self, - path: Iter, - _seed: Option, - ) -> Result<(Pair, Option), DeriveError> { - let mut acc = self.seed(); - for j in path { - match j { - DeriveJunction::Soft(_cc) => return Err(DeriveError::SoftKeyInPath), - DeriveJunction::Hard(cc) => acc = derive_hard_junction(&acc, &cc), - } - } - Ok((Self::from_seed(&acc), Some(acc))) - } - - /// Get the public key. - fn public(&self) -> Public { - self.public - } - - /// Sign a message. - fn sign(&self, message: &[u8]) -> Signature { - let key = SigningKey::from(&self.secret); - let p256_signature: recoverable::Signature = key.sign(message); - - Signature::try_from(p256_signature).expect("invalid signature") - } - - /// Verify a signature on a message. Returns true if the signature is good. - fn verify>( - sig: &Self::Signature, - message: M, - pubkey: &Self::Public, - ) -> bool { - sig.verify(message.as_ref(), pubkey) - } - - /// Verify a signature on a message. Returns true if the signature is good. - /// - /// This doesn't use the type system to ensure that `sig` and `pubkey` are the correct - /// size. Use it only if you're coming from byte buffers and need the speed. - fn verify_weak, M: AsRef<[u8]>>(sig: &[u8], message: M, pubkey: P) -> bool { - // TODO: weak version, for now use normal verify - let signature = match Self::Signature::try_from(sig) { - Err(_) => return false, - Ok(sign) => sign, - }; - let public = match Self::Public::try_from(pubkey.as_ref()) { - Err(_) => return false, - Ok(pk) => pk, - }; - - Self::verify(&signature, message, &public) - } - - /// Return a vec filled with raw data. - fn to_raw_vec(&self) -> Vec { - self.seed().to_vec() - } - } - - #[cfg(feature = "full_crypto")] - impl Pair { - /// Get the seed for this key. - pub fn seed(&self) -> Seed { - *self.secret.to_be_bytes().as_ref() - } - - /// Exactly as `from_string` except that if no matches are found then, the the first 32 - /// characters are taken (padded with spaces as necessary) and used as the MiniSecretKey. - pub fn from_legacy_string(s: &str, password_override: Option<&str>) -> Pair { - Self::from_string(s, password_override).unwrap_or_else(|_| { - let mut padded_seed: Seed = [b' '; 32]; - let len = s.len().min(32); - padded_seed[..len].copy_from_slice(&s.as_bytes()[..len]); - Self::from_seed(&padded_seed) - }) - } - } + #[cfg(feature = "std")] + use bip39::{Language, Mnemonic, MnemonicType}; + use codec::{Decode, Encode, MaxEncodedLen}; + + use p256::{ + ecdsa::{recoverable, signature::Signer, SigningKey, VerifyingKey}, + EncodedPoint, PublicKey, SecretKey, + }; + use scale_info::TypeInfo; + #[cfg(feature = "std")] + use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; + use sp_runtime::traits::{IdentifyAccount, Lazy, Verify}; + use sp_runtime_interface::pass_by::PassByInner; + + #[cfg(feature = "std")] + use sp_core::crypto::Ss58Codec; + use sp_core::crypto::{ + ByteArray, CryptoType, CryptoTypeId, CryptoTypePublicPair, Derive, Public as TraitPublic, + UncheckedFrom, + }; + #[cfg(feature = "full_crypto")] + use sp_core::{ + crypto::{DeriveJunction, Pair as TraitPair, SecretStringError}, + hashing::blake2_256, + }; + + /// An identifier used to match public keys against ecdsa keys + pub const CRYPTO_ID: CryptoTypeId = CryptoTypeId(*b"p256"); + + /// The ECDSA compressed public key. + #[cfg_attr(feature = "full_crypto", derive(Hash))] + #[derive( + Clone, + Copy, + Encode, + Decode, + PassByInner, + MaxEncodedLen, + TypeInfo, + Eq, + PartialEq, + PartialOrd, + Ord, + )] + pub struct Public(pub [u8; 33]); + + impl Public { + /// A new instance from the given 33-byte `data`. + /// + /// NOTE: No checking goes on to ensure this is a real public key. Only use it if + /// you are certain that the array actually is a pubkey. GIGO! + pub fn from_raw(data: [u8; 33]) -> Self { + Self(data) + } + } + + impl Derive for Public {} + + impl AsRef<[u8]> for Public { + fn as_ref(&self) -> &[u8] { + &self.0[..] + } + } + + impl AsMut<[u8]> for Public { + fn as_mut(&mut self) -> &mut [u8] { + &mut self.0[..] + } + } + + impl TryFrom<&[u8]> for Public { + type Error = (); + + fn try_from(data: &[u8]) -> Result { + if data.len() != Self::LEN { + return Err(()); + } + let mut r = [0u8; Self::LEN]; + r.copy_from_slice(data); + Ok(Self::unchecked_from(r)) + } + } + + impl TraitPublic for Public { + fn to_public_crypto_pair(&self) -> CryptoTypePublicPair { + CryptoTypePublicPair(CRYPTO_ID, self.to_raw_vec()) + } + } + + impl From for CryptoTypePublicPair { + fn from(key: Public) -> Self { + (&key).into() + } + } + + impl From<&Public> for CryptoTypePublicPair { + fn from(key: &Public) -> Self { + CryptoTypePublicPair(CRYPTO_ID, key.to_raw_vec()) + } + } + + impl ByteArray for Public { + const LEN: usize = 33; + } + + impl CryptoType for Public { + #[cfg(feature = "full_crypto")] + type Pair = Pair; + } + + impl UncheckedFrom<[u8; 33]> for Public { + fn unchecked_from(x: [u8; 33]) -> Self { + Public(x) + } + } + + impl From for [u8; 33] { + fn from(x: Public) -> [u8; 33] { + x.0 + } + } + + #[cfg(feature = "std")] + impl std::fmt::Display for Public { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}", self.to_ss58check()) + } + } + + impl sp_std::fmt::Debug for Public { + #[cfg(feature = "std")] + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + let s = self.to_ss58check(); + write!( + f, + "{} ({}...)", + sp_core::hexdisplay::HexDisplay::from(&self.as_ref()), + &s[0..8] + ) + } + + #[cfg(not(feature = "std"))] + fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result { + Ok(()) + } + } + + #[cfg(feature = "std")] + impl Serialize for Public { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&self.to_ss58check()) + } + } + + #[cfg(feature = "std")] + impl<'de> Deserialize<'de> for Public { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + Public::from_ss58check(&String::deserialize(deserializer)?) + .map_err(|e| de::Error::custom(format!("{:?}", e))) + } + } + + impl IdentifyAccount for Public { + type AccountId = Self; + fn into_account(self) -> Self { + self + } + } + + #[cfg(feature = "full_crypto")] + type Seed = [u8; 32]; + + /// A signature (a 512-bit value, plus 8 bits for recovery ID). + #[cfg_attr(any(feature = "std", feature = "full_crypto"), derive(Hash))] + #[derive(Encode, Decode, MaxEncodedLen, PassByInner, TypeInfo, PartialEq, Eq)] + pub struct Signature(pub [u8; 65]); + + impl TryFrom for Signature { + type Error = (); + + fn try_from(data: recoverable::Signature) -> Result { + let signature_bytes = p256::ecdsa::signature::Signature::as_bytes(&data); + Signature::try_from(signature_bytes) + } + } + + impl TryFrom<&[u8]> for Signature { + type Error = (); + + fn try_from(data: &[u8]) -> Result { + if data.len() == 65 { + let mut inner = [0u8; 65]; + inner.copy_from_slice(data); + Ok(Signature(inner)) + } else { + Err(()) + } + } + } + + #[cfg(feature = "std")] + impl Serialize for Signature { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&hex::encode(self)) + } + } + + #[cfg(feature = "std")] + impl<'de> Deserialize<'de> for Signature { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let signature_hex = hex::decode(&String::deserialize(deserializer)?) + .map_err(|e| de::Error::custom(format!("{:?}", e)))?; + Signature::try_from(signature_hex.as_ref()) + .map_err(|e| de::Error::custom(format!("{:?}", e))) + } + } + + impl Clone for Signature { + fn clone(&self) -> Self { + let mut r = [0u8; 65]; + r.copy_from_slice(&self.0[..]); + Signature(r) + } + } + + impl Default for Signature { + fn default() -> Self { + Signature([0u8; 65]) + } + } + + impl From for [u8; 65] { + fn from(v: Signature) -> [u8; 65] { + v.0 + } + } + + impl AsRef<[u8; 65]> for Signature { + fn as_ref(&self) -> &[u8; 65] { + &self.0 + } + } + + impl AsRef<[u8]> for Signature { + fn as_ref(&self) -> &[u8] { + &self.0[..] + } + } + + impl AsMut<[u8]> for Signature { + fn as_mut(&mut self) -> &mut [u8] { + &mut self.0[..] + } + } + + impl sp_std::fmt::Debug for Signature { + fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result { + write!(f, "{}", sp_core::hexdisplay::HexDisplay::from(&self.0)) + } + } + + impl UncheckedFrom<[u8; 65]> for Signature { + fn unchecked_from(data: [u8; 65]) -> Signature { + Signature(data) + } + } + + impl CryptoType for Signature { + #[cfg(feature = "full_crypto")] + type Pair = Pair; + } + + impl Verify for Signature { + type Signer = Public; + + fn verify>(&self, mut msg: L, signer: &Self::Signer) -> bool { + match PublicKey::from_sec1_bytes(signer.as_ref()) { + Ok(public_key) => { + let message = msg.get(); + let signature_bytes: &[u8] = self.as_ref(); + let verifying_key = VerifyingKey::from(public_key); + let verifying_key_from_signature = + recoverable::Signature::try_from(signature_bytes.as_ref()) + .unwrap() + .recover_verify_key(message) + .unwrap(); + + verifying_key == verifying_key_from_signature + } + Err(_) => false, + } + } + } + + /// A key pair. + #[derive(Clone)] + pub struct Pair { + public: Public, + #[cfg(feature = "full_crypto")] + secret: SecretKey, + } + + impl Pair { + pub fn generate_from_seed_bytes(bytes: &[u8]) -> Result { + let secret = SecretKey::from_be_bytes(bytes).map_err(|_error| ())?; + let public = secret.public_key(); + let pub_bytes = public.to_bytes(); + Ok(Pair { + public: Public(pub_bytes), + #[cfg(feature = "full_crypto")] + secret, + }) + } + + pub fn get_public(&self) -> Public { + self.public + } + } + + #[cfg(feature = "full_crypto")] + impl CryptoType for Pair { + type Pair = Pair; + } + + /// An error when deriving a key. + #[cfg(feature = "full_crypto")] + pub enum DeriveError { + /// A soft key was found in the path (and is unsupported). + SoftKeyInPath, + } + + /// Derive a single hard junction. + #[cfg(feature = "full_crypto")] + fn derive_hard_junction(secret_seed: &Seed, cc: &[u8; 32]) -> Seed { + ("Secp256r1", secret_seed, cc).using_encoded(blake2_256) + } + + trait ToBytes33 { + fn to_bytes(&self) -> [u8; 33]; + } + + impl ToBytes33 for p256::PublicKey { + fn to_bytes(&self) -> [u8; 33] { + let encoded_point = EncodedPoint::from(self); + let compressed_point = encoded_point.compress(); + compressed_point.as_bytes().try_into().unwrap() + } + } + + #[cfg(feature = "full_crypto")] + impl TraitPair for Pair { + type Public = Public; + type Seed = Seed; + type Signature = Signature; + type DeriveError = DeriveError; + + // Using default fn generate() + + /// Generate new secure (random) key pair and provide the recovery phrase. + /// + /// You can recover the same key later with `from_phrase`. + fn generate_with_phrase(password: Option<&str>) -> (Pair, String, Seed) { + let mnemonic = Mnemonic::new(MnemonicType::Words12, Language::English); + let phrase = mnemonic.phrase(); + let (pair, seed) = Self::from_phrase(phrase, password) + .expect("All phrases generated by Mnemonic are valid; qed"); + (pair, phrase.to_owned(), seed) + } + + /// Generate key pair from given recovery phrase and password. + fn from_phrase( + phrase: &str, + password: Option<&str>, + ) -> Result<(Pair, Seed), SecretStringError> { + let big_seed = substrate_bip39::seed_from_entropy( + Mnemonic::from_phrase(phrase, Language::English) + .map_err(|_| SecretStringError::InvalidPhrase)? + .entropy(), + password.unwrap_or(""), + ) + .map_err(|_| SecretStringError::InvalidSeed)?; + let mut seed = Seed::default(); + seed.copy_from_slice(&big_seed[0..32]); + Self::from_seed_slice(&big_seed[0..32]).map(|x| (x, seed)) + } + + /// Make a new key pair from secret seed material. + /// + /// You should never need to use this; generate(), generate_with_phrase + fn from_seed(seed: &Seed) -> Pair { + Self::from_seed_slice(&seed[..]).expect("seed has valid length; qed") + } + + /// Make a new key pair from secret seed material. The slice must be 32 bytes long or it + /// will return `None`. + /// + /// You should never need to use this; generate(), generate_with_phrase + fn from_seed_slice(seed_slice: &[u8]) -> Result { + Self::generate_from_seed_bytes(seed_slice).map_err(|_| SecretStringError::InvalidSeed) + } + + /// Derive a child key from a series of given junctions. + fn derive>( + &self, + path: Iter, + _seed: Option, + ) -> Result<(Pair, Option), DeriveError> { + let mut acc = self.seed(); + for j in path { + match j { + DeriveJunction::Soft(_cc) => return Err(DeriveError::SoftKeyInPath), + DeriveJunction::Hard(cc) => acc = derive_hard_junction(&acc, &cc), + } + } + Ok((Self::from_seed(&acc), Some(acc))) + } + + /// Get the public key. + fn public(&self) -> Public { + self.public + } + + /// Sign a message. + fn sign(&self, message: &[u8]) -> Signature { + let key = SigningKey::from(&self.secret); + let p256_signature: recoverable::Signature = key.sign(message); + + Signature::try_from(p256_signature).expect("invalid signature") + } + + /// Verify a signature on a message. Returns true if the signature is good. + fn verify>( + sig: &Self::Signature, + message: M, + pubkey: &Self::Public, + ) -> bool { + sig.verify(message.as_ref(), pubkey) + } + + /// Verify a signature on a message. Returns true if the signature is good. + /// + /// This doesn't use the type system to ensure that `sig` and `pubkey` are the correct + /// size. Use it only if you're coming from byte buffers and need the speed. + fn verify_weak, M: AsRef<[u8]>>(sig: &[u8], message: M, pubkey: P) -> bool { + // TODO: weak version, for now use normal verify + let signature = match Self::Signature::try_from(sig) { + Err(_) => return false, + Ok(sign) => sign, + }; + let public = match Self::Public::try_from(pubkey.as_ref()) { + Err(_) => return false, + Ok(pk) => pk, + }; + + Self::verify(&signature, message, &public) + } + + /// Return a vec filled with raw data. + fn to_raw_vec(&self) -> Vec { + self.seed().to_vec() + } + } + + #[cfg(feature = "full_crypto")] + impl Pair { + /// Get the seed for this key. + pub fn seed(&self) -> Seed { + *self.secret.to_be_bytes().as_ref() + } + + /// Exactly as `from_string` except that if no matches are found then, the the first 32 + /// characters are taken (padded with spaces as necessary) and used as the MiniSecretKey. + pub fn from_legacy_string(s: &str, password_override: Option<&str>) -> Pair { + Self::from_string(s, password_override).unwrap_or_else(|_| { + let mut padded_seed: Seed = [b' '; 32]; + let len = s.len().min(32); + padded_seed[..len].copy_from_slice(&s.as_bytes()[..len]); + Self::from_seed(&padded_seed) + }) + } + } } #[cfg(test)] mod test { - use super::*; - use crate::application_crypto::p256::Pair; - use hex_literal::hex; - use sp_application_crypto::DeriveJunction; - use sp_core::{ - crypto::{Pair as TraitPair, DEV_PHRASE}, - hashing::blake2_256, - }; - use sp_runtime::AccountId32; - - fn build_dummy_pair() -> Pair { - let seed = "Test"; - Pair::from_string(&format!("//{}", seed), None).expect("static values are valid; qed") - } - - #[test] - fn generate_account_id() { - let pair = build_dummy_pair(); - - let account_id: AccountId32 = blake2_256(pair.get_public().as_ref()).into(); - assert_eq!("5CahxeGW24hPXsUTZsiiBgsuBbsQqga8oY6ai4uKMm5X4wym", account_id.to_string()); - } - - #[test] - fn test_account() { - let pair = build_dummy_pair(); - - let payload = hex!("0a000090b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe22070010a5d4e84502000001000000010000003ce9390c8bd3361b348592b2c3008ece6c530e415821abb9759215e8dc83f0490e70b9cbbbcd07a80821fd7dfca9c93ae922688b37a484d5fd68dedcc2cabaa5"); - - let signature = pair.sign(&payload); - assert!(Pair::verify(&signature, &payload, &pair.public())); - } - - #[test] - fn default_phrase_should_be_used() { - assert_eq!( - Pair::from_string("//Alice///password", None).unwrap().public(), - Pair::from_string(&format!("{}//Alice", DEV_PHRASE), Some("password")) - .unwrap() - .public(), - ); - } - - #[test] - fn seed_and_derive_should_work() { - let seed = hex!("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60"); - let pair = Pair::from_seed(&seed); - assert_eq!(pair.seed(), seed); - let path = vec![DeriveJunction::Hard([0u8; 32])]; - let derived = pair.derive(path.into_iter(), None).ok().unwrap(); - assert_eq!( - derived.0.seed(), - hex!("6188237fc80465cd043c58ac7623eaefa9f4db5ce8dee2cd00c6458c5303cf30") - ); - } - - #[test] - fn test_vector_should_work() { - let seed = hex!("f67b03b2c6e4bf86cce50298dbce351b332c3be65ced9f312b6d9ffc3de6b04f"); - let pair = Pair::from_seed(&seed); - let public = pair.public(); - - let public_key_bytes = - hex!("02c156afee1ce52ef83a0dd168c1144eb20008697e6664fa132ba23c128cce8055"); - assert_eq!(public, p256::Public::from_raw(public_key_bytes),); - let message = b"".to_vec(); - - let signature = hex!("696e710fc4516d0a2ba91162777b5f0a4d0e9849a6121a4bae00a0d2df70b5d2ef6e26b0191024872aa22530ed3bef47cd8b0c635e659c79a4cc4a1533013b9c01"); - let signature = p256::Signature(signature); - - assert!(pair.sign(&message[..]) == signature); - assert!(Pair::verify(&signature, &message[..], &public)); - } - - #[test] - fn test_vector_by_string_should_work() { - let pair = Pair::from_string( - "0xf67b03b2c6e4bf86cce50298dbce351b332c3be65ced9f312b6d9ffc3de6b04f", - None, - ) - .unwrap(); - let public = pair.public(); - assert_eq!( - public, - p256::Public::from_raw(hex!( - "02c156afee1ce52ef83a0dd168c1144eb20008697e6664fa132ba23c128cce8055" - )), - ); - let message = b""; - let signature = hex!("696e710fc4516d0a2ba91162777b5f0a4d0e9849a6121a4bae00a0d2df70b5d2ef6e26b0191024872aa22530ed3bef47cd8b0c635e659c79a4cc4a1533013b9c01"); - let signature = p256::Signature(signature); - assert!(pair.sign(&message[..]) == signature); - assert!(Pair::verify(&signature, &message[..], &public)); - } + use super::*; + use crate::application_crypto::p256::Pair; + use hex_literal::hex; + use sp_application_crypto::DeriveJunction; + use sp_core::{ + crypto::{Pair as TraitPair, DEV_PHRASE}, + hashing::blake2_256, + }; + use sp_runtime::AccountId32; + + fn build_dummy_pair() -> Pair { + let seed = "Test"; + Pair::from_string(&format!("//{}", seed), None).expect("static values are valid; qed") + } + + #[test] + fn generate_account_id() { + let pair = build_dummy_pair(); + + let account_id: AccountId32 = blake2_256(pair.get_public().as_ref()).into(); + assert_eq!( + "5CahxeGW24hPXsUTZsiiBgsuBbsQqga8oY6ai4uKMm5X4wym", + account_id.to_string() + ); + } + + #[test] + fn test_account() { + let pair = build_dummy_pair(); + + let payload = hex!("0a000090b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe22070010a5d4e84502000001000000010000003ce9390c8bd3361b348592b2c3008ece6c530e415821abb9759215e8dc83f0490e70b9cbbbcd07a80821fd7dfca9c93ae922688b37a484d5fd68dedcc2cabaa5"); + + let signature = pair.sign(&payload); + assert!(Pair::verify(&signature, &payload, &pair.public())); + } + + #[test] + fn default_phrase_should_be_used() { + assert_eq!( + Pair::from_string("//Alice///password", None) + .unwrap() + .public(), + Pair::from_string(&format!("{}//Alice", DEV_PHRASE), Some("password")) + .unwrap() + .public(), + ); + } + + #[test] + fn seed_and_derive_should_work() { + let seed = hex!("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60"); + let pair = Pair::from_seed(&seed); + assert_eq!(pair.seed(), seed); + let path = vec![DeriveJunction::Hard([0u8; 32])]; + let derived = pair.derive(path.into_iter(), None).ok().unwrap(); + assert_eq!( + derived.0.seed(), + hex!("6188237fc80465cd043c58ac7623eaefa9f4db5ce8dee2cd00c6458c5303cf30") + ); + } + + #[test] + fn test_vector_should_work() { + let seed = hex!("f67b03b2c6e4bf86cce50298dbce351b332c3be65ced9f312b6d9ffc3de6b04f"); + let pair = Pair::from_seed(&seed); + let public = pair.public(); + + let public_key_bytes = + hex!("02c156afee1ce52ef83a0dd168c1144eb20008697e6664fa132ba23c128cce8055"); + assert_eq!(public, p256::Public::from_raw(public_key_bytes),); + let message = b"".to_vec(); + + let signature = hex!("696e710fc4516d0a2ba91162777b5f0a4d0e9849a6121a4bae00a0d2df70b5d2ef6e26b0191024872aa22530ed3bef47cd8b0c635e659c79a4cc4a1533013b9c01"); + let signature = p256::Signature(signature); + + assert!(pair.sign(&message[..]) == signature); + assert!(Pair::verify(&signature, &message[..], &public)); + } + + #[test] + fn test_vector_by_string_should_work() { + let pair = Pair::from_string( + "0xf67b03b2c6e4bf86cce50298dbce351b332c3be65ced9f312b6d9ffc3de6b04f", + None, + ) + .unwrap(); + let public = pair.public(); + assert_eq!( + public, + p256::Public::from_raw(hex!( + "02c156afee1ce52ef83a0dd168c1144eb20008697e6664fa132ba23c128cce8055" + )), + ); + let message = b""; + let signature = hex!("696e710fc4516d0a2ba91162777b5f0a4d0e9849a6121a4bae00a0d2df70b5d2ef6e26b0191024872aa22530ed3bef47cd8b0c635e659c79a4cc4a1533013b9c01"); + let signature = p256::Signature(signature); + assert!(pair.sign(&message[..]) == signature); + assert!(Pair::verify(&signature, &message[..], &public)); + } } diff --git a/p256-crypto/src/multi_signature.rs b/p256-crypto/src/multi_signature.rs index 9f586a2c..39d66134 100644 --- a/p256-crypto/src/multi_signature.rs +++ b/p256-crypto/src/multi_signature.rs @@ -5,8 +5,8 @@ use scale_info::TypeInfo; use serde::{Deserialize, Serialize}; use sp_core::{crypto::UncheckedFrom, ecdsa, ed25519, sr25519, RuntimeDebug, H256}; use sp_runtime::{ - traits::{IdentifyAccount, Lazy, Verify}, - AccountId32, MultiSignature as SPMultiSignature, MultiSigner as SPMultiSigner, + traits::{IdentifyAccount, Lazy, Verify}, + AccountId32, MultiSignature as SPMultiSignature, MultiSigner as SPMultiSigner, }; use crate::application_crypto::p256::{Public, Signature}; @@ -14,245 +14,246 @@ use crate::application_crypto::p256::{Public, Signature}; #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] #[derive(Eq, PartialEq, Clone, Encode, Decode, MaxEncodedLen, RuntimeDebug, TypeInfo)] pub enum MultiSignature { - /// An Ed25519 signature. - Ed25519(ed25519::Signature), - /// An Sr25519 signature. - Sr25519(sr25519::Signature), - /// An ECDSA/SECP256k1 signature. - Ecdsa(ecdsa::Signature), - /// An ECDSA/SECP256r1 signature - P256(Signature), + /// An Ed25519 signature. + Ed25519(ed25519::Signature), + /// An Sr25519 signature. + Sr25519(sr25519::Signature), + /// An ECDSA/SECP256k1 signature. + Ecdsa(ecdsa::Signature), + /// An ECDSA/SECP256r1 signature + P256(Signature), } impl From for MultiSignature { - fn from(x: ed25519::Signature) -> Self { - Self::Ed25519(x) - } + fn from(x: ed25519::Signature) -> Self { + Self::Ed25519(x) + } } impl TryFrom for ed25519::Signature { - type Error = (); - fn try_from(m: MultiSignature) -> Result { - if let MultiSignature::Ed25519(x) = m { - Ok(x) - } else { - Err(()) - } - } + type Error = (); + fn try_from(m: MultiSignature) -> Result { + if let MultiSignature::Ed25519(x) = m { + Ok(x) + } else { + Err(()) + } + } } impl From for MultiSignature { - fn from(x: sr25519::Signature) -> Self { - Self::Sr25519(x) - } + fn from(x: sr25519::Signature) -> Self { + Self::Sr25519(x) + } } impl TryFrom for sr25519::Signature { - type Error = (); - fn try_from(m: MultiSignature) -> Result { - if let MultiSignature::Sr25519(x) = m { - Ok(x) - } else { - Err(()) - } - } + type Error = (); + fn try_from(m: MultiSignature) -> Result { + if let MultiSignature::Sr25519(x) = m { + Ok(x) + } else { + Err(()) + } + } } impl From for MultiSignature { - fn from(x: ecdsa::Signature) -> Self { - Self::Ecdsa(x) - } + fn from(x: ecdsa::Signature) -> Self { + Self::Ecdsa(x) + } } impl TryFrom for ecdsa::Signature { - type Error = (); - fn try_from(m: MultiSignature) -> Result { - if let MultiSignature::Ecdsa(x) = m { - Ok(x) - } else { - Err(()) - } - } + type Error = (); + fn try_from(m: MultiSignature) -> Result { + if let MultiSignature::Ecdsa(x) = m { + Ok(x) + } else { + Err(()) + } + } } impl From for MultiSignature { - fn from(x: Signature) -> Self { - Self::P256(x) - } + fn from(x: Signature) -> Self { + Self::P256(x) + } } impl TryFrom for Signature { - type Error = (); - fn try_from(m: MultiSignature) -> Result { - if let MultiSignature::P256(x) = m { - Ok(x) - } else { - Err(()) - } - } + type Error = (); + fn try_from(m: MultiSignature) -> Result { + if let MultiSignature::P256(x) = m { + Ok(x) + } else { + Err(()) + } + } } #[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Encode, Decode, RuntimeDebug, TypeInfo)] #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] pub enum MultiSigner { - /// An Ed25519 identity. - Ed25519(ed25519::Public), - /// An Sr25519 identity. - Sr25519(sr25519::Public), - /// An SECP256k1/ECDSA identity (actually, the Blake2 hash of the compressed pub key). - Ecdsa(ecdsa::Public), - /// An P256/ECDSA identity. - P256(Public), + /// An Ed25519 identity. + Ed25519(ed25519::Public), + /// An Sr25519 identity. + Sr25519(sr25519::Public), + /// An SECP256k1/ECDSA identity (actually, the Blake2 hash of the compressed pub key). + Ecdsa(ecdsa::Public), + /// An P256/ECDSA identity. + P256(Public), } /// NOTE: This implementations is required by `SimpleAddressDeterminer`, /// we convert the hash into some AccountId, it's fine to use any scheme. impl> UncheckedFrom for MultiSigner { - fn unchecked_from(x: T) -> Self { - ed25519::Public::unchecked_from(x.into()).into() - } + fn unchecked_from(x: T) -> Self { + ed25519::Public::unchecked_from(x.into()).into() + } } impl AsRef<[u8]> for MultiSigner { - fn as_ref(&self) -> &[u8] { - match self { - Self::Ed25519(ref who) => who.as_ref(), - Self::Sr25519(ref who) => who.as_ref(), - Self::Ecdsa(ref who) => who.as_ref(), - Self::P256(ref who) => who.as_ref(), - } - } + fn as_ref(&self) -> &[u8] { + match self { + Self::Ed25519(ref who) => who.as_ref(), + Self::Sr25519(ref who) => who.as_ref(), + Self::Ecdsa(ref who) => who.as_ref(), + Self::P256(ref who) => who.as_ref(), + } + } } impl IdentifyAccount for MultiSigner { - type AccountId = AccountId32; + type AccountId = AccountId32; - fn into_account(self) -> AccountId32 { - match self { - Self::Ed25519(who) => { - let msigner: SPMultiSigner = who.into(); - msigner.into_account() - }, - Self::Sr25519(who) => { - let msigner: SPMultiSigner = who.into(); - msigner.into_account() - }, - Self::Ecdsa(who) => { - let msigner: SPMultiSigner = who.into(); - msigner.into_account() - }, - Self::P256(who) => sp_io::hashing::blake2_256(who.as_ref()).into(), - } - } + fn into_account(self) -> AccountId32 { + match self { + Self::Ed25519(who) => { + let msigner: SPMultiSigner = who.into(); + msigner.into_account() + } + Self::Sr25519(who) => { + let msigner: SPMultiSigner = who.into(); + msigner.into_account() + } + Self::Ecdsa(who) => { + let msigner: SPMultiSigner = who.into(); + msigner.into_account() + } + Self::P256(who) => sp_io::hashing::blake2_256(who.as_ref()).into(), + } + } } impl From for MultiSigner { - fn from(x: ed25519::Public) -> Self { - Self::Ed25519(x) - } + fn from(x: ed25519::Public) -> Self { + Self::Ed25519(x) + } } impl TryFrom for ed25519::Public { - type Error = (); - fn try_from(m: MultiSigner) -> Result { - if let MultiSigner::Ed25519(x) = m { - Ok(x) - } else { - Err(()) - } - } + type Error = (); + fn try_from(m: MultiSigner) -> Result { + if let MultiSigner::Ed25519(x) = m { + Ok(x) + } else { + Err(()) + } + } } impl From for MultiSigner { - fn from(x: sr25519::Public) -> Self { - Self::Sr25519(x) - } + fn from(x: sr25519::Public) -> Self { + Self::Sr25519(x) + } } impl TryFrom for sr25519::Public { - type Error = (); - fn try_from(m: MultiSigner) -> Result { - if let MultiSigner::Sr25519(x) = m { - Ok(x) - } else { - Err(()) - } - } + type Error = (); + fn try_from(m: MultiSigner) -> Result { + if let MultiSigner::Sr25519(x) = m { + Ok(x) + } else { + Err(()) + } + } } impl From for MultiSigner { - fn from(x: ecdsa::Public) -> Self { - Self::Ecdsa(x) - } + fn from(x: ecdsa::Public) -> Self { + Self::Ecdsa(x) + } } impl TryFrom for ecdsa::Public { - type Error = (); - fn try_from(m: MultiSigner) -> Result { - if let MultiSigner::Ecdsa(x) = m { - Ok(x) - } else { - Err(()) - } - } + type Error = (); + fn try_from(m: MultiSigner) -> Result { + if let MultiSigner::Ecdsa(x) = m { + Ok(x) + } else { + Err(()) + } + } } impl From for MultiSigner { - fn from(x: Public) -> Self { - Self::P256(x) - } + fn from(x: Public) -> Self { + Self::P256(x) + } } impl TryFrom for Public { - type Error = (); - fn try_from(m: MultiSigner) -> Result { - if let MultiSigner::P256(x) = m { - Ok(x) - } else { - Err(()) - } - } + type Error = (); + fn try_from(m: MultiSigner) -> Result { + if let MultiSigner::P256(x) = m { + Ok(x) + } else { + Err(()) + } + } } #[cfg(feature = "std")] impl std::fmt::Display for MultiSigner { - fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { - match self { - Self::Ed25519(ref who) => write!(fmt, "ed25519: {}", who), - Self::Sr25519(ref who) => write!(fmt, "sr25519: {}", who), - Self::Ecdsa(ref who) => write!(fmt, "ecdsa: {}", who), - Self::P256(ref who) => write!(fmt, "p256: {}", who), - } - } + fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + Self::Ed25519(ref who) => write!(fmt, "ed25519: {}", who), + Self::Sr25519(ref who) => write!(fmt, "sr25519: {}", who), + Self::Ecdsa(ref who) => write!(fmt, "ecdsa: {}", who), + Self::P256(ref who) => write!(fmt, "p256: {}", who), + } + } } impl Verify for MultiSignature { - type Signer = MultiSigner; - fn verify>(&self, mut msg: L, signer: &AccountId32) -> bool { - match (self, signer) { - (Self::Ed25519(multi_sig), _) => { - let msig: SPMultiSignature = multi_sig.clone().into(); - msig.verify(msg, signer) - }, - (Self::Sr25519(multi_sig), _) => { - let msig: SPMultiSignature = multi_sig.clone().into(); - msig.verify(msg, signer) - }, - (Self::Ecdsa(multi_sig), _) => { - let msig: SPMultiSignature = multi_sig.clone().into(); - msig.verify(msg, signer) - }, - (Self::P256(ref sig), who) => { - match p256::ecdsa::recoverable::Signature::try_from(sig.as_ref()) - .unwrap() - .recover_verify_key(&msg.get()) - { - Ok(pubkey) => - &sp_io::hashing::blake2_256(&pubkey.to_bytes().as_slice()) == - >::as_ref(who), - _ => false, - } - }, - } - } + type Signer = MultiSigner; + fn verify>(&self, mut msg: L, signer: &AccountId32) -> bool { + match (self, signer) { + (Self::Ed25519(multi_sig), _) => { + let msig: SPMultiSignature = multi_sig.clone().into(); + msig.verify(msg, signer) + } + (Self::Sr25519(multi_sig), _) => { + let msig: SPMultiSignature = multi_sig.clone().into(); + msig.verify(msg, signer) + } + (Self::Ecdsa(multi_sig), _) => { + let msig: SPMultiSignature = multi_sig.clone().into(); + msig.verify(msg, signer) + } + (Self::P256(ref sig), who) => { + match p256::ecdsa::recoverable::Signature::try_from(sig.as_ref()) + .unwrap() + .recover_verify_key(&msg.get()) + { + Ok(pubkey) => { + &sp_io::hashing::blake2_256(&pubkey.to_bytes().as_slice()) + == >::as_ref(who) + } + _ => false, + } + } + } + } } diff --git a/pallets/acurast/Cargo.toml b/pallets/acurast/Cargo.toml index 6e30c2f4..286db6c4 100644 --- a/pallets/acurast/Cargo.toml +++ b/pallets/acurast/Cargo.toml @@ -21,7 +21,17 @@ frame-support = { git = "https://github.com/paritytech/substrate", default-featu frame-system = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.26" } sp-std = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.26" } sp-io = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.26" } +sp-runtime = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.26" } pallet-timestamp = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.26" } +pallet-assets = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.26" } + +# Cumulus +parachains-common = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.26", default-features = false } + +# Polkadot +xcm = { git = "https://github.com/paritytech/polkadot", default-features = false, branch = "release-v0.9.26"} +xcm-executor = { git = "https://github.com/paritytech/polkadot", default-features = false, branch = "release-v0.9.26"} +xcm-builder = { git = "https://github.com/paritytech/polkadot", default-features = false, branch = "release-v0.9.26"} # Attestation asn1 = { version = "0.11.0", default-features = false, features = ["derive"] } @@ -54,8 +64,14 @@ std = [ "asn1/std", "p256/std", "sha2/std", + "p384/std", "num-bigint/std", "ecdsa-vendored/std", "sp-io/std", + "pallet-assets/std", + "parachains-common/std", + "xcm/std", + "xcm-executor/std", + "xcm-builder/std", ] try-runtime = [ "frame-support/try-runtime" ] diff --git a/pallets/acurast/README.md b/pallets/acurast/README.md index 791081b2..dafd9e60 100644 --- a/pallets/acurast/README.md +++ b/pallets/acurast/README.md @@ -29,9 +29,13 @@ Allows the de-registration of a job. Allows to update the list of allowed sources for a previously registered job. +### updateJobAssignments + +Allows to assign a registered job to a processor, so that it can later call `fulfill`. + ### fulfill -Allows to post the fulfillment of a registered job. The fulfillment structure consists of: +Allows to post the fulfillment of a registered job. The `fulfill` call will fail if the job was not previously assigned to the origin. The fulfillment structure consists of: - The ipfs url of the `script` executed. - The `payload` bytes representing the output of the `script`. @@ -81,7 +85,6 @@ impl pallet_acurast::FulfillmentRouter for AcurastRouter { parameter_types! { pub const MaxAllowedSources: u16 = 100; - pub AllowedRevocationListUpdate: Vec = vec![]; } impl pallet_acurast::Config for Runtime { @@ -89,7 +92,8 @@ impl pallet_acurast::Config for Runtime { type RegistrationExtra = AcurastRegistrationExtra; type FulfillmentRouter = AcurastRouter; type MaxAllowedSources = MaxAllowedSources; - type AllowedRevocationListUpdate = AllowedRevocationListUpdate; + type RevocationListUpdateBarrier = (); + type JobAssignmentUpdateBarrier = (); } // Create the runtime by composing the FRAME pallets that were previously configured. diff --git a/pallets/acurast/src/lib.rs b/pallets/acurast/src/lib.rs index b34ca523..48642650 100644 --- a/pallets/acurast/src/lib.rs +++ b/pallets/acurast/src/lib.rs @@ -6,10 +6,13 @@ mod mock; mod tests; mod attestation; +pub mod payments; mod types; mod utils; +pub mod xcm_adapters; pub use pallet::*; +pub use payments::*; pub use types::*; #[frame_support::pallet] @@ -17,11 +20,12 @@ pub mod pallet { use frame_support::{ dispatch::DispatchResultWithPostInfo, ensure, pallet_prelude::*, - sp_runtime::traits::StaticLookup, Blake2_128Concat, + sp_runtime::traits::StaticLookup, Blake2_128Concat, PalletId, }; use frame_system::pallet_prelude::*; use sp_std::prelude::*; + use crate::payments::*; use crate::types::*; use crate::utils::*; @@ -36,8 +40,44 @@ pub mod pallet { ) -> DispatchResultWithPostInfo; } + pub trait RevocationListUpdateBarrier { + fn can_update_revocation_list( + origin: &T::AccountId, + updates: &Vec, + ) -> bool; + } + + impl RevocationListUpdateBarrier for () { + fn can_update_revocation_list( + _origin: &T::AccountId, + _updates: &Vec, + ) -> bool { + false + } + } + + pub trait JobAssignmentUpdateBarrier { + fn can_update_assigned_jobs( + origin: &T::AccountId, + updates: &Vec>, + ) -> bool; + } + + impl JobAssignmentUpdateBarrier for () { + fn can_update_assigned_jobs( + _origin: &T::AccountId, + _updates: &Vec>, + ) -> bool { + false + } + } + #[pallet::config] - pub trait Config: frame_system::Config + pallet_timestamp::Config { + pub trait Config: + frame_system::Config + + pallet_timestamp::Config + + pallet_assets::Config + { type Event: From> + IsType<::Event>; /// Extra structure to include in the registration of a job. type RegistrationExtra: Parameter + Member + MaxEncodedLen; @@ -46,9 +86,15 @@ pub mod pallet { /// The max length of the allowed sources list for a registration. #[pallet::constant] type MaxAllowedSources: Get; - /// AccountIDs that are allowed to call update_certificate_revocation_list. + // Logic for locking and paying tokens for job execution + type AssetTransactor: LockAndPayAsset; + /// The ID for this pallet #[pallet::constant] - type AllowedRevocationListUpdate: Get>; + type PalletId: Get; + /// Barrier for the update_certificate_revocation_list extrinsic call. + type RevocationListUpdateBarrier: RevocationListUpdateBarrier; + /// Barrier for update_job_assignments extrinsic call. + type JobAssignmentUpdateBarrier: JobAssignmentUpdateBarrier; } #[pallet::pallet] @@ -80,6 +126,12 @@ pub mod pallet { pub type StoredRevokedCertificate = StorageMap<_, Blake2_128Concat, SerialNumber, ()>; + /// Job assignments. + #[pallet::storage] + #[pallet::getter(fn stored_job_assignment)] + pub type StoredJobAssignment = + StorageMap<_, Blake2_128Concat, T::AccountId, Vec>>; + #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { @@ -107,6 +159,8 @@ pub mod pallet { AttestationStored(Attestation, T::AccountId), /// The certificate revocation list has been updated. [who, updates] CertificateRecovationListUpdated(T::AccountId, Vec), + /// The job assignemts have been updated. [who, updates] + JobAssignmentUpdate(T::AccountId, Vec>), } #[pallet::error] @@ -153,6 +207,12 @@ pub mod pallet { UnsupportedAttestationPublicKeyType, /// The submitted attestation public key does not match the source. AttestationPublicKeyDoesNotMatchSource, + /// Job assignment update not allowed. + JobAssignmentUpdateNotAllowed, + /// Payment wasn't recognized as valid. Probably didn't come from statemint assets pallet + InvalidPayment, + /// Failed to retrieve funds from pallet account to pay processor. SEVERE error + FailedToPay, } #[pallet::hooks] @@ -184,6 +244,14 @@ pub mod pallet { Error::::TooManyAllowedSources ); } + + if let Err(()) = T::AssetTransactor::lock_asset( + registration.reward.clone(), + T::Lookup::unlookup(who.clone()), + ) { + return Err(Error::::InvalidPayment.into()); + } + >::insert( who.clone(), (®istration).script.clone(), @@ -214,7 +282,7 @@ pub mod pallet { .ok_or(Error::::JobRegistrationNotFound)?; let mut current_allowed_sources = - (®istration).allowed_sources.clone().unwrap_or(vec![]); + (®istration).allowed_sources.clone().unwrap_or_default(); for update in &updates { let position = current_allowed_sources .iter() @@ -244,10 +312,11 @@ pub mod pallet { who.clone(), script.clone(), JobRegistration { - script, + script: script.clone(), allowed_sources, extra: (®istration).extra.clone(), allow_only_verified_sources: (®istration).allow_only_verified_sources, + reward: (®istration).reward.clone(), }, ); @@ -256,8 +325,36 @@ pub mod pallet { Ok(().into()) } + /// Assigns jobs to [AccountId]s. Those accounts can then later call `fulfill` for those jobs. + #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(2, 1))] + pub fn update_job_assignments( + origin: OriginFor, + updates: Vec>, + ) -> DispatchResultWithPostInfo { + let who = ensure_signed(origin)?; + if !T::JobAssignmentUpdateBarrier::can_update_assigned_jobs(&who, &updates) { + return Err(Error::::JobAssignmentUpdateNotAllowed)?; + } + for update in &updates { + let job_registration = + >::get(&update.job_id.0, &update.job_id.1) + .ok_or(Error::::JobRegistrationNotFound)?; + + ensure_source_allowed::(&update.assignee, &job_registration)?; + + let mut assignments = + >::get(&update.assignee).unwrap_or_default(); + if !assignments.contains(&update.job_id) { + assignments.push(update.job_id.clone()); + } + >::set(&update.assignee, Some(assignments)); + } + Self::deposit_event(Event::JobAssignmentUpdate(who, updates)); + Ok(().into()) + } + /// Fulfills a previously registered job. - #[pallet::weight(10_000 + T::DbWeight::get().reads(7))] + #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(7, 1))] pub fn fulfill( origin: OriginFor, fulfillment: Fulfillment, @@ -266,26 +363,59 @@ pub mod pallet { let who = ensure_signed(origin.clone())?; let requester = T::Lookup::lookup(requester)?; - let registration = - >::get(requester.clone(), (&fulfillment).script.clone()) - .ok_or(Error::::JobRegistrationNotFound)?; + // find assignment + let job_id: JobId = (requester, fulfillment.script.clone()); + let mut assigned_jobs = >::get(&who).unwrap_or_default(); + let job_index = assigned_jobs + .iter() + .position(|assigned_job_id| assigned_job_id == &job_id) + .ok_or(Error::::FulfillSourceNotAllowed)?; - ensure_source_allowed::(&who, ®istration)?; + // find registration + if let Some(registration) = + >::get(&job_id.0, &fulfillment.script) + { + let allowed_result = ensure_source_allowed(&who, ®istration); + if let Err(Error::::FulfillSourceNotAllowed) = allowed_result { + assigned_jobs.remove(job_index); + >::set(&who, Some(assigned_jobs)); + return Err(Error::::FulfillSourceNotAllowed)?; + } + allowed_result?; - let info = T::FulfillmentRouter::received_fulfillment( - origin, - who.clone(), - fulfillment.clone(), - registration.clone(), - requester.clone(), - )?; - Self::deposit_event(Event::ReceivedFulfillment( - who, - fulfillment, - registration, - requester, - )); - Ok(info) + if let Err(()) = T::AssetTransactor::pay_asset( + registration.reward.clone(), + T::Lookup::unlookup(who.clone()), + ) { + return Err(Error::::FailedToPay.into()); + }; + + // route fulfillment + let info = T::FulfillmentRouter::received_fulfillment( + origin, + who.clone(), + fulfillment.clone(), + registration.clone(), + job_id.0, + )?; + + // removed fulfilled job from assigned jobs + let job_id = assigned_jobs.remove(job_index); + >::set(&who, Some(assigned_jobs)); + + Self::deposit_event(Event::ReceivedFulfillment( + who, + fulfillment, + registration, + job_id.0, + )); + Ok(info) + } else { + // registration was removed, job is not available anymore, remove it from assignment + assigned_jobs.remove(job_index); + >::set(&who, Some(assigned_jobs)); + Err(Error::::JobRegistrationNotFound)? + } } /// Submits an attestation given a valid certificate chain. @@ -311,7 +441,7 @@ pub mod pallet { ensure_not_expired::(&attestation)?; ensure_not_revoked::(&attestation)?; - >::insert(who.clone(), attestation.clone()); + >::insert(&who, attestation.clone()); Self::deposit_event(Event::AttestationStored(attestation, who)); Ok(().into()) } @@ -322,16 +452,13 @@ pub mod pallet { updates: Vec, ) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; - if !T::AllowedRevocationListUpdate::get().contains(&who) { + if !T::RevocationListUpdateBarrier::can_update_revocation_list(&who, &updates) { return Err(Error::::CertificateRevocationListUpdateNotAllowed)?; } for update in &updates { match &update.operation { ListUpdateOperation::Add => { - >::insert( - update.cert_serial_number.clone(), - (), - ); + >::insert(&update.cert_serial_number, ()); } ListUpdateOperation::Remove => { >::remove(&update.cert_serial_number); diff --git a/pallets/acurast/src/mock.rs b/pallets/acurast/src/mock.rs index 766cf8fd..53bd7b0b 100644 --- a/pallets/acurast/src/mock.rs +++ b/pallets/acurast/src/mock.rs @@ -1,8 +1,13 @@ +use frame_support::{traits::ConstU32, PalletId}; use hex_literal::hex; use sp_io; use sp_runtime::{testing::Header, traits::IdentityLookup, AccountId32}; +use xcm::v2::{AssetId, Fungibility, Junctions, MultiAsset, MultiLocation}; -use crate::{AttestationChain, Fulfillment, JobRegistration, Script, SerialNumber}; +use crate::{ + AttestationChain, Fulfillment, JobAssignmentUpdate, JobAssignmentUpdateBarrier, + JobRegistration, LockAndPayAsset, RevocationListUpdateBarrier, Script, SerialNumber, +}; type AccountId = AccountId32; type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; @@ -17,6 +22,7 @@ frame_support::construct_runtime!( System: frame_system::{Pallet, Call, Config, Storage, Event}, Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent}, Acurast: crate::{Pallet, Call, Storage, Event}, + Assets: pallet_assets, } ); @@ -51,7 +57,9 @@ frame_support::parameter_types! { pub BlockWeights: frame_system::limits::BlockWeights = frame_system::limits::BlockWeights::simple_max(1024); pub const MinimumPeriod: u64 = 6000; pub AllowedRevocationListUpdate: Vec = vec![alice_account_id()]; + pub AllowedJobAssignmentUpdate: Vec = vec![bob_account_id()]; pub static ExistentialDeposit: u64 = 0; + pub const TestPalletId: PalletId = PalletId(*b"testpid1"); } impl pallet_timestamp::Config for Test { @@ -61,12 +69,56 @@ impl pallet_timestamp::Config for Test { type WeightInfo = (); } +pub type Balance = u32; +pub const UNIT: Balance = 1_000_000; +pub const MICROUNIT: Balance = 1; + +impl pallet_assets::Config for Test { + type Event = Event; + type Balance = Balance; + type AssetId = u32; + type Currency = (); + type ForceOrigin = frame_system::EnsureRoot; + type AssetDeposit = ConstU32<0>; + type AssetAccountDeposit = ConstU32<0>; + type MetadataDepositBase = ConstU32<{ UNIT }>; + type MetadataDepositPerByte = ConstU32<{ 10 * MICROUNIT }>; + type ApprovalDeposit = ConstU32<{ 10 * MICROUNIT }>; + type StringLimit = ConstU32<50>; + type Freezer = (); + type Extra = (); + type WeightInfo = (); +} + impl crate::Config for Test { type Event = Event; type RegistrationExtra = (); type FulfillmentRouter = Router; type MaxAllowedSources = frame_support::traits::ConstU16<4>; - type AllowedRevocationListUpdate = AllowedRevocationListUpdate; + type RevocationListUpdateBarrier = Barrier; + type JobAssignmentUpdateBarrier = Barrier; + type AssetTransactor = TestTransactor; + type PalletId = TestPalletId; +} + +pub struct Barrier; + +impl RevocationListUpdateBarrier for Barrier { + fn can_update_revocation_list( + origin: &::AccountId, + _updates: &Vec, + ) -> bool { + AllowedRevocationListUpdate::get().contains(origin) + } +} + +impl JobAssignmentUpdateBarrier for Barrier { + fn can_update_assigned_jobs( + origin: &::AccountId, + _updates: &Vec::AccountId>>, + ) -> bool { + AllowedJobAssignmentUpdate::get().contains(origin) + } } pub struct Router; @@ -86,6 +138,24 @@ impl crate::FulfillmentRouter for Router { } } +pub struct TestTransactor; + +impl LockAndPayAsset for TestTransactor { + fn lock_asset( + _asset: MultiAsset, + _owner: <::Lookup as sp_runtime::traits::StaticLookup>::Source, + ) -> Result<(), ()> { + Ok(()) + } + + fn pay_asset( + _asset: MultiAsset, + _target: <::Lookup as sp_runtime::traits::StaticLookup>::Source, + ) -> Result<(), ()> { + Ok(()) + } +} + pub struct ExtBuilder; impl ExtBuilder { @@ -134,6 +204,16 @@ pub fn invalid_script_2() -> Script { bytes.try_into().unwrap() } +fn multi_asset() -> MultiAsset { + MultiAsset { + id: AssetId::Concrete(MultiLocation { + parents: 0, + interior: Junctions::Here, + }), + fun: Fungibility::Fungible(10), + } +} + pub fn job_registration( allowed_sources: Option>, allow_only_verified_sources: bool, @@ -143,15 +223,31 @@ pub fn job_registration( allowed_sources, allow_only_verified_sources, extra: (), + reward: multi_asset(), } } +pub fn job_assignment_update_for( + registration: &JobRegistration, + requester: Option, +) -> Vec> { + vec![JobAssignmentUpdate { + operation: crate::ListUpdateOperation::Add, + assignee: processor_account_id(), + job_id: ( + requester.unwrap_or(alice_account_id()), + registration.script.clone(), + ), + }] +} + pub fn invalid_job_registration_1() -> JobRegistration { JobRegistration { script: invalid_script_1(), allowed_sources: None, allow_only_verified_sources: false, extra: (), + reward: multi_asset(), } } @@ -161,6 +257,7 @@ pub fn invalid_job_registration_2() -> JobRegistration { allowed_sources: None, allow_only_verified_sources: false, extra: (), + reward: multi_asset(), } } diff --git a/pallets/acurast/src/payments.rs b/pallets/acurast/src/payments.rs new file mode 100644 index 00000000..3bac712a --- /dev/null +++ b/pallets/acurast/src/payments.rs @@ -0,0 +1,67 @@ +use super::xcm_adapters::get_statemint_asset; +use super::Config; +use frame_support::dispatch::RawOrigin; +use sp_runtime::traits::{AccountIdConversion, Get, StaticLookup}; +use xcm::latest::prelude::*; + +pub trait LockAndPayAsset { + fn lock_asset(asset: MultiAsset, owner: ::Source) -> Result<(), ()>; + + fn pay_asset(asset: MultiAsset, target: ::Source) -> Result<(), ()>; +} + +pub struct StatemintAssetTransactor; +impl LockAndPayAsset for StatemintAssetTransactor +where + T::AssetId: TryFrom, + T::Balance: TryFrom, +{ + fn lock_asset(asset: MultiAsset, owner: ::Source) -> Result<(), ()> { + let pallet_account: T::AccountId = T::PalletId::get().into_account_truncating(); + let raw_origin = RawOrigin::::Signed(pallet_account.clone()); + let pallet_origin: T::Origin = raw_origin.into(); + + let (id, amount) = get_statemint_asset(&asset).map_err(|_| ())?; + let (id, amount): (T::AssetId, T::Balance) = match (id.try_into(), amount.try_into()) { + (Ok(id), Ok(amount)) => (id, amount), + _ => return Err(()), + }; + + // transfer funds from caller to pallet account for holding until fulfill is called + // this is a privileged operation, hence the force_transfer call. + // we could do an approve_transfer first, but this would require the assets pallet being + // public which we can't do at the moment due to our statemint assets 1 to 1 integration + let extrinsic_call = pallet_assets::Pallet::::force_transfer( + pallet_origin, + id, + owner, + T::Lookup::unlookup(pallet_account), + amount, + ); + + match extrinsic_call { + Ok(_) => Ok(()), + Err(_e) => Err(()), + } + } + + fn pay_asset(asset: MultiAsset, target: ::Source) -> Result<(), ()> { + let pallet_account: T::AccountId = T::PalletId::get().into_account_truncating(); + let raw_origin = RawOrigin::::Signed(pallet_account); + let pallet_origin: T::Origin = raw_origin.into(); + + let (id, amount) = get_statemint_asset(&asset).map_err(|_| ())?; + let (id, amount): (T::AssetId, T::Balance) = match (id.try_into(), amount.try_into()) { + (Ok(id), Ok(amount)) => (id, amount), + _ => return Err(()), + }; + + let extrinsic_call = + pallet_assets::Pallet::::transfer(pallet_origin, id, target, amount); + + match extrinsic_call { + Ok(_) => Ok(()), + Err(_) => Err(()), + } + } +} diff --git a/pallets/acurast/src/tests.rs b/pallets/acurast/src/tests.rs index e15d7de3..8e2d5d6c 100644 --- a/pallets/acurast/src/tests.rs +++ b/pallets/acurast/src/tests.rs @@ -254,23 +254,24 @@ fn test_update_allowed_sources_failure() { } #[test] -fn test_fulfill() { +fn test_assign_job() { let registration = job_registration(None, false); - let fulfillment = Fulfillment { - script: registration.script.clone(), - payload: hex!("00").to_vec(), - }; + let updates = job_assignment_update_for(®istration, None); ExtBuilder::default().build().execute_with(|| { assert_ok!(Acurast::register( Origin::signed(alice_account_id()).into(), registration.clone(), )); - assert_ok!(Acurast::fulfill( - Origin::signed(bob_account_id()).into(), - fulfillment.clone(), - alice_account_id() + assert_ok!(Acurast::update_job_assignments( + Origin::signed(bob_account_id()), + updates.clone(), )); + assert_eq!( + Some(vec![(alice_account_id(), registration.script.clone())]), + Acurast::stored_job_assignment(processor_account_id()) + ); + assert_eq!( events(), [ @@ -278,55 +279,51 @@ fn test_fulfill() { registration.clone(), alice_account_id() )), - Event::Acurast(crate::Event::ReceivedFulfillment( - bob_account_id(), - fulfillment, - registration, - alice_account_id() - )), + Event::Acurast(crate::Event::JobAssignmentUpdate(bob_account_id(), updates)), ] ); }); } #[test] -fn test_fulfill_failure_1() { - let fulfillment = Fulfillment { - script: script(), - payload: hex!("00").to_vec(), - }; +fn test_assign_job_failure_1() { + let registration = job_registration(None, true); + let updates = job_assignment_update_for(®istration, None); ExtBuilder::default().build().execute_with(|| { + assert_ok!(Acurast::register( + Origin::signed(alice_account_id()).into(), + registration.clone(), + )); assert_err!( - Acurast::fulfill( - Origin::signed(alice_account_id()).into(), - fulfillment.clone(), - bob_account_id() + Acurast::update_job_assignments( + Origin::signed(bob_account_id()).into(), + updates.clone() ), - Error::::JobRegistrationNotFound + Error::::FulfillSourceNotVerified + ); + assert_eq!( + events(), + [Event::Acurast(crate::Event::JobRegistrationStored( + registration.clone(), + alice_account_id() + )),] ); - - assert_eq!(events(), []); }); } #[test] -fn test_fulfill_failure_2() { - let registration = job_registration(None, true); - let fulfillment = fulfillment_for(®istration); +fn test_assign_job_failure_2() { + let registration = job_registration(Some(vec![charlie_account_id()]), false); + let updates = job_assignment_update_for(®istration, None); ExtBuilder::default().build().execute_with(|| { assert_ok!(Acurast::register( Origin::signed(alice_account_id()).into(), registration.clone(), )); assert_err!( - Acurast::fulfill( - Origin::signed(bob_account_id()).into(), - fulfillment.clone(), - alice_account_id() - ), - Error::::FulfillSourceNotVerified + Acurast::update_job_assignments(Origin::signed(bob_account_id().into()), updates), + Error::::FulfillSourceNotAllowed ); - assert_eq!( events(), [Event::Acurast(crate::Event::JobRegistrationStored( @@ -338,29 +335,92 @@ fn test_fulfill_failure_2() { } #[test] -fn test_fulfill_failure_3() { - let registration = job_registration(Some(vec![charlie_account_id()]), false); - let fulfillment = fulfillment_for(®istration); +fn test_fulfill() { + let registration = job_registration(None, false); + let fulfillment = Fulfillment { + script: registration.script.clone(), + payload: hex!("00").to_vec(), + }; + let updates = job_assignment_update_for(®istration, None); ExtBuilder::default().build().execute_with(|| { assert_ok!(Acurast::register( Origin::signed(alice_account_id()).into(), registration.clone(), )); + assert_ok!(Acurast::update_job_assignments( + Origin::signed(bob_account_id()), + updates.clone(), + )); + assert_ok!(Acurast::fulfill( + Origin::signed(processor_account_id()).into(), + fulfillment.clone(), + alice_account_id() + )); + assert_eq!( + events(), + [ + Event::Acurast(crate::Event::JobRegistrationStored( + registration.clone(), + alice_account_id() + )), + Event::Acurast(crate::Event::JobAssignmentUpdate(bob_account_id(), updates)), + Event::Acurast(crate::Event::ReceivedFulfillment( + processor_account_id(), + fulfillment, + registration, + alice_account_id() + )), + ] + ); + }); +} + +#[test] +fn test_fulfill_failure_1() { + let registration = job_registration(None, false); + let fulfillment = Fulfillment { + script: registration.script.clone(), + payload: hex!("00").to_vec(), + }; + let updates = job_assignment_update_for(®istration, None); + ExtBuilder::default().build().execute_with(|| { + assert_ok!(Acurast::register( + Origin::signed(alice_account_id()).into(), + registration.clone(), + )); + assert_ok!(Acurast::update_job_assignments( + Origin::signed(bob_account_id()), + updates.clone(), + )); + assert_ok!(Acurast::deregister( + Origin::signed(alice_account_id()).into(), + registration.script.clone(), + )); assert_err!( Acurast::fulfill( - Origin::signed(bob_account_id()).into(), + Origin::signed(processor_account_id()).into(), fulfillment.clone(), alice_account_id() ), - Error::::FulfillSourceNotAllowed + Error::::JobRegistrationNotFound + ); + assert_eq!( + Some(vec![]), + Acurast::stored_job_assignment(processor_account_id()) ); - assert_eq!( events(), - [Event::Acurast(crate::Event::JobRegistrationStored( - registration.clone(), - alice_account_id() - ))] + [ + Event::Acurast(crate::Event::JobRegistrationStored( + registration.clone(), + alice_account_id() + )), + Event::Acurast(crate::Event::JobAssignmentUpdate(bob_account_id(), updates)), + Event::Acurast(crate::Event::JobRegistrationRemoved( + registration.script, + alice_account_id() + )), + ], ); }); } @@ -399,7 +459,7 @@ fn test_submit_attestation_register_fulfill() { let chain = attestation_chain(); let registration = job_registration(None, true); let fulfillment = fulfillment_for(®istration); - + let updates = job_assignment_update_for(®istration, Some(bob_account_id())); _ = Timestamp::set(Origin::none(), 1657363915001); assert_ok!(Acurast::submit_attestation( Origin::signed(processor_account_id()).into(), @@ -409,6 +469,10 @@ fn test_submit_attestation_register_fulfill() { Origin::signed(bob_account_id()).into(), registration.clone() )); + assert_ok!(Acurast::update_job_assignments( + Origin::signed(bob_account_id().into()), + updates.clone() + )); assert_ok!(Acurast::fulfill( Origin::signed(processor_account_id()), fulfillment.clone(), @@ -429,6 +493,7 @@ fn test_submit_attestation_register_fulfill() { registration.clone(), bob_account_id() )), + Event::Acurast(crate::Event::JobAssignmentUpdate(bob_account_id(), updates)), Event::Acurast(crate::Event::ReceivedFulfillment( processor_account_id(), fulfillment, @@ -620,20 +685,25 @@ fn test_update_revocation_list_fulfill() { }]; let chain = attestation_chain(); let registration = job_registration(None, true); + let assignment_updates = job_assignment_update_for(®istration, Some(bob_account_id())); let fulfillment = fulfillment_for(®istration); _ = Timestamp::set(Origin::none(), 1657363915001); assert_ok!(Acurast::submit_attestation( Origin::signed(processor_account_id()).into(), chain.clone() )); - assert_ok!(Acurast::update_certificate_revocation_list( - Origin::signed(alice_account_id()).into(), - updates.clone(), - )); assert_ok!(Acurast::register( Origin::signed(bob_account_id()).into(), registration.clone() )); + assert_ok!(Acurast::update_job_assignments( + Origin::signed(bob_account_id()), + assignment_updates.clone() + )); + assert_ok!(Acurast::update_certificate_revocation_list( + Origin::signed(alice_account_id()).into(), + updates.clone(), + )); assert_err!( Acurast::fulfill( Origin::signed(processor_account_id()), @@ -653,14 +723,18 @@ fn test_update_revocation_list_fulfill() { attestation, processor_account_id() )), - Event::Acurast(crate::Event::CertificateRecovationListUpdated( - alice_account_id(), - updates - )), Event::Acurast(crate::Event::JobRegistrationStored( registration.clone(), bob_account_id() )), + Event::Acurast(crate::Event::JobAssignmentUpdate( + bob_account_id(), + assignment_updates + )), + Event::Acurast(crate::Event::CertificateRecovationListUpdated( + alice_account_id(), + updates + )), ] ); }); diff --git a/pallets/acurast/src/types.rs b/pallets/acurast/src/types.rs index 47162f5f..0680b4f9 100644 --- a/pallets/acurast/src/types.rs +++ b/pallets/acurast/src/types.rs @@ -37,16 +37,33 @@ pub struct AllowedSourcesUpdate where A: Parameter + Member + MaybeSerializeDeserialize + MaybeDisplay + Ord + MaxEncodedLen, { - /// The update operation + /// The update operation. pub operation: ListUpdateOperation, /// The [AccountId] to add or remove. pub account_id: A, } +/// A Job ID consists of an [AccountId] and a [Script]. +pub type JobId = (AccountId, Script); + +/// Structure used to updated the allowed sources list of a [Registration]. +#[derive(RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo, Clone, PartialEq)] +pub struct JobAssignmentUpdate +where + A: Parameter + Member + MaybeSerializeDeserialize + MaybeDisplay + Ord + MaxEncodedLen, +{ + /// The update operation. + pub operation: ListUpdateOperation, + /// The [AccountId] to assign the job to. + pub assignee: A, + /// the job id to be assigned. + pub job_id: JobId, +} + /// Structure used to updated the certificate recovation list. #[derive(RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo, Clone, PartialEq)] pub struct CertificateRevocationListUpdate { - /// The update operation + /// The update operation. pub operation: ListUpdateOperation, /// The [AccountId] to add or remove. pub cert_serial_number: SerialNumber, @@ -60,7 +77,7 @@ pub enum ListUpdateOperation { } /// Structure representing a job registration. -#[derive(RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo, Clone, PartialEq)] +#[derive(RuntimeDebug, Encode, Decode, TypeInfo, Clone, PartialEq)] pub struct JobRegistration where A: Parameter + Member + MaybeSerializeDeserialize + MaybeDisplay + Ord + MaxEncodedLen, @@ -72,6 +89,8 @@ where pub allowed_sources: Option>, /// A boolean indicating if only verified sources can fulfill the job. A verified source is one that has provided a valid key attestation. pub allow_only_verified_sources: bool, + /// Reward offered for the job + pub reward: xcm::v2::MultiAsset, /// Extra parameters. This type can be configured through [Config::RegistrationExtra]. pub extra: T, } diff --git a/pallets/acurast/src/utils.rs b/pallets/acurast/src/utils.rs index e06fb505..d05a722e 100644 --- a/pallets/acurast/src/utils.rs +++ b/pallets/acurast/src/utils.rs @@ -71,13 +71,21 @@ pub(crate) fn ensure_source_allowed( }) .unwrap_or(Ok(()))?; + ensure_source_verified(source, registration)?; + + Ok(()) +} + +pub(crate) fn ensure_source_verified( + source: &T::AccountId, + registration: &JobRegistration, +) -> Result<(), Error> { if registration.allow_only_verified_sources { let attestation = >::get(source).ok_or(Error::::FulfillSourceNotVerified)?; ensure_not_expired(&attestation)?; ensure_not_revoked(&attestation)?; } - Ok(()) } diff --git a/pallets/acurast/src/xcm_adapters.rs b/pallets/acurast/src/xcm_adapters.rs new file mode 100644 index 00000000..419c8719 --- /dev/null +++ b/pallets/acurast/src/xcm_adapters.rs @@ -0,0 +1,163 @@ +use frame_support::dispatch::RawOrigin; +use frame_support::traits::{fungibles, Contains}; +use sp_runtime::traits::{AccountIdConversion, Get, StaticLookup}; +use sp_std::{marker::PhantomData, result::Result}; +use xcm::latest::{MultiAsset, MultiLocation, Result as XcmResult}; +use xcm::prelude::*; +use xcm_builder::{FungiblesMutateAdapter, FungiblesTransferAdapter}; +use xcm_executor::traits::{Convert, MatchesFungibles, TransactAsset}; + +pub fn get_statemint_asset(asset: &MultiAsset) -> Result<(u128, u128), ()> { + return match asset { + MultiAsset { + fun: Fungible(amount), + id: + Concrete(MultiLocation { + parents: 1, + interior: X3(Parachain(1000), PalletInstance(50), GeneralIndex(id)), + }), + } => Ok((*id, *amount)), + + _ => return Err(()), + }; +} + +/// wrapper around FungiblesAdapter. It proxies to it and just on deposit_asset if it failed due to +/// the asset not being created, then creates it and calls the adapter again +pub struct StatemintTransactor< + Runtime, + Assets, + Matcher, + AccountIdConverter, + AccountId, + CheckAsset, + CheckingAccount, +>( + PhantomData<( + Runtime, + Assets, + Matcher, + AccountIdConverter, + AccountId, + CheckAsset, + CheckingAccount, + )>, +); +impl< + Runtime: frame_system::Config + pallet_assets::Config + crate::Config, + Assets: fungibles::Mutate + fungibles::Transfer, + Matcher: MatchesFungibles, + AccountIdConverter: Convert, + AccountId: Clone, // can't get away without it since Currency is generic over it. + CheckAsset: Contains, + CheckingAccount: Get, + > TransactAsset + for StatemintTransactor< + Runtime, + Assets, + Matcher, + AccountIdConverter, + AccountId, + CheckAsset, + CheckingAccount, + > +{ + fn can_check_in(origin: &MultiLocation, what: &MultiAsset) -> XcmResult { + FungiblesMutateAdapter::< + Assets, + Matcher, + AccountIdConverter, + AccountId, + CheckAsset, + CheckingAccount, + >::can_check_in(origin, what) + } + + fn check_in(origin: &MultiLocation, what: &MultiAsset) { + FungiblesMutateAdapter::< + Assets, + Matcher, + AccountIdConverter, + AccountId, + CheckAsset, + CheckingAccount, + >::check_in(origin, what) + } + + fn check_out(dest: &MultiLocation, what: &MultiAsset) { + FungiblesMutateAdapter::< + Assets, + Matcher, + AccountIdConverter, + AccountId, + CheckAsset, + CheckingAccount, + >::check_out(dest, what) + } + + fn deposit_asset(what: &MultiAsset, who: &MultiLocation) -> XcmResult { + FungiblesMutateAdapter::< + Assets, + Matcher, + AccountIdConverter, + AccountId, + CheckAsset, + CheckingAccount, + >::deposit_asset(what, who) + .or_else(|_| { + // asset might not have been created. Try creating it and give it again to FungiblesMutateAdapter + let (asset_id, _amount) = + get_statemint_asset(what).map_err(|_| XcmError::AssetNotFound)?; + let pallet_assets_account: ::AccountId = + ::PalletId::get().into_account_truncating(); + let raw_origin = RawOrigin::<::AccountId>::Signed( + pallet_assets_account.clone(), + ); + let pallet_origin: ::Origin = raw_origin.into(); + + pallet_assets::Pallet::::create( + pallet_origin, + asset_id + .try_into() + .map_err(|_| XcmError::FailedToTransactAsset("unable to create asset"))?, + ::Lookup::unlookup(pallet_assets_account), + ::Balance::from(1u32), + ) + .map_err(|_| XcmError::FailedToTransactAsset("unable to create asset"))?; + + // try depositing again + FungiblesMutateAdapter::< + Assets, + Matcher, + AccountIdConverter, + AccountId, + CheckAsset, + CheckingAccount, + >::deposit_asset(what, who) + }) + } + + fn withdraw_asset( + what: &MultiAsset, + who: &MultiLocation, + ) -> Result { + FungiblesMutateAdapter::< + Assets, + Matcher, + AccountIdConverter, + AccountId, + CheckAsset, + CheckingAccount, + >::withdraw_asset(what, who) + } + + fn internal_transfer_asset( + what: &MultiAsset, + from: &MultiLocation, + to: &MultiLocation, + ) -> Result { + FungiblesTransferAdapter::::internal_transfer_asset( + what, from, to, + ) + } +} diff --git a/pallets/proxy/Cargo.toml b/pallets/proxy/Cargo.toml index 12dccee2..be3aa509 100644 --- a/pallets/proxy/Cargo.toml +++ b/pallets/proxy/Cargo.toml @@ -50,6 +50,7 @@ xcm-simulator = { package = "xcm-simulator", git = "https://github.com/paritytec xcm-executor = { package = "xcm-executor", git = "https://github.com/paritytech/polkadot", default-features = false, branch = "release-v0.9.26"} xcm-builder = { package = "xcm-builder", git = "https://github.com/paritytech/polkadot", default-features = false, branch = "release-v0.9.26"} pallet-xcm = { package = "pallet-xcm", git = "https://github.com/paritytech/polkadot", default-features = false, branch = "release-v0.9.26"} +pallet-assets = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.26" } polkadot-core-primitives = { git = "https://github.com/paritytech/polkadot", default-features = false, branch = "release-v0.9.26"} polkadot-runtime-parachains = { git = "https://github.com/paritytech/polkadot", default-features = false, branch = "release-v0.9.26"} polkadot-parachain = { git = "https://github.com/paritytech/polkadot", default-features = false, branch = "release-v0.9.26"} diff --git a/pallets/proxy/src/lib.rs b/pallets/proxy/src/lib.rs index 3c1fc057..a837684e 100644 --- a/pallets/proxy/src/lib.rs +++ b/pallets/proxy/src/lib.rs @@ -1,179 +1,208 @@ #![cfg_attr(not(feature = "std"), no_std)] -#[cfg(test)] -mod tests; #[cfg(test)] mod mock; +#[cfg(test)] +mod tests; /// Edit this file to define custom logic or remove it if it is not needed. /// Learn more about FRAME and the core library of Substrate FRAME pallets: /// pub use pallet::*; - #[frame_support::pallet] pub mod pallet { - use frame_support::{dispatch::DispatchResult, pallet_prelude::*, sp_runtime::traits::{StaticLookup},}; - use frame_system::pallet_prelude::*; - use pallet_acurast::{JobRegistration, Script, Fulfillment, AllowedSourcesUpdate}; - use xcm::v2::{OriginKind, SendError, }; - use xcm::v2::Instruction::{Transact, DescendOrigin}; - use xcm::v2::{Junction::{Parachain, AccountId32}, SendXcm, Xcm, Junctions::{X1}}; - use xcm::v2::prelude::*; - use frame_support::inherent::Vec; - - /// Configure the pallet by specifying the parameters and types on which it depends. - #[pallet::config] - pub trait Config: frame_system::Config { - type Event: From> + IsType<::Event>; - /// Extra structure to include in the registration of a job. - type RegistrationExtra: Parameter + Member + MaxEncodedLen + Eq; - type XcmSender: SendXcm; - type AcurastPalletId: Get; - type AcurastParachainId: Get; - } - - #[pallet::error] - pub enum Error { - XcmError - } - - #[derive(Clone, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] - pub enum ProxyCall { - #[codec(index = 0u8)] - Register { registration: JobRegistration }, - - #[codec(index = 1u8)] - Deregister { script: Script }, - - #[codec(index = 2u8)] - UpdateAllowedSources { script: Script, updates: Vec> }, - - #[codec(index = 3u8)] - Fulfill { fulfillment: Fulfillment, requester: ::Source }, - } - - #[derive(Clone, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] - pub enum ExtrinsicName { - Register, - Deregister, - UpdateAllowedSources, - Fulfill, - } - - impl ProxyCall { - fn get_name(&self) -> ExtrinsicName { - match self { - ProxyCall::Register{..} => ExtrinsicName::Register, - ProxyCall::Deregister{..} => ExtrinsicName::Deregister, - ProxyCall::UpdateAllowedSources{..} => ExtrinsicName::UpdateAllowedSources, - ProxyCall::Fulfill{..} => ExtrinsicName::Fulfill, - } - } - } - - pub fn acurast_call(proxy_call: ProxyCall, caller: T::AccountId) -> DispatchResult { - // extract bytes from struct - let account_bytes = caller.encode().try_into().unwrap(); - let mut xcm_message = Vec::new(); - let extrinsic = proxy_call.get_name(); - - // create an encoded version of the call - let mut encoded_call = Vec::::new(); - // first byte is the pallet id on the destination chain - encoded_call.push(T::AcurastPalletId::get() as u8); - //second byte the position of the calling function on the enum, - // and then the arguments SCALE encoded in order. - encoded_call.append(&mut proxy_call.encode()); - - // before calling transact, we want to use not the parachain origin, but a user's account - xcm_message.push(DescendOrigin(X1(AccountId32 { - network: NetworkId::Any, - id: account_bytes - }))); - - // put our transact message in the vector of instructions - xcm_message.push(Transact { - origin_type: OriginKind::Xcm, - require_weight_at_most: 1_000_000_000 as u64, - call: encoded_call.into(), - }); - - // use router to send the xcm message - return match T::XcmSender::send_xcm( - (1, X1(Parachain(T::AcurastParachainId::get()))), - Xcm(xcm_message), - ) { - Ok(_) => { - Pallet::::deposit_event(Event::XcmSent { extrinsic, caller }); - Ok(()) - }, - Err(error) => { - Pallet::::deposit_event(Event::XcmNotSent { extrinsic, error, caller }); - Err(Error::::XcmError.into()) - }, - } - } - - #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] - pub struct Pallet(_); - - #[pallet::event] - #[pallet::generate_deposit(pub(super) fn deposit_event)] - // #[derive(Clone)] - pub enum Event { - XcmSent { extrinsic: ExtrinsicName, caller: T::AccountId }, - XcmNotSent { extrinsic: ExtrinsicName, error: SendError, caller: T::AccountId } - } - - #[pallet::hooks] - impl Hooks> for Pallet {} - - #[pallet::call] - impl Pallet { - /// Registers a job by providing a [Registration]. If a job for the same script was previously registered, it will be overwritten. - // TODO: Define proxy weight - #[pallet::weight(10_000)] - pub fn register( - origin: OriginFor, - registration: JobRegistration, - ) -> DispatchResult { - let caller = ensure_signed(origin)?; - let proxy_call = ProxyCall::Register { registration }; - acurast_call::(proxy_call, caller) - } - - /// Deregisters a job for the given script. - #[pallet::weight(10_000)] - pub fn deregister(origin: OriginFor, script: Script) -> DispatchResult { - let caller = ensure_signed(origin)?; - let proxy_call = ProxyCall::Deregister { script }; - acurast_call::(proxy_call, caller) - } - - /// Updates the allowed sources list of a [Registration]. - #[pallet::weight(10_000)] - pub fn update_allowed_sources( - origin: OriginFor, - script: Script, - updates: Vec>, - ) -> DispatchResult { - let caller = ensure_signed(origin)?; - let proxy_call = ProxyCall::UpdateAllowedSources { script, updates }; - acurast_call::(proxy_call, caller) - } - - /// Fulfills a previously registered job. - #[pallet::weight(10_000 )] - pub fn fulfill( - origin: OriginFor, - fulfillment: Fulfillment, - requester: ::Source, - ) -> DispatchResult { - let caller = ensure_signed(origin)?; - let proxy_call = ProxyCall::Fulfill { fulfillment, requester }; - acurast_call::(proxy_call, caller) - } - } + use frame_support::inherent::Vec; + use frame_support::{ + dispatch::DispatchResult, pallet_prelude::*, sp_runtime::traits::StaticLookup, + }; + use frame_system::pallet_prelude::*; + use pallet_acurast::{AllowedSourcesUpdate, Fulfillment, JobRegistration, Script}; + use xcm::v2::prelude::*; + use xcm::v2::Instruction::{DescendOrigin, Transact}; + use xcm::v2::{ + Junction::{AccountId32, Parachain}, + Junctions::X1, + SendXcm, Xcm, + }; + use xcm::v2::{OriginKind, SendError}; + + /// Configure the pallet by specifying the parameters and types on which it depends. + #[pallet::config] + pub trait Config: frame_system::Config { + type Event: From> + IsType<::Event>; + /// Extra structure to include in the registration of a job. + type RegistrationExtra: Parameter + Member + MaxEncodedLen + Eq; + type XcmSender: SendXcm; + type AcurastPalletId: Get; + type AcurastParachainId: Get; + } + + #[pallet::error] + pub enum Error { + XcmError, + } + + #[derive(Clone, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] + pub enum ProxyCall { + #[codec(index = 0u8)] + Register { + registration: JobRegistration, + }, + + #[codec(index = 1u8)] + Deregister { script: Script }, + + #[codec(index = 2u8)] + UpdateAllowedSources { + script: Script, + updates: Vec>, + }, + + #[codec(index = 4u8)] + Fulfill { + fulfillment: Fulfillment, + requester: ::Source, + }, + } + + #[derive(Clone, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] + pub enum ExtrinsicName { + Register, + Deregister, + UpdateAllowedSources, + Fulfill, + } + + impl ProxyCall { + fn get_name(&self) -> ExtrinsicName { + match self { + ProxyCall::Register { .. } => ExtrinsicName::Register, + ProxyCall::Deregister { .. } => ExtrinsicName::Deregister, + ProxyCall::UpdateAllowedSources { .. } => ExtrinsicName::UpdateAllowedSources, + ProxyCall::Fulfill { .. } => ExtrinsicName::Fulfill, + } + } + } + + pub fn acurast_call( + proxy_call: ProxyCall, + caller: T::AccountId, + ) -> DispatchResult { + // extract bytes from struct + let account_bytes = caller.encode().try_into().unwrap(); + let mut xcm_message = Vec::new(); + let extrinsic = proxy_call.get_name(); + + // create an encoded version of the call + let mut encoded_call = Vec::::new(); + // first byte is the pallet id on the destination chain + encoded_call.push(T::AcurastPalletId::get() as u8); + //second byte the position of the calling function on the enum, + // and then the arguments SCALE encoded in order. + encoded_call.append(&mut proxy_call.encode()); + + // before calling transact, we want to use not the parachain origin, but a user's account + xcm_message.push(DescendOrigin(X1(AccountId32 { + network: NetworkId::Any, + id: account_bytes, + }))); + + // put our transact message in the vector of instructions + xcm_message.push(Transact { + origin_type: OriginKind::Xcm, + require_weight_at_most: 1_000_000_000 as u64, + call: encoded_call.into(), + }); + + // use router to send the xcm message + return match T::XcmSender::send_xcm( + (1, X1(Parachain(T::AcurastParachainId::get()))), + Xcm(xcm_message), + ) { + Ok(_) => { + Pallet::::deposit_event(Event::XcmSent { extrinsic, caller }); + Ok(()) + } + Err(error) => { + Pallet::::deposit_event(Event::XcmNotSent { + extrinsic, + error, + caller, + }); + Err(Error::::XcmError.into()) + } + }; + } + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + pub struct Pallet(_); + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + XcmSent { + extrinsic: ExtrinsicName, + caller: T::AccountId, + }, + XcmNotSent { + extrinsic: ExtrinsicName, + error: SendError, + caller: T::AccountId, + }, + } + + #[pallet::hooks] + impl Hooks> for Pallet {} + + #[pallet::call] + impl Pallet { + /// Registers a job by providing a [Registration]. If a job for the same script was previously registered, it will be overwritten. + // TODO: Define proxy weight + #[pallet::weight(10_000)] + pub fn register( + origin: OriginFor, + registration: JobRegistration, + ) -> DispatchResult { + let caller = ensure_signed(origin)?; + let proxy_call = ProxyCall::Register { registration }; + acurast_call::(proxy_call, caller) + } + + /// Deregisters a job for the given script. + #[pallet::weight(10_000)] + pub fn deregister(origin: OriginFor, script: Script) -> DispatchResult { + let caller = ensure_signed(origin)?; + let proxy_call = ProxyCall::Deregister { script }; + acurast_call::(proxy_call, caller) + } + + /// Updates the allowed sources list of a [Registration]. + #[pallet::weight(10_000)] + pub fn update_allowed_sources( + origin: OriginFor, + script: Script, + updates: Vec>, + ) -> DispatchResult { + let caller = ensure_signed(origin)?; + let proxy_call = ProxyCall::UpdateAllowedSources { script, updates }; + acurast_call::(proxy_call, caller) + } + + /// Fulfills a previously registered job. + #[pallet::weight(10_000)] + pub fn fulfill( + origin: OriginFor, + fulfillment: Fulfillment, + requester: ::Source, + ) -> DispatchResult { + let caller = ensure_signed(origin)?; + let proxy_call = ProxyCall::Fulfill { + fulfillment, + requester, + }; + acurast_call::(proxy_call, caller) + } + } } diff --git a/pallets/proxy/src/mock.rs b/pallets/proxy/src/mock.rs index a19a1076..1eb1f392 100644 --- a/pallets/proxy/src/mock.rs +++ b/pallets/proxy/src/mock.rs @@ -1,19 +1,21 @@ pub mod acurast_runtime { - use std::marker::PhantomData; - pub use pallet_acurast; use codec::{Decode, Encode}; use frame_support::{ construct_runtime, parameter_types, traits::{Everything, Nothing}, weights::{constants::WEIGHT_PER_SECOND, Weight}, + PalletId, }; + pub use pallet_acurast; + use pallet_acurast::LockAndPayAsset; use sp_core::H256; use sp_runtime::{ testing::Header, - traits::{Hash, AccountIdLookup}, + traits::{AccountIdLookup, Hash}, AccountId32, }; use sp_std::prelude::*; + use std::marker::PhantomData; use pallet_xcm::XcmPassthrough; use polkadot_core_primitives::BlockNumber as RelayBlockNumber; @@ -100,14 +102,13 @@ pub mod acurast_runtime { AccountId32Aliases, ); - use frame_support::traits::{EnsureOrigin, Get, GetBacking, OriginTrait}; - use xcm_executor::traits::{Convert, ConvertOrigin}; + use frame_support::traits::{Get, OriginTrait}; + use xcm_executor::traits::ConvertOrigin; - pub struct SignedAccountId32FromXcm(PhantomData<(Origin)>); - impl< Origin: OriginTrait> ConvertOrigin - for SignedAccountId32FromXcm - where - Origin::AccountId: From<[u8; 32]>, + pub struct SignedAccountId32FromXcm(PhantomData); + impl ConvertOrigin for SignedAccountId32FromXcm + where + Origin::AccountId: From<[u8; 32]>, { fn convert_origin( origin: impl Into, @@ -115,16 +116,19 @@ pub mod acurast_runtime { ) -> Result { let origin = origin.into(); log::trace!( - target: "xcm::origin_conversion", - "SignedAccountId32AsNative origin: {:?}, kind: {:?}", - origin, kind, - ); + target: "xcm::origin_conversion", + "SignedAccountId32AsNative origin: {:?}, kind: {:?}", + origin, kind, + ); match (kind, origin) { ( OriginKind::Xcm, - MultiLocation { parents: 1, interior: X2(Junction::Parachain(para_id), Junction::AccountId32 { id, network }) }, - ) => - Ok(Origin::signed(id.into())), + MultiLocation { + parents: 1, + interior: + X2(Junction::Parachain(_para_id), Junction::AccountId32 { id, network: _ }), + }, + ) => Ok(Origin::signed(id.into())), (_, origin) => Err(origin), } } @@ -139,13 +143,13 @@ pub mod acurast_runtime { ); parameter_types! { - pub const UnitWeightCost: Weight = 1; - pub KsmPerSecond: (AssetId, u128) = (Concrete(Parent.into()), 1); - pub const MaxInstructions: u32 = 100; -} + pub const UnitWeightCost: Weight = 1; + pub KsmPerSecond: (AssetId, u128) = (Concrete(Parent.into()), 1); + pub const MaxInstructions: u32 = 100; + } pub type LocalAssetTransactor = - XcmCurrencyAdapter, LocationToAccountId, AccountId, ()>; + XcmCurrencyAdapter, LocationToAccountId, AccountId, ()>; pub type XcmRouter = crate::tests::ParachainXcmRouter; pub type Barrier = AllowUnpaidExecutionFrom; @@ -249,8 +253,11 @@ pub mod acurast_runtime { // we just report the weight used. Outcome::Incomplete(w, e) => (Ok(w), Event::Fail(Some(hash), e)), } - }, - Err(()) => (Err(XcmError::UnhandledXcmVersion), Event::BadVersion(Some(hash))), + } + Err(()) => ( + Err(XcmError::UnhandledXcmVersion), + Event::BadVersion(Some(hash)), + ), }; Self::deposit_event(event); result @@ -258,7 +265,10 @@ pub mod acurast_runtime { } impl XcmpMessageHandler for Pallet { - fn handle_xcmp_messages<'a, I: Iterator>( + fn handle_xcmp_messages< + 'a, + I: Iterator, + >( iter: I, max_weight: Weight, ) -> Weight { @@ -282,25 +292,25 @@ pub mod acurast_runtime { impl DmpMessageHandler for Pallet { fn handle_dmp_messages( - iter: impl Iterator)>, + iter: impl Iterator)>, limit: Weight, ) -> Weight { for (_i, (_sent_at, data)) in iter.enumerate() { let id = sp_io::hashing::blake2_256(&data[..]); - let maybe_msg = - VersionedXcm::::decode(&mut &data[..]).map(Xcm::::try_from); + let maybe_msg = VersionedXcm::::decode(&mut &data[..]) + .map(Xcm::::try_from); match maybe_msg { Err(_) => { Self::deposit_event(Event::InvalidFormat(id)); - }, + } Ok(Err(())) => { Self::deposit_event(Event::UnsupportedVersion(id)); - }, + } Ok(Ok(x)) => { let outcome = T::XcmExecutor::execute_xcm(Parent, x.clone(), limit); >::append(x); Self::deposit_event(Event::ExecutedDownward(id, outcome)); - }, + } } } limit @@ -347,13 +357,14 @@ pub mod acurast_runtime { PolkadotXcm: pallet_xcm::{Pallet, Call, Event, Origin}, Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent}, Acurast: pallet_acurast::{Pallet, Call, Storage, Event} = 40, + Assets: pallet_assets, } ); parameter_types! { pub const MinimumPeriod: u64 = SLOT_DURATION / 2; pub const IsRelay: bool = false; - pub Admins: Vec = vec![]; + pub const AcurastPalletId: PalletId = PalletId(*b"acrstpid"); } impl pallet_timestamp::Config for Runtime { @@ -363,12 +374,35 @@ pub mod acurast_runtime { type WeightInfo = (); } + pub const UNIT: Balance = 1_000_000; + pub const MICROUNIT: Balance = 1; + + impl pallet_assets::Config for Runtime { + type Event = Event; + type Balance = Balance; + type AssetId = u32; + type Currency = Balances; + type ForceOrigin = frame_system::EnsureRoot; + type AssetDeposit = frame_support::traits::ConstU128<0>; + type AssetAccountDeposit = frame_support::traits::ConstU128<0>; + type MetadataDepositBase = frame_support::traits::ConstU128<{ UNIT }>; + type MetadataDepositPerByte = frame_support::traits::ConstU128<{ 10 * MICROUNIT }>; + type ApprovalDeposit = frame_support::traits::ConstU128<{ 10 * MICROUNIT }>; + type StringLimit = frame_support::traits::ConstU32<50>; + type Freezer = (); + type Extra = (); + type WeightInfo = (); + } + impl pallet_acurast::Config for Runtime { type Event = Event; type RegistrationExtra = (); type FulfillmentRouter = FulfillmentRouter; type MaxAllowedSources = frame_support::traits::ConstU16<1000>; - type AllowedRevocationListUpdate = Admins; + type AssetTransactor = Transactor; + type PalletId = AcurastPalletId; + type RevocationListUpdateBarrier = (); + type JobAssignmentUpdateBarrier = (); } pub struct FulfillmentRouter; @@ -376,21 +410,38 @@ pub mod acurast_runtime { impl pallet_acurast::FulfillmentRouter for FulfillmentRouter { fn received_fulfillment( _origin: frame_system::pallet_prelude::OriginFor, - from: ::AccountId, + _from: ::AccountId, _fulfillment: pallet_acurast::Fulfillment, _registration: pallet_acurast::JobRegistration< ::AccountId, ::RegistrationExtra, >, - requester: <::Lookup as sp_runtime::traits::StaticLookup>::Target, + _requester: <::Lookup as sp_runtime::traits::StaticLookup>::Target, ) -> frame_support::pallet_prelude::DispatchResultWithPostInfo { Ok(().into()) } } + + pub struct Transactor; + + impl LockAndPayAsset for Transactor { + fn lock_asset( + _asset: MultiAsset, + _owner: <::Lookup as sp_runtime::traits::StaticLookup>::Source, + ) -> Result<(), ()> { + Ok(()) + } + + fn pay_asset( + _asset: MultiAsset, + _target: <::Lookup as sp_runtime::traits::StaticLookup>::Source, + ) -> Result<(), ()> { + Ok(()) + } + } } pub mod proxy_runtime { - use std::marker::PhantomData; use codec::{Decode, Encode}; use frame_support::{ construct_runtime, parameter_types, @@ -400,10 +451,11 @@ pub mod proxy_runtime { use sp_core::H256; use sp_runtime::{ testing::Header, - traits::{Hash, AccountIdLookup}, + traits::{AccountIdLookup, Hash}, AccountId32, }; use sp_std::prelude::*; + use std::marker::PhantomData; use pallet_xcm::XcmPassthrough; use polkadot_core_primitives::BlockNumber as RelayBlockNumber; @@ -425,7 +477,7 @@ pub mod proxy_runtime { pub const SLOT_DURATION: u64 = MILLISECS_PER_BLOCK; parameter_types! { - pub const BlockHashCount: u64 = 250; + pub const BlockHashCount: u64 = 250; } impl frame_system::Config for Runtime { @@ -456,10 +508,10 @@ pub mod proxy_runtime { } parameter_types! { - pub ExistentialDeposit: Balance = 1; - pub const MaxLocks: u32 = 50; - pub const MaxReserves: u32 = 50; -} + pub ExistentialDeposit: Balance = 1; + pub const MaxLocks: u32 = 50; + pub const MaxReserves: u32 = 50; + } impl pallet_balances::Config for Runtime { type MaxLocks = MaxLocks; @@ -474,15 +526,15 @@ pub mod proxy_runtime { } parameter_types! { - pub const ReservedXcmpWeight: Weight = WEIGHT_PER_SECOND / 4; - pub const ReservedDmpWeight: Weight = WEIGHT_PER_SECOND / 4; -} + pub const ReservedXcmpWeight: Weight = WEIGHT_PER_SECOND / 4; + pub const ReservedDmpWeight: Weight = WEIGHT_PER_SECOND / 4; + } parameter_types! { - pub const KsmLocation: MultiLocation = MultiLocation::parent(); - pub const RelayNetwork: NetworkId = NetworkId::Kusama; - pub Ancestry: MultiLocation = Parachain(MsgQueue::parachain_id().into()).into(); -} + pub const KsmLocation: MultiLocation = MultiLocation::parent(); + pub const RelayNetwork: NetworkId = NetworkId::Kusama; + pub Ancestry: MultiLocation = Parachain(MsgQueue::parachain_id().into()).into(); + } pub type LocationToAccountId = ( ParentIsPreset, @@ -490,14 +542,13 @@ pub mod proxy_runtime { AccountId32Aliases, ); - use frame_support::traits::{EnsureOrigin, Get, GetBacking, OriginTrait}; - use xcm_executor::traits::{Convert, ConvertOrigin}; + use frame_support::traits::{Get, OriginTrait}; + use xcm_executor::traits::ConvertOrigin; - pub struct SignedAccountId32FromXcm(PhantomData<(Origin)>); - impl< Origin: OriginTrait> ConvertOrigin - for SignedAccountId32FromXcm - where - Origin::AccountId: From<[u8; 32]>, + pub struct SignedAccountId32FromXcm(PhantomData); + impl ConvertOrigin for SignedAccountId32FromXcm + where + Origin::AccountId: From<[u8; 32]>, { fn convert_origin( origin: impl Into, @@ -505,16 +556,19 @@ pub mod proxy_runtime { ) -> Result { let origin = origin.into(); log::trace!( - target: "xcm::origin_conversion", - "SignedAccountId32AsNative origin: {:?}, kind: {:?}", - origin, kind, - ); + target: "xcm::origin_conversion", + "SignedAccountId32AsNative origin: {:?}, kind: {:?}", + origin, kind, + ); match (kind, origin) { ( OriginKind::Xcm, - MultiLocation { parents: 1, interior: X2(Junction::Parachain(para_id), Junction::AccountId32 { id, network }) }, - ) => - Ok(Origin::signed(id.into())), + MultiLocation { + parents: 1, + interior: + X2(Junction::Parachain(_para_id), Junction::AccountId32 { id, network: _ }), + }, + ) => Ok(Origin::signed(id.into())), (_, origin) => Err(origin), } } @@ -535,7 +589,7 @@ pub mod proxy_runtime { } pub type LocalAssetTransactor = - XcmCurrencyAdapter, LocationToAccountId, AccountId, ()>; + XcmCurrencyAdapter, LocationToAccountId, AccountId, ()>; pub type XcmRouter = crate::tests::ParachainXcmRouter; pub type Barrier = AllowUnpaidExecutionFrom; @@ -639,8 +693,11 @@ pub mod proxy_runtime { // we just report the weight used. Outcome::Incomplete(w, e) => (Ok(w), Event::Fail(Some(hash), e)), } - }, - Err(()) => (Err(XcmError::UnhandledXcmVersion), Event::BadVersion(Some(hash))), + } + Err(()) => ( + Err(XcmError::UnhandledXcmVersion), + Event::BadVersion(Some(hash)), + ), }; Self::deposit_event(event); result @@ -648,7 +705,10 @@ pub mod proxy_runtime { } impl XcmpMessageHandler for Pallet { - fn handle_xcmp_messages<'a, I: Iterator>( + fn handle_xcmp_messages< + 'a, + I: Iterator, + >( iter: I, max_weight: Weight, ) -> Weight { @@ -672,25 +732,25 @@ pub mod proxy_runtime { impl DmpMessageHandler for Pallet { fn handle_dmp_messages( - iter: impl Iterator)>, + iter: impl Iterator)>, limit: Weight, ) -> Weight { for (_i, (_sent_at, data)) in iter.enumerate() { let id = sp_io::hashing::blake2_256(&data[..]); - let maybe_msg = - VersionedXcm::::decode(&mut &data[..]).map(Xcm::::try_from); + let maybe_msg = VersionedXcm::::decode(&mut &data[..]) + .map(Xcm::::try_from); match maybe_msg { Err(_) => { Self::deposit_event(Event::InvalidFormat(id)); - }, + } Ok(Err(())) => { Self::deposit_event(Event::UnsupportedVersion(id)); - }, + } Ok(Ok(x)) => { let outcome = T::XcmExecutor::execute_xcm(Parent, x.clone(), limit); >::append(x); Self::deposit_event(Event::ExecutedDownward(id, outcome)); - }, + } } } limit @@ -735,7 +795,7 @@ pub mod proxy_runtime { Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, MsgQueue: mock_msg_queue::{Pallet, Storage, Event}, PolkadotXcm: pallet_xcm::{Pallet, Call, Event, Origin}, - AcurastProxy: crate::{Pallet, Call, Event} = 34 + AcurastProxy: crate::{Pallet, Call, Event} = 34, } ); @@ -753,8 +813,8 @@ pub mod proxy_runtime { } parameter_types! { - pub const AcurastParachainId: u32 = 2000; - pub const AcurastPalletId: u8 = 40; + pub const AcurastParachainId: u32 = 2000; + pub const AcurastPalletId: u8 = 40; } impl crate::Config for Runtime { @@ -782,7 +842,8 @@ pub mod relay_chain { AccountId32Aliases, AllowUnpaidExecutionFrom, ChildParachainAsNative, ChildParachainConvertsVia, ChildSystemParachainAsSuperuser, CurrencyAdapter as XcmCurrencyAdapter, FixedRateOfFungible, FixedWeightBounds, IsConcrete, - LocationInverter, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, + LocationInverter, SignedAccountId32AsNative, SignedToAccountId32, + SovereignSignedViaLocation, }; use xcm_executor::{Config, XcmExecutor}; @@ -790,8 +851,8 @@ pub mod relay_chain { pub type Balance = u128; parameter_types! { - pub const BlockHashCount: u64 = 250; -} + pub const BlockHashCount: u64 = 250; + } impl frame_system::Config for Runtime { type Origin = Origin; @@ -821,10 +882,10 @@ pub mod relay_chain { } parameter_types! { - pub ExistentialDeposit: Balance = 1; - pub const MaxLocks: u32 = 50; - pub const MaxReserves: u32 = 50; -} + pub ExistentialDeposit: Balance = 1; + pub const MaxLocks: u32 = 50; + pub const MaxReserves: u32 = 50; + } impl pallet_balances::Config for Runtime { type MaxLocks = MaxLocks; @@ -845,18 +906,20 @@ pub mod relay_chain { } parameter_types! { - pub const KsmLocation: MultiLocation = Here.into(); - pub const KusamaNetwork: NetworkId = NetworkId::Kusama; - pub const AnyNetwork: NetworkId = NetworkId::Any; - pub Ancestry: MultiLocation = Here.into(); - pub UnitWeightCost: Weight = 1_000; -} + pub const KsmLocation: MultiLocation = Here.into(); + pub const KusamaNetwork: NetworkId = NetworkId::Kusama; + pub const AnyNetwork: NetworkId = NetworkId::Any; + pub Ancestry: MultiLocation = Here.into(); + pub UnitWeightCost: Weight = 1_000; + } - pub type SovereignAccountOf = - (ChildParachainConvertsVia, AccountId32Aliases); + pub type SovereignAccountOf = ( + ChildParachainConvertsVia, + AccountId32Aliases, + ); pub type LocalAssetTransactor = - XcmCurrencyAdapter, SovereignAccountOf, AccountId, ()>; + XcmCurrencyAdapter, SovereignAccountOf, AccountId, ()>; type LocalOriginConverter = ( SovereignSignedViaLocation, @@ -866,10 +929,10 @@ pub mod relay_chain { ); parameter_types! { - pub const BaseXcmWeight: Weight = 1_000; - pub KsmPerSecond: (AssetId, u128) = (Concrete(KsmLocation::get()), 1); - pub const MaxInstructions: u32 = 100; -} + pub const BaseXcmWeight: Weight = 1_000; + pub KsmPerSecond: (AssetId, u128) = (Concrete(KsmLocation::get()), 1); + pub const MaxInstructions: u32 = 100; + } pub type XcmRouter = crate::tests::RelayChainXcmRouter; pub type Barrier = AllowUnpaidExecutionFrom; @@ -913,8 +976,8 @@ pub mod relay_chain { } parameter_types! { - pub const FirstMessageFactorPercent: u64 = 100; -} + pub const FirstMessageFactorPercent: u64 = 100; + } impl ump::Config for Runtime { type Event = Event; @@ -942,4 +1005,4 @@ pub mod relay_chain { XcmPallet: pallet_xcm::{Pallet, Call, Storage, Event, Origin}, } ); -} \ No newline at end of file +} diff --git a/pallets/proxy/src/tests.rs b/pallets/proxy/src/tests.rs index d1228373..c8d573aa 100644 --- a/pallets/proxy/src/tests.rs +++ b/pallets/proxy/src/tests.rs @@ -24,93 +24,106 @@ pub const ALICE: sp_runtime::AccountId32 = sp_runtime::AccountId32::new([0u8; 32 pub const INITIAL_BALANCE: u128 = 1_000_000_000; decl_test_parachain! { - pub struct AcurastParachain { - Runtime = acurast_runtime::Runtime, - XcmpMessageHandler = acurast_runtime::MsgQueue, - DmpMessageHandler = acurast_runtime::MsgQueue, - new_ext = acurast_ext(2000), - } + pub struct AcurastParachain { + Runtime = acurast_runtime::Runtime, + XcmpMessageHandler = acurast_runtime::MsgQueue, + DmpMessageHandler = acurast_runtime::MsgQueue, + new_ext = acurast_ext(2000), + } } decl_test_parachain! { - pub struct CumulusParachain { - Runtime = proxy_runtime::Runtime, - XcmpMessageHandler = proxy_runtime::MsgQueue, - DmpMessageHandler = proxy_runtime::MsgQueue, - new_ext = proxy_ext(2001), - } + pub struct CumulusParachain { + Runtime = proxy_runtime::Runtime, + XcmpMessageHandler = proxy_runtime::MsgQueue, + DmpMessageHandler = proxy_runtime::MsgQueue, + new_ext = proxy_ext(2001), + } } decl_test_relay_chain! { - pub struct Relay { - Runtime = relay_chain::Runtime, - XcmConfig = relay_chain::XcmConfig, - new_ext = relay_ext(), - } + pub struct Relay { + Runtime = relay_chain::Runtime, + XcmConfig = relay_chain::XcmConfig, + new_ext = relay_ext(), + } } decl_test_network! { - pub struct Network { - relay_chain = Relay, - parachains = vec![ - (2000, AcurastParachain), - (2001, CumulusParachain), - ], - } + pub struct Network { + relay_chain = Relay, + parachains = vec![ + (2000, AcurastParachain), + (2001, CumulusParachain), + ], + } } pub fn para_account_id(id: u32) -> relay_chain::AccountId { - ParaId::from(id).into_account_truncating() + ParaId::from(id).into_account_truncating() } pub fn acurast_ext(para_id: u32) -> sp_io::TestExternalities { - use acurast_runtime::{MsgQueue, Runtime, System}; - - let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); - - pallet_balances::GenesisConfig:: { balances: vec![(ALICE, INITIAL_BALANCE)] } - .assimilate_storage(&mut t) - .unwrap(); - - let mut ext = sp_io::TestExternalities::new(t); - ext.execute_with(|| { - System::set_block_number(1); - MsgQueue::set_para_id(para_id.into()); - }); - ext + use acurast_runtime::{MsgQueue, Runtime, System}; + + let mut t = frame_system::GenesisConfig::default() + .build_storage::() + .unwrap(); + + pallet_balances::GenesisConfig:: { + balances: vec![(ALICE, INITIAL_BALANCE)], + } + .assimilate_storage(&mut t) + .unwrap(); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| { + System::set_block_number(1); + MsgQueue::set_para_id(para_id.into()); + }); + ext } pub fn proxy_ext(para_id: u32) -> sp_io::TestExternalities { - use proxy_runtime::{MsgQueue, Runtime, System}; - - let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); - - pallet_balances::GenesisConfig:: { balances: vec![(ALICE, INITIAL_BALANCE)] } - .assimilate_storage(&mut t) - .unwrap(); - - let mut ext = sp_io::TestExternalities::new(t); - ext.execute_with(|| { - System::set_block_number(1); - MsgQueue::set_para_id(para_id.into()); - }); - ext + use proxy_runtime::{MsgQueue, Runtime, System}; + + let mut t = frame_system::GenesisConfig::default() + .build_storage::() + .unwrap(); + + pallet_balances::GenesisConfig:: { + balances: vec![(ALICE, INITIAL_BALANCE)], + } + .assimilate_storage(&mut t) + .unwrap(); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| { + System::set_block_number(1); + MsgQueue::set_para_id(para_id.into()); + }); + ext } pub fn relay_ext() -> sp_io::TestExternalities { - use relay_chain::{Runtime, System}; - - let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); - - pallet_balances::GenesisConfig:: { - balances: vec![(ALICE, INITIAL_BALANCE), (para_account_id(2000), INITIAL_BALANCE)], - } - .assimilate_storage(&mut t) - .unwrap(); - - let mut ext = sp_io::TestExternalities::new(t); - ext.execute_with(|| System::set_block_number(1)); - ext + use relay_chain::{Runtime, System}; + + let mut t = frame_system::GenesisConfig::default() + .build_storage::() + .unwrap(); + + pallet_balances::GenesisConfig:: { + balances: vec![ + (ALICE, INITIAL_BALANCE), + (para_account_id(2000), INITIAL_BALANCE), + ], + } + .assimilate_storage(&mut t) + .unwrap(); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| System::set_block_number(1)); + ext } pub type RelayChainPalletXcm = pallet_xcm::Pallet; @@ -118,506 +131,461 @@ pub type AcurastPalletXcm = pallet_xcm::Pallet; #[cfg(test)] mod network_tests { - use super::*; - - use codec::Encode; - use frame_support::assert_ok; - use xcm::latest::prelude::*; - use xcm_simulator::TestExt; - - // Helper function for forming buy execution message - fn buy_execution(fees: impl Into) -> Instruction { - BuyExecution { fees: fees.into(), weight_limit: Unlimited } - } - - #[test] - fn dmp() { - Network::reset(); - - let remark = - acurast_runtime::Call::System(frame_system::Call::::remark_with_event { - remark: vec![1, 2, 3], - }); - Relay::execute_with(|| { - assert_ok!(RelayChainPalletXcm::send_xcm( - Here, - Parachain(2000), - Xcm(vec![Transact { - origin_type: OriginKind::SovereignAccount, - require_weight_at_most: INITIAL_BALANCE as u64, - call: remark.encode().into(), - }]), - )); - }); - - AcurastParachain::execute_with(|| { - use acurast_runtime::{Event, System}; - assert!(System::events() - .iter() - .any(|r| matches!(r.event, Event::System(frame_system::Event::Remarked { .. })))); - }); - } - - #[test] - fn ump() { - Network::reset(); - - let remark = relay_chain::Call::System( - frame_system::Call::::remark_with_event { remark: vec![1, 2, 3] }, - ); - AcurastParachain::execute_with(|| { - assert_ok!(AcurastPalletXcm::send_xcm( - Here, - Parent, - Xcm(vec![Transact { - origin_type: OriginKind::SovereignAccount, - require_weight_at_most: INITIAL_BALANCE as u64, - call: remark.encode().into(), - }]), - )); - }); - - Relay::execute_with(|| { - use relay_chain::{Event, System}; - assert!(System::events() - .iter() - .any(|r| matches!(r.event, Event::System(frame_system::Event::Remarked { .. })))); - }); - } - - #[test] - fn xcmp() { - Network::reset(); - - let remark = - proxy_runtime::Call::System(frame_system::Call::::remark_with_event { - remark: vec![1, 2, 3], - }); - - AcurastParachain::execute_with(|| { - assert_ok!(AcurastPalletXcm::send_xcm( - Here, - (Parent, Parachain(2001)), - Xcm(vec![Transact { - origin_type: OriginKind::SovereignAccount, - require_weight_at_most: INITIAL_BALANCE as u64, - call: remark.encode().into(), - }]), - )); - }); - - CumulusParachain::execute_with(|| { - use proxy_runtime::{Event, System}; - assert!(System::events() - .iter() - .any(|r| matches!(r.event, Event::System(frame_system::Event::Remarked { .. })))); - }); - } - - #[test] - fn reserve_transfer() { - Network::reset(); - - let withdraw_amount = 123; - - Relay::execute_with(|| { - assert_ok!(RelayChainPalletXcm::reserve_transfer_assets( - relay_chain::Origin::signed(ALICE), - Box::new(X1(Parachain(2000)).into().into()), - Box::new(X1(AccountId32 { network: Any, id: ALICE.into() }).into().into()), - Box::new((Here, withdraw_amount).into()), - 0, - )); - assert_eq!( - relay_chain::Balances::free_balance(¶_account_id(2000)), - INITIAL_BALANCE + withdraw_amount - ); - }); - - AcurastParachain::execute_with(|| { - // free execution, full amount received - assert_eq!( - pallet_balances::Pallet::::free_balance(&ALICE), - INITIAL_BALANCE + withdraw_amount - ); - }); - } - - /// Scenario: - /// A parachain transfers funds on the relay chain to another parachain account. - /// - /// Asserts that the parachain accounts are updated as expected. - #[test] - fn withdraw_and_deposit() { - Network::reset(); - - let send_amount = 10; - - AcurastParachain::execute_with(|| { - let message = Xcm(vec![ - WithdrawAsset((Here, send_amount).into()), - buy_execution((Here, send_amount)), - DepositAsset { - assets: All.into(), - max_assets: 1, - beneficiary: Parachain(2001).into(), - }, - ]); - // Send withdraw and deposit - assert_ok!(AcurastPalletXcm::send_xcm(Here, Parent, message.clone())); - }); - - Relay::execute_with(|| { - assert_eq!( - relay_chain::Balances::free_balance(para_account_id(2000)), - INITIAL_BALANCE - send_amount - ); - assert_eq!(relay_chain::Balances::free_balance(para_account_id(2001)), send_amount); - }); - } - - /// Scenario: - /// A parachain wants to be notified that a transfer worked correctly. - /// It sends a `QueryHolding` after the deposit to get notified on success. - /// - /// Asserts that the balances are updated correctly and the expected XCM is sent. - #[test] - fn query_holding() { - Network::reset(); - - let send_amount = 10; - let query_id_set = 1234; - - // Send a message which fully succeeds on the relay chain - AcurastParachain::execute_with(|| { - let message = Xcm(vec![ - WithdrawAsset((Here, send_amount).into()), - buy_execution((Here, send_amount)), - DepositAsset { - assets: All.into(), - max_assets: 1, - beneficiary: Parachain(2001).into(), - }, - QueryHolding { - query_id: query_id_set, - dest: Parachain(2000).into(), - assets: All.into(), - max_response_weight: 1_000_000_000, - }, - ]); - // Send withdraw and deposit with query holding - assert_ok!(AcurastPalletXcm::send_xcm(Here, Parent, message.clone(),)); - }); - - // Check that transfer was executed - Relay::execute_with(|| { - // Withdraw executed - assert_eq!( - relay_chain::Balances::free_balance(para_account_id(2000)), - INITIAL_BALANCE - send_amount - ); - // Deposit executed - assert_eq!(relay_chain::Balances::free_balance(para_account_id(2001)), send_amount); - }); - - // Check that QueryResponse message was received - AcurastParachain::execute_with(|| { - assert_eq!( - acurast_runtime::MsgQueue::received_dmp(), - vec![Xcm(vec![QueryResponse { - query_id: query_id_set, - response: Response::Assets(MultiAssets::new()), - max_weight: 1_000_000_000, - }])], - ); - }); - } + use super::*; + + use codec::Encode; + use frame_support::assert_ok; + use xcm::latest::prelude::*; + use xcm_simulator::TestExt; + + // Helper function for forming buy execution message + fn buy_execution(fees: impl Into) -> Instruction { + BuyExecution { + fees: fees.into(), + weight_limit: Unlimited, + } + } + + #[test] + fn dmp() { + Network::reset(); + + let remark = acurast_runtime::Call::System( + frame_system::Call::::remark_with_event { + remark: vec![1, 2, 3], + }, + ); + Relay::execute_with(|| { + assert_ok!(RelayChainPalletXcm::send_xcm( + Here, + Parachain(2000), + Xcm(vec![Transact { + origin_type: OriginKind::SovereignAccount, + require_weight_at_most: INITIAL_BALANCE as u64, + call: remark.encode().into(), + }]), + )); + }); + + AcurastParachain::execute_with(|| { + use acurast_runtime::{Event, System}; + assert!(System::events() + .iter() + .any(|r| matches!(r.event, Event::System(frame_system::Event::Remarked { .. })))); + }); + } + + #[test] + fn ump() { + Network::reset(); + + let remark = relay_chain::Call::System( + frame_system::Call::::remark_with_event { + remark: vec![1, 2, 3], + }, + ); + AcurastParachain::execute_with(|| { + assert_ok!(AcurastPalletXcm::send_xcm( + Here, + Parent, + Xcm(vec![Transact { + origin_type: OriginKind::SovereignAccount, + require_weight_at_most: INITIAL_BALANCE as u64, + call: remark.encode().into(), + }]), + )); + }); + + Relay::execute_with(|| { + use relay_chain::{Event, System}; + assert!(System::events() + .iter() + .any(|r| matches!(r.event, Event::System(frame_system::Event::Remarked { .. })))); + }); + } + + #[test] + fn xcmp() { + Network::reset(); + + let remark = proxy_runtime::Call::System( + frame_system::Call::::remark_with_event { + remark: vec![1, 2, 3], + }, + ); + + AcurastParachain::execute_with(|| { + assert_ok!(AcurastPalletXcm::send_xcm( + Here, + (Parent, Parachain(2001)), + Xcm(vec![Transact { + origin_type: OriginKind::SovereignAccount, + require_weight_at_most: INITIAL_BALANCE as u64, + call: remark.encode().into(), + }]), + )); + }); + + CumulusParachain::execute_with(|| { + use proxy_runtime::{Event, System}; + assert!(System::events() + .iter() + .any(|r| matches!(r.event, Event::System(frame_system::Event::Remarked { .. })))); + }); + } + + #[test] + fn reserve_transfer() { + Network::reset(); + + let withdraw_amount = 123; + + Relay::execute_with(|| { + assert_ok!(RelayChainPalletXcm::reserve_transfer_assets( + relay_chain::Origin::signed(ALICE), + Box::new(X1(Parachain(2000)).into().into()), + Box::new( + X1(AccountId32 { + network: Any, + id: ALICE.into() + }) + .into() + .into() + ), + Box::new((Here, withdraw_amount).into()), + 0, + )); + assert_eq!( + relay_chain::Balances::free_balance(¶_account_id(2000)), + INITIAL_BALANCE + withdraw_amount + ); + }); + + AcurastParachain::execute_with(|| { + // free execution, full amount received + assert_eq!( + pallet_balances::Pallet::::free_balance(&ALICE), + INITIAL_BALANCE + withdraw_amount + ); + }); + } + + /// Scenario: + /// A parachain transfers funds on the relay chain to another parachain account. + /// + /// Asserts that the parachain accounts are updated as expected. + #[test] + fn withdraw_and_deposit() { + Network::reset(); + + let send_amount = 10; + + AcurastParachain::execute_with(|| { + let message = Xcm(vec![ + WithdrawAsset((Here, send_amount).into()), + buy_execution((Here, send_amount)), + DepositAsset { + assets: All.into(), + max_assets: 1, + beneficiary: Parachain(2001).into(), + }, + ]); + // Send withdraw and deposit + assert_ok!(AcurastPalletXcm::send_xcm(Here, Parent, message.clone())); + }); + + Relay::execute_with(|| { + assert_eq!( + relay_chain::Balances::free_balance(para_account_id(2000)), + INITIAL_BALANCE - send_amount + ); + assert_eq!( + relay_chain::Balances::free_balance(para_account_id(2001)), + send_amount + ); + }); + } + + /// Scenario: + /// A parachain wants to be notified that a transfer worked correctly. + /// It sends a `QueryHolding` after the deposit to get notified on success. + /// + /// Asserts that the balances are updated correctly and the expected XCM is sent. + #[test] + fn query_holding() { + Network::reset(); + + let send_amount = 10; + let query_id_set = 1234; + + // Send a message which fully succeeds on the relay chain + AcurastParachain::execute_with(|| { + let message = Xcm(vec![ + WithdrawAsset((Here, send_amount).into()), + buy_execution((Here, send_amount)), + DepositAsset { + assets: All.into(), + max_assets: 1, + beneficiary: Parachain(2001).into(), + }, + QueryHolding { + query_id: query_id_set, + dest: Parachain(2000).into(), + assets: All.into(), + max_response_weight: 1_000_000_000, + }, + ]); + // Send withdraw and deposit with query holding + assert_ok!(AcurastPalletXcm::send_xcm(Here, Parent, message.clone(),)); + }); + + // Check that transfer was executed + Relay::execute_with(|| { + // Withdraw executed + assert_eq!( + relay_chain::Balances::free_balance(para_account_id(2000)), + INITIAL_BALANCE - send_amount + ); + // Deposit executed + assert_eq!( + relay_chain::Balances::free_balance(para_account_id(2001)), + send_amount + ); + }); + + // Check that QueryResponse message was received + AcurastParachain::execute_with(|| { + assert_eq!( + acurast_runtime::MsgQueue::received_dmp(), + vec![Xcm(vec![QueryResponse { + query_id: query_id_set, + response: Response::Assets(MultiAssets::new()), + max_weight: 1_000_000_000, + }])], + ); + }); + } } #[cfg(test)] mod proxy_calls { - use super::*; - use codec::{Decode, Encode}; - use xcm_simulator::TestExt; - use frame_support::{assert_ok, RuntimeDebug, traits::Currency}; - use frame_support::dispatch::TypeInfo; - use sp_runtime::traits::{AccountIdConversion}; - use xcm::{latest::prelude::*}; - // use xcm::v2::{MultiLocation}; - use frame_support::traits::PalletInfoAccess; - use pallet_acurast::{AllowedSourcesUpdate, ListUpdateOperation, Fulfillment, AttestationChain}; - // use pallet_acurast::attestation::{CertificateChainInput, CertificateInput, CertificateId}; - use polkadot_parachain::primitives::Sibling; - use hex_literal::hex; - use pallet_acurast::Event::ReceivedFulfillment; - use frame_support::dispatch::Dispatchable; - - const SCRIPT_BYTES: [u8; 53] = hex!("697066733A2F2F00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"); - - #[test] - fn register() { - Network::reset(); - use pallet_acurast::{JobRegistration, Script}; - - CumulusParachain::execute_with(|| { - use proxy_runtime::Call::AcurastProxy; - use crate::pallet::Call::register; - - let message_call = AcurastProxy( register { - registration: JobRegistration { - script: SCRIPT_BYTES.to_vec().try_into().unwrap(), - allowed_sources: None, - allow_only_verified_sources: false, - extra: () - } - } ); - let alice_origin = proxy_runtime::Origin::signed(ALICE); - let dispatch_status = message_call.dispatch(alice_origin); - assert_ok!(dispatch_status); - }); - - AcurastParachain::execute_with(|| { - use acurast_runtime::{Event, System, Runtime}; - use acurast_runtime::pallet_acurast::StoredJobRegistration; - use acurast_runtime::pallet_acurast::Event::JobRegistrationStored; - - let events = System::events(); - let script: Script = SCRIPT_BYTES.to_vec().try_into().unwrap(); - let _p_store = StoredJobRegistration::::get(ALICE, script); - assert!( - events.iter().any(|event| - matches!(event.event, Event::Acurast(JobRegistrationStored{ .. })) - ) - ); - }); - } - - #[test] - fn deregister() { - Network::reset(); - register(); - - // check that job is stored in the context of this test - AcurastParachain::execute_with(|| { - use acurast_runtime::{Runtime}; - use acurast_runtime::pallet_acurast::StoredJobRegistration; - - let script: Script = SCRIPT_BYTES.to_vec().try_into().unwrap(); - let p_store = StoredJobRegistration::::get(ALICE, script); - assert!(p_store.is_some()); - }); - - use frame_support::dispatch::Dispatchable; - use hex_literal::hex; - use pallet_acurast::{JobRegistration, Script}; - - CumulusParachain::execute_with(|| { - use proxy_runtime::Call::AcurastProxy; - use crate::pallet::Call::deregister; - - let message_call = AcurastProxy( deregister { - script: SCRIPT_BYTES.to_vec().try_into().unwrap(), - }); - - let alice_origin = proxy_runtime::Origin::signed(ALICE); - let dispatch_status = message_call.dispatch(alice_origin); - assert_ok!(dispatch_status); - }); - - AcurastParachain::execute_with(|| { - use acurast_runtime::{Event, System, Runtime}; - use acurast_runtime::pallet_acurast::StoredJobRegistration; - use acurast_runtime::pallet_acurast::Event::JobRegistrationRemoved; - - let events = System::events(); - let script: Script = SCRIPT_BYTES.to_vec().try_into().unwrap(); - let _p_store = StoredJobRegistration::::get(ALICE, script); - assert!( - events.iter().any(|event| - matches!(event.event, Event::Acurast(JobRegistrationRemoved{ .. })) - ) - ); - }); - } - - #[test] - fn update_allowed_sources() { - Network::reset(); - - register(); - - // check that job is stored in the context of this test - AcurastParachain::execute_with(|| { - use acurast_runtime::{Runtime}; - use acurast_runtime::pallet_acurast::StoredJobRegistration; - - let script: Script = SCRIPT_BYTES.to_vec().try_into().unwrap(); - let p_store = StoredJobRegistration::::get(ALICE, script); - assert!(p_store.is_some()); - }); - - use frame_support::dispatch::Dispatchable; - use hex_literal::hex; - use pallet_acurast::{Script, AllowedSourcesUpdate}; - - - let rand_array: [u8; 32] = rand::random(); - let source = sp_runtime::AccountId32::new(rand_array); - - CumulusParachain::execute_with(|| { - use proxy_runtime::Call::AcurastProxy; - use crate::pallet::Call::update_allowed_sources; - - - let update = AllowedSourcesUpdate { operation: ListUpdateOperation::Add, account_id: source.clone() }; - - let message_call = AcurastProxy( update_allowed_sources { - script: SCRIPT_BYTES.to_vec().try_into().unwrap(), - updates: vec![update] - }); - - let alice_origin = proxy_runtime::Origin::signed(ALICE); - let dispatch_status = message_call.dispatch(alice_origin); - assert_ok!(dispatch_status); - }); - - AcurastParachain::execute_with(|| { - use acurast_runtime::{Event, System, Runtime}; - use acurast_runtime::pallet_acurast::StoredJobRegistration; - use acurast_runtime::pallet_acurast::Event::AllowedSourcesUpdated; - - let events = System::events(); - let script: Script = SCRIPT_BYTES.to_vec().try_into().unwrap(); - let p_store = StoredJobRegistration::::get(ALICE, script); - - // source in storage same as one submitted to proxy - let found_source: &sp_runtime::AccountId32 = &p_store.unwrap().allowed_sources.unwrap()[0]; - assert_eq!(*found_source, source); - - // event emitted - assert!( - events.iter().any(|event| - matches!(event.event, Event::Acurast(AllowedSourcesUpdated{ .. })) - ) - ); - }); - } - - #[test] - fn fulfill() { - Network::reset(); - - register(); - - // check that job is stored in the context of this test - AcurastParachain::execute_with(|| { - use acurast_runtime::{Runtime}; - use acurast_runtime::pallet_acurast::StoredJobRegistration; - - let script: Script = SCRIPT_BYTES.to_vec().try_into().unwrap(); - let p_store = StoredJobRegistration::::get(ALICE, script); - assert!(p_store.is_some()); - }); - - use frame_support::dispatch::Dispatchable; - use hex_literal::hex; - use pallet_acurast::{Script, AllowedSourcesUpdate}; - - let rand_array: [u8; 32] = rand::random(); - let BOB = sp_runtime::AccountId32::new(rand_array); - - CumulusParachain::execute_with(|| { - use proxy_runtime::Call::AcurastProxy; - use crate::pallet::Call::fulfill; - - let payload: [u8; 32] = rand::random(); - - let fulfillment = Fulfillment{ - script: SCRIPT_BYTES.to_vec().try_into().unwrap(), - payload: payload.to_vec() - }; - - let message_call = AcurastProxy( fulfill { - fulfillment, - requester: sp_runtime::MultiAddress::Id(ALICE) - }); - - let bob_origin = proxy_runtime::Origin::signed(BOB); - let dispatch_status = message_call.dispatch(bob_origin); - assert_ok!(dispatch_status); - }); - - AcurastParachain::execute_with(|| { - use acurast_runtime::{Event, System, Runtime}; - use acurast_runtime::pallet_acurast::StoredJobRegistration; - use acurast_runtime::pallet_acurast::Event::ReceivedFulfillment; - - let events = System::events(); - - //event emitted - assert!( - events.iter().any(|event| - matches!(event.event, Event::Acurast(ReceivedFulfillment{ .. })) - ) - ); - }); - } - - #[test] - fn submit_attestation(){ - Network::reset(); - - const ROOT_CERT: [u8; 1380] = hex!("3082056030820348a003020102020900e8fa196314d2fa18300d06092a864886f70d01010b0500301b311930170603550405131066393230303965383533623662303435301e170d3136303532363136323835325a170d3236303532343136323835325a301b31193017060355040513106639323030396538353362366230343530820222300d06092a864886f70d01010105000382020f003082020a0282020100afb6c7822bb1a701ec2bb42e8bcc541663abef982f32c77f7531030c97524b1b5fe809fbc72aa9451f743cbd9a6f1335744aa55e77f6b6ac3535ee17c25e639517dd9c92e6374a53cbfe258f8ffbb6fd129378a22a4ca99c452d47a59f3201f44197ca1ccd7e762fb2f53151b6feb2fffd2b6fe4fe5bc6bd9ec34bfe08239daafceb8eb5a8ed2b3acd9c5e3a7790e1b51442793159859811ad9eb2a96bbdd7a57c93a91c41fccd27d67fd6f671aa0b815261ad384fa37944864604ddb3d8c4f920a19b1656c2f14ad6d03c56ec060899041c1ed1a5fe6d3440b556bad1d0a152589c53e55d370762f0122eef91861b1b0e6c4c80927499c0e9bec0b83e3bc1f93c72c049604bbd2f1345e62c3f8e26dbec06c94766f3c128239d4f4312fad8123887e06becf567583bf8355a81feeabaf99a83c8df3e2a322afc672bf120b135158b6821ceaf309b6eee77f98833b018daa10e451f06a374d50781f359082966bb778b9308942698e74e0bcd24628a01c2cc03e51f0b3e5b4ac1e4df9eaf9ff6a492a77c1483882885015b422ce67b80b88c9b48e13b607ab545c723ff8c44f8f2d368b9f6520d31145ebf9e862ad71df6a3bfd2450959d653740d97a12f368b13ef66d5d0a54a6e2f5d9a6fef446832bc67844725861f093dd0e6f3405da89643ef0f4d69b6420051fdb93049673e36950580d3cdf4fbd08bc58483952600630203010001a381a63081a3301d0603551d0e041604143661e1007c880509518b446c47ff1a4cc9ea4f12301f0603551d230418301680143661e1007c880509518b446c47ff1a4cc9ea4f12300f0603551d130101ff040530030101ff300e0603551d0f0101ff04040302018630400603551d1f043930373035a033a031862f68747470733a2f2f616e64726f69642e676f6f676c65617069732e636f6d2f6174746573746174696f6e2f63726c2f300d06092a864886f70d01010b0500038202010020c8c38d4bdca9571b468c892fff72aac6f844a11d41a8f0736cc37d16d6426d8e7e9407044cea39e68b07c13dbf1503dd5c85bdafb2c02d5f6cdb4efa8127df8b04f182770fc4e7745b7fceaa87129a8801ce8e9bc0cb96379b4d26a82d30fd9c2f8eed6dc1be2f84b689e4d914258b144bbae624a1c70671132e2f0616a884b2a4d6a46ffa89b602bfbad80c1243711f56eb6056f637c8a0141cc54094268b8c3c7db994b35c0dcd6cb2abc2dafee252023d2dea0cd6c368bea3e6414886f6b1e58b5bd7c730b268c4e3c1fb6424b91febbdb80c586e2ae8368c84d5d10917bda2561789d4687393340e2e254f560ef64b2358fcdc0fbfc6700952e708bffcc627500c1f66e81ea17c098d7a2e9b18801b7ab4ac71587d345dcc8309d5b62a50427aa6d03dcb05996c96ba0c5d71e92162c016ca849ff35f0d52c65d05605a47f3ae917acd2df910efd2326688596ef69b3bf5fe3154f7aeb880a0a73ca04d94c2ce8317eeb43d5eff5883e336f5f249daaca4899237bf267e5c43ab02ea44162403723be6aa692c61bdae9ed409d463c4c97c64306577eef2bc7560b75715cc9c7dc67c86082db751a89c30349762b0782385875cf1a3c6166e0ae3c12d374e2d4f1846f318744bd879b587329bf018217a6c0c77241a4878e435c03079cb451289c5776206069a2f8d65f840e1445287bed877abae24e24435168d553ce4"); - const INT_CERT_1: [u8; 987] = hex!("308203d7308201bfa003020102020a038826676065899685f5300d06092a864886f70d01010b0500301b311930170603550405131066393230303965383533623662303435301e170d3139303830393233303332335a170d3239303830363233303332335a302f31193017060355040513103534663539333730353432663561393531123010060355040c0c095374726f6e67426f783076301006072a8648ce3d020106052b8104002203620004e352276f9bfcea4301a5f0427fa6478e573209ae44fd762cfbc57cbbd4713631509e802ea0e940536e54fa2570ca2846154698075509293b3100b3955b4317768b286bf6fe2651c59af6c6b0db3360090a4647c7860e76ecc3b8a7db5ce57acca381b63081b3301d0603551d0e041604146990b10c3b088aee2af88c3387b42c12dadfc3a6301f0603551d230418301680143661e1007c880509518b446c47ff1a4cc9ea4f12300f0603551d130101ff040530030101ff300e0603551d0f0101ff04040302020430500603551d1f044930473045a043a041863f68747470733a2f2f616e64726f69642e676f6f676c65617069732e636f6d2f6174746573746174696f6e2f63726c2f38463637333443394641353034373839300d06092a864886f70d01010b050003820201005c591327a0b0249ecadc949184c9651ed1f2a617a17516439875429e9bd21f87fd2365d0dcde747022c19410f23ab380fe1cef0f47aebc443c2a4531df3eca4101bf96d6bc30dfd878ed6734653111b5e782a03350cc2605e128b48a57e7ff1fe4bf4104de3f7ca9ace6afb01bdd9205fa10b91837a337257afb8290afa456fa629cfae5477b172b009bf28d43dcd4d31edcbf3dc1b6fcfcca5c38a79773d38b5a9d3ccd8152d51f25f9900701d9fb4fbf1307e17fcf5ddc759409863d2f0fb2e6c24468c9c5d85154e104318cb10ae60ba27bb252080e072645681c39e560e8586a64550867162f4bde9db75645882cb9eaff4efe1b0a312f5bd40224298c91f135061b8e04e8fa4c618c33f7b942c028f00d18113bfb6e55a952ccb5d71ee046f9bfdc85aa083e26d94be354545954b70c812ac4e326fdf07703bb79e536d429ff1d099c81722d81714593c7c2bb56740ccbc801332bb548695e28f2c8ac1452a260cfe57f311adc132e8dda01d638f9a4a31288a623a917f5b6c87e1c8316927129a0d11f384251d2df26b942a76844ab91968f4953e7484f2ecd2d6e187f9772d3b4584ac986e2079bc75f20773f8814ba2d16c7266761d6a3505f939fc316efda8787085a5d4f479df944f9d061d2c99acce73ed31770659297113f94140500306887be1b88082b96b18e123cabfcffbd79b68782a0408748cbf4f02f42"); - const INT_CERT_2: [u8; 564] = hex!("30820230308201b7a003020102020a15905857467176635834300a06082a8648ce3d040302302f31193017060355040513103534663539333730353432663561393531123010060355040c0c095374726f6e67426f78301e170d3139303732373031353231395a170d3239303732343031353231395a302f31193017060355040513103937333533373739333664306464373431123010060355040c0c095374726f6e67426f783059301306072a8648ce3d020106082a8648ce3d030107034200047639963abb7d336b5f238d8b355efdb395a22b2ccde67bda24328e4bbf802fefa97f204dd8bdb450332cb5e566f759bdc6ffafb9f3bc78e3747dfce8278e5f02a381ba3081b7301d0603551d0e04160414413e3ca9b34bc7a51cbb0125c0421be651ad7ad8301f0603551d230418301680146990b10c3b088aee2af88c3387b42c12dadfc3a6300f0603551d130101ff040530030101ff300e0603551d0f0101ff04040302020430540603551d1f044d304b3049a047a045864368747470733a2f2f616e64726f69642e676f6f676c65617069732e636f6d2f6174746573746174696f6e2f63726c2f3135393035383537343637313736363335383334300a06082a8648ce3d0403020367003064023017a0df3880a22ea1d4b3dfbdb6c04a4e5655d0ba70bdc8a5ac483b270c1e6d520cda9800b3ad775bae8dfccc7a86ecf802302898f95f24867bb3112f440db5dad27769e42be7db8dc51cf0b2af55aa43c11002e340a24f3965032f9a3a7c83c6bbdb"); - const LEAF_CERT: [u8; 672] = hex!("3082029c30820241a003020102020101300c06082a8648ce3d0403020500302f31193017060355040513103937333533373739333664306464373431123010060355040c0c095374726f6e67426f783022180f32303232303730393130353135355a180f32303238303532333233353935395a301f311d301b06035504030c14416e64726f6964204b657973746f7265204b65793059301306072a8648ce3d020106082a8648ce3d03010703420004b20c1d15477662623ecf430104898006e0f81c0db1bae87cb96a87c7777404659e585d3d9057b8a2ff8ae61f401a078fc75cf52c8c4268e810f93798c729e862a382015630820152300e0603551d0f0101ff0404030207803082013e060a2b06010401d6790201110482012e3082012a0201040a01020201290a0102040874657374617364660400306cbf853d0802060181e296611fbf85455c045a305831323030042b636f6d2e7562696e657469632e61747465737465642e6578656375746f722e746573742e746573746e657402010e31220420bdcb4560f6b3c41dad920668169c28be1ef9ea49f23d98cd8eb2f37ae4488ff93081a1a1053103020102a203020103a30402020100a5053103020100aa03020101bf8377020500bf853e03020100bf85404c304a0420879cd3f18ea76e244d4d4ac3bcb9c337c13b4667190b19035afe2536550050f10101ff0a010004203f4136ee3581e6aba8ea337a6b43d703de1eca241f9b7f277ecdfafff7a8dcf1bf854105020301d4c0bf85420502030315debf854e06020401348abdbf854f06020401348abd300c06082a8648ce3d04030205000347003044022033a613cce9a6ed25026a492b651f0ac67c3c0289d4e4743168c6903e2faa0bda0220324cd35c4bf2695d71ad12a28868e69232112922eaf0e3699f6add8133d528d9"); - - let attestation_chain = AttestationChain { - certificate_chain: vec![ - ROOT_CERT.to_vec().try_into().unwrap(), - INT_CERT_1.to_vec().try_into().unwrap(), - INT_CERT_2.to_vec().try_into().unwrap(), - LEAF_CERT.to_vec().try_into().unwrap(), - ] - .try_into() - .unwrap(), - }; - - AcurastParachain::execute_with(|| { - use acurast_runtime::{Timestamp, Origin}; - _ = Timestamp::set(Origin::none(), 1657363915001).expect("Couldn't set timestamp"); - }); - - CumulusParachain::execute_with(|| { - use proxy_runtime::{Origin, Runtime}; - use proxy_runtime::Call::AcurastProxy; - use crate::pallet::Call::submit_attestation; - - let message_call = AcurastProxy(submit_attestation { - attestation_chain - }); - - let x_origin = Origin::signed(hex!("b8bc25a2b4c0386b8892b43e435b71fe11fa50533935f027949caf04bcce4694").into()).into(); - let dispatch_status = message_call.dispatch(x_origin); - assert_ok!(dispatch_status); - }); - - AcurastParachain::execute_with(|| { - use acurast_runtime::{Event, System, Runtime, Acurast}; - use acurast_runtime::pallet_acurast::Event::AttestationStored; - - let events = System::events(); - - // attestation stored - let _p_store = Acurast::stored_attestation(ALICE); - - // event emitted - assert!( - events.iter().any(|event| - matches!(event.event, Event::Acurast(AttestationStored{ .. })) - ) - ); - }); - } + use super::*; + use frame_support::assert_ok; + use frame_support::dispatch::Dispatchable; + use hex_literal::hex; + use pallet_acurast::{Fulfillment, ListUpdateOperation}; + use xcm::latest::prelude::*; + use xcm_simulator::TestExt; + + const SCRIPT_BYTES: [u8; 53] = hex!("697066733A2F2F00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"); + + fn multi_asset() -> MultiAsset { + MultiAsset { + id: AssetId::Concrete(MultiLocation { + parents: 0, + interior: Junctions::Here, + }), + fun: Fungibility::Fungible(10), + } + } + + #[test] + fn register() { + Network::reset(); + use pallet_acurast::{JobRegistration, Script}; + + CumulusParachain::execute_with(|| { + use crate::pallet::Call::register; + use proxy_runtime::Call::AcurastProxy; + + let message_call = AcurastProxy(register { + registration: JobRegistration { + script: SCRIPT_BYTES.to_vec().try_into().unwrap(), + allowed_sources: None, + allow_only_verified_sources: false, + extra: (), + reward: multi_asset(), + }, + }); + let alice_origin = proxy_runtime::Origin::signed(ALICE); + let dispatch_status = message_call.dispatch(alice_origin); + assert_ok!(dispatch_status); + }); + + AcurastParachain::execute_with(|| { + use acurast_runtime::pallet_acurast::Event::JobRegistrationStored; + use acurast_runtime::pallet_acurast::StoredJobRegistration; + use acurast_runtime::{Event, Runtime, System}; + + let events = System::events(); + let script: Script = SCRIPT_BYTES.to_vec().try_into().unwrap(); + let p_store = StoredJobRegistration::::get(ALICE, script); + assert!(p_store.is_some()); + assert!(events + .iter() + .any(|event| matches!(event.event, Event::Acurast(JobRegistrationStored { .. })))); + }); + } + + #[test] + fn deregister() { + Network::reset(); + register(); + + // check that job is stored in the context of this test + AcurastParachain::execute_with(|| { + use acurast_runtime::pallet_acurast::StoredJobRegistration; + use acurast_runtime::Runtime; + + let script: Script = SCRIPT_BYTES.to_vec().try_into().unwrap(); + let p_store = StoredJobRegistration::::get(ALICE, script); + assert!(p_store.is_some()); + }); + + use frame_support::dispatch::Dispatchable; + use pallet_acurast::Script; + + CumulusParachain::execute_with(|| { + use crate::pallet::Call::deregister; + use proxy_runtime::Call::AcurastProxy; + + let message_call = AcurastProxy(deregister { + script: SCRIPT_BYTES.to_vec().try_into().unwrap(), + }); + + let alice_origin = proxy_runtime::Origin::signed(ALICE); + let dispatch_status = message_call.dispatch(alice_origin); + assert_ok!(dispatch_status); + }); + + AcurastParachain::execute_with(|| { + use acurast_runtime::pallet_acurast::Event::JobRegistrationRemoved; + use acurast_runtime::pallet_acurast::StoredJobRegistration; + use acurast_runtime::{Event, Runtime, System}; + + let events = System::events(); + let script: Script = SCRIPT_BYTES.to_vec().try_into().unwrap(); + let _p_store = StoredJobRegistration::::get(ALICE, script); + assert!(events + .iter() + .any(|event| matches!(event.event, Event::Acurast(JobRegistrationRemoved { .. })))); + }); + } + + #[test] + fn update_allowed_sources() { + Network::reset(); + + register(); + + // check that job is stored in the context of this test + AcurastParachain::execute_with(|| { + use acurast_runtime::pallet_acurast::StoredJobRegistration; + use acurast_runtime::Runtime; + + let script: Script = SCRIPT_BYTES.to_vec().try_into().unwrap(); + let p_store = StoredJobRegistration::::get(ALICE, script); + assert!(p_store.is_some()); + }); + + use frame_support::dispatch::Dispatchable; + use pallet_acurast::{AllowedSourcesUpdate, Script}; + + let rand_array: [u8; 32] = rand::random(); + let source = sp_runtime::AccountId32::new(rand_array); + + CumulusParachain::execute_with(|| { + use crate::pallet::Call::update_allowed_sources; + use proxy_runtime::Call::AcurastProxy; + + let update = AllowedSourcesUpdate { + operation: ListUpdateOperation::Add, + account_id: source.clone(), + }; + + let message_call = AcurastProxy(update_allowed_sources { + script: SCRIPT_BYTES.to_vec().try_into().unwrap(), + updates: vec![update], + }); + + let alice_origin = proxy_runtime::Origin::signed(ALICE); + let dispatch_status = message_call.dispatch(alice_origin); + assert_ok!(dispatch_status); + }); + + AcurastParachain::execute_with(|| { + use acurast_runtime::pallet_acurast::Event::AllowedSourcesUpdated; + use acurast_runtime::pallet_acurast::StoredJobRegistration; + use acurast_runtime::{Event, Runtime, System}; + + let events = System::events(); + let script: Script = SCRIPT_BYTES.to_vec().try_into().unwrap(); + let p_store = StoredJobRegistration::::get(ALICE, script); + + // source in storage same as one submitted to proxy + let found_source: &sp_runtime::AccountId32 = + &p_store.unwrap().allowed_sources.unwrap()[0]; + assert_eq!(*found_source, source); + + // event emitted + assert!(events + .iter() + .any(|event| matches!(event.event, Event::Acurast(AllowedSourcesUpdated { .. })))); + }); + } + + #[test] + fn fulfill() { + Network::reset(); + + register(); + + let bob = sp_runtime::AccountId32::new(rand::random()); + + // check that job is stored in the context of this test + AcurastParachain::execute_with(|| { + use acurast_runtime::pallet_acurast::StoredJobAssignment; + use acurast_runtime::Runtime; + + let script: Script = SCRIPT_BYTES.to_vec().try_into().unwrap(); + + StoredJobAssignment::::set(bob.clone(), Some(vec![(ALICE, script)])); + }); + + use frame_support::dispatch::Dispatchable; + use pallet_acurast::Script; + + CumulusParachain::execute_with(|| { + use crate::pallet::Call::fulfill; + use proxy_runtime::Call::AcurastProxy; + + let payload: [u8; 32] = rand::random(); + + let fulfillment = Fulfillment { + script: SCRIPT_BYTES.to_vec().try_into().unwrap(), + payload: payload.to_vec(), + }; + + let message_call = AcurastProxy(fulfill { + fulfillment, + requester: sp_runtime::MultiAddress::Id(ALICE), + }); + + let bob_origin = proxy_runtime::Origin::signed(bob); + let dispatch_status = message_call.dispatch(bob_origin); + assert_ok!(dispatch_status); + }); + + AcurastParachain::execute_with(|| { + use acurast_runtime::pallet_acurast::Event::ReceivedFulfillment; + use acurast_runtime::{Event, System}; + + let events = System::events(); + //event emitted + assert!(events + .iter() + .any(|event| matches!(event.event, Event::Acurast(ReceivedFulfillment { .. })))); + }); + } }