diff --git a/pallets/acurast-xcm-sender/src/mock/runtime/receiver_parachain.rs b/pallets/acurast-xcm-sender/src/mock/runtime/receiver_parachain.rs index 1716f360..cda67879 100644 --- a/pallets/acurast-xcm-sender/src/mock/runtime/receiver_parachain.rs +++ b/pallets/acurast-xcm-sender/src/mock/runtime/receiver_parachain.rs @@ -1,8 +1,8 @@ -use sp_std::prelude::*; use codec::{Decode, Encode}; +use frame_support::traits::Get; use frame_support::{ construct_runtime, parameter_types, - traits::{Everything, Nothing, ConstU32}, + traits::{ConstU32, Everything, Nothing}, weights::Weight, BoundedVec, }; @@ -12,7 +12,7 @@ use sp_runtime::{ traits::{AccountIdLookup, Hash}, AccountId32, }; -use frame_support::traits::Get; +use sp_std::prelude::*; use pallet_xcm::XcmPassthrough; use polkadot_core_primitives::BlockNumber as RelayBlockNumber; @@ -21,10 +21,8 @@ use polkadot_parachain::primitives::{ }; use xcm::{latest::prelude::*, VersionedXcm}; use xcm_builder::{ - AllowUnpaidExecutionFrom, - EnsureXcmOrigin, FixedRateOfFungible, FixedWeightBounds, LocationInverter, - NativeAsset, SignedAccountId32AsNative, - SignedToAccountId32, + AllowUnpaidExecutionFrom, EnsureXcmOrigin, FixedRateOfFungible, FixedWeightBounds, + LocationInverter, NativeAsset, SignedAccountId32AsNative, SignedToAccountId32, }; use xcm_executor::{Config, XcmExecutor}; pub type AccountId = AccountId32; @@ -170,10 +168,14 @@ pub mod mock_msg_queue { let location = (1, Parachain(sender.into())); match T::XcmExecutor::execute_xcm(location, xcm, max_weight.ref_time()) { Outcome::Error(e) => (Err(e.clone()), Event::Fail(Some(hash), e)), - Outcome::Complete(w) => (Ok(Weight::from_ref_time(w)), Event::Success(Some(hash))), + Outcome::Complete(w) => { + (Ok(Weight::from_ref_time(w)), Event::Success(Some(hash))) + } // As far as the caller is concerned, this was dispatched without error, so // we just report the weight used. - Outcome::Incomplete(w, e) => (Ok(Weight::from_ref_time(w)), Event::Fail(Some(hash), e)), + Outcome::Incomplete(w, e) => { + (Ok(Weight::from_ref_time(w)), Event::Fail(Some(hash), e)) + } } } Err(()) => ( @@ -187,10 +189,7 @@ pub mod mock_msg_queue { } impl XcmpMessageHandler for Pallet { - fn handle_xcmp_messages< - 'a, - I: Iterator, - >( + fn handle_xcmp_messages<'a, I: Iterator>( iter: I, max_weight: Weight, ) -> Weight { @@ -219,8 +218,8 @@ pub mod mock_msg_queue { ) -> 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)); @@ -229,7 +228,8 @@ pub mod mock_msg_queue { Self::deposit_event(Event::UnsupportedVersion(id)); } Ok(Ok(x)) => { - let outcome = T::XcmExecutor::execute_xcm(Parent, x.clone(), limit.ref_time()); + let outcome = + T::XcmExecutor::execute_xcm(Parent, x.clone(), limit.ref_time()); >::append(x); Self::deposit_event(Event::ExecutedDownward(id, outcome)); } @@ -268,8 +268,12 @@ type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic type Block = frame_system::mocking::MockBlock; pub struct AllowAcurastBarrier {} -impl pallet_acurast_receiver::traits::ParachainBarrier for AllowAcurastBarrier { - fn ensure_xcm_origin(_: ::Origin) -> Result<(), sp_runtime::DispatchError> { +impl pallet_acurast_receiver::traits::ParachainBarrier + for AllowAcurastBarrier +{ + fn ensure_xcm_origin( + _: ::Origin, + ) -> Result<(), sp_runtime::DispatchError> { Ok(()) } } @@ -284,7 +288,10 @@ impl pallet_acurast_receiver::Config for Runtime { pub struct OnFulfillment; impl pallet_acurast_receiver::traits::OnFulfillment for OnFulfillment { - fn fulfill(_fulfillment: Vec, _parameters: Option>,) -> frame_support::pallet_prelude::DispatchResultWithPostInfo { + fn fulfill( + _fulfillment: Vec, + _parameters: Option>, + ) -> frame_support::pallet_prelude::DispatchResultWithPostInfo { Ok(().into()) } } diff --git a/pallets/acurast-xcm-sender/src/mock/runtime/relay_chain.rs b/pallets/acurast-xcm-sender/src/mock/runtime/relay_chain.rs index bbe3349a..5ee208e3 100644 --- a/pallets/acurast-xcm-sender/src/mock/runtime/relay_chain.rs +++ b/pallets/acurast-xcm-sender/src/mock/runtime/relay_chain.rs @@ -44,7 +44,6 @@ impl frame_system::Config for Runtime { type MaxConsumers = frame_support::traits::ConstU32<16>; } - impl shared::Config for Runtime {} impl configuration::Config for Runtime { diff --git a/pallets/acurast/Cargo.toml b/pallets/acurast/Cargo.toml index c54f0577..039a8794 100644 --- a/pallets/acurast/Cargo.toml +++ b/pallets/acurast/Cargo.toml @@ -4,8 +4,8 @@ authors = ["Papers AG"] description = "FRAME pallet template for defining custom runtime logic." version = "0.0.1" license = "Unlicense" -homepage = "https://substrate.io" -repository = "https://github.com/paritytech/substrate/" +homepage = "https://docs.acurast.com/" +repository = "https://github.com/acurast" edition = "2021" [package.metadata.docs.rs] @@ -46,6 +46,7 @@ p384 = { package = "p384_vendored", path = "p384", default-features = false, fea sha2 = { version = "0.10", default-features = false } num-bigint = { version = "0.4.3", default-features = false } ecdsa-vendored = { package = "ecdsa_vendored", path = "p384/ecdsa", default-features = false } +sp-runtime = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.29" } # benchmarks hex-literal = { version = "0.3", optional = true } @@ -67,9 +68,6 @@ pallet-timestamp = { git = "https://github.com/paritytech/substrate", default-fe parachain-info = { git = "https://github.com/paritytech/cumulus", default-features = false, branch = "polkadot-v0.9.29" } parachains-common = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.29", default-features = false } -# Acurast -acurast-p256-crypto = { path = "../../p256-crypto" } - [features] default = ["std"] runtime-benchmarks = [ @@ -83,7 +81,6 @@ runtime-benchmarks = [ ] std = [ - "acurast-p256-crypto/std", "asn1/std", "base64/std", "codec/std", diff --git a/pallets/acurast/p384/src/ecdsa.rs b/pallets/acurast/p384/src/ecdsa.rs index dde1ef5c..ea03c2f4 100644 --- a/pallets/acurast/p384/src/ecdsa.rs +++ b/pallets/acurast/p384/src/ecdsa.rs @@ -54,10 +54,7 @@ impl VerifyPrimitive for AffinePoint {} #[cfg(all(test, feature = "ecdsa"))] mod tests { - use crate::{ - ecdsa::{SigningKey}, - SecretKey, - }; + use crate::{ecdsa::SigningKey, SecretKey}; #[test] fn signing_secret_key_equivalent() { @@ -83,5 +80,4 @@ mod tests { use crate::{test_vectors::ecdsa::ECDSA_TEST_VECTORS, NistP384}; ecdsa_vendored::new_verification_test!(NistP384, ECDSA_TEST_VECTORS); } - } diff --git a/pallets/acurast/src/attestation.rs b/pallets/acurast/src/attestation.rs index 7f1d8b9f..7032d528 100644 --- a/pallets/acurast/src/attestation.rs +++ b/pallets/acurast/src/attestation.rs @@ -304,7 +304,7 @@ pub fn validate_certificate_chain<'a>( } /// The list of trusted root certificates, as decoded bytes arrays. [Source](https://developer.android.com/training/articles/security-key-attestation#root_certificate) -const TRUSTED_ROOT_CERTS: & [&[u8]] = &[ +const TRUSTED_ROOT_CERTS: &[&[u8]] = &[ // base64 equivalent: r"MIIFYDCCA0igAwIBAgIJAOj6GWMU0voYMA0GCSqGSIb3DQEBCwUAMBsxGTAXBgNVBAUTEGY5MjAwOWU4NTNiNmIwNDUwHhcNMTYwNTI2MTYyODUyWhcNMjYwNTI0MTYyODUyWjAbMRkwFwYDVQQFExBmOTIwMDllODUzYjZiMDQ1MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAr7bHgiuxpwHsK7Qui8xUFmOr75gvMsd/dTEDDJdSSxtf6An7xyqpRR90PL2abxM1dEqlXnf2tqw1Ne4Xwl5jlRfdnJLmN0pTy/4lj4/7tv0Sk3iiKkypnEUtR6WfMgH0QZfKHM1+di+y9TFRtv6y//0rb+T+W8a9nsNL/ggjnar86461qO0rOs2cXjp3kOG1FEJ5MVmFmBGtnrKpa73XpXyTqRxB/M0n1n/W9nGqC4FSYa04T6N5RIZGBN2z2MT5IKGbFlbC8UrW0DxW7AYImQQcHtGl/m00QLVWutHQoVJYnFPlXTcHYvASLu+RhhsbDmxMgJJ0mcDpvsC4PjvB+TxywElgS70vE0XmLD+OJtvsBslHZvPBKCOdT0MS+tgSOIfga+z1Z1g7+DVagf7quvmag8jfPioyKvxnK/EgsTUVi2ghzq8wm27ud/mIM7AY2qEORR8Go3TVB4HzWQgpZrt3i5MIlCaY504LzSRiigHCzAPlHws+W0rB5N+er5/2pJKnfBSDiCiFAVtCLOZ7gLiMm0jhO2B6tUXHI/+MRPjy02i59lINMRRev56GKtcd9qO/0kUJWdZTdA2XoS82ixPvZtXQpUpuL12ab+9EaDK8Z4RHJYYfCT3Q5vNAXaiWQ+8PTWm2QgBR/bkwSWc+NpUFgNPN9PvQi8WEg5UmAGMCAwEAAaOBpjCBozAdBgNVHQ4EFgQUNmHhAHyIBQlRi0RsR/8aTMnqTxIwHwYDVR0jBBgwFoAUNmHhAHyIBQlRi0RsR/8aTMnqTxIwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwQAYDVR0fBDkwNzA1oDOgMYYvaHR0cHM6Ly9hbmRyb2lkLmdvb2dsZWFwaXMuY29tL2F0dGVzdGF0aW9uL2NybC8wDQYJKoZIhvcNAQELBQADggIBACDIw41L3KlXG0aMiS//cqrG+EShHUGo8HNsw30W1kJtjn6UBwRM6jnmiwfBPb8VA91chb2vssAtX2zbTvqBJ9+LBPGCdw/E53Rbf86qhxKaiAHOjpvAy5Y3m00mqC0w/Zwvju1twb4vhLaJ5NkUJYsUS7rmJKHHBnETLi8GFqiEsqTWpG/6ibYCv7rYDBJDcR9W62BW9jfIoBQcxUCUJouMPH25lLNcDc1ssqvC2v7iUgI9LeoM1sNovqPmQUiG9rHli1vXxzCyaMTjwftkJLkf6724DFhuKug2jITV0QkXvaJWF4nUaHOTNA4uJU9WDvZLI1j83A+/xnAJUucIv/zGJ1AMH2boHqF8CY16LpsYgBt6tKxxWH00XcyDCdW2KlBCeqbQPcsFmWyWugxdcekhYsAWyoSf818NUsZdBWBaR/OukXrNLfkQ79IyZohZbvabO/X+MVT3rriAoKc8oE2Uws6DF+60PV7/WIPjNvXySdqspImSN78mflxDqwLqRBYkA3I75qppLGG9rp7UCdRjxMl8ZDBld+7yvHVgt1cVzJx9xnyGCC23UaicMDSXYrB4I4WHXPGjxhZuCuPBLTdOLU8YRvMYdEvYebWHMpvwGCF6bAx3JBpIeOQ1wDB5y0USicV3YgYGmi+NZfhA4URSh77Yd6uuJOJENRaNVTzk" include_bytes!("./__root_certs__/00E8FA196314D2FA18.cer"), // base64 equivalent: r"MIIFHDCCAwSgAwIBAgIJANUP8luj8tazMA0GCSqGSIb3DQEBCwUAMBsxGTAXBgNVBAUTEGY5MjAwOWU4NTNiNmIwNDUwHhcNMTkxMTIyMjAzNzU4WhcNMzQxMTE4MjAzNzU4WjAbMRkwFwYDVQQFExBmOTIwMDllODUzYjZiMDQ1MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAr7bHgiuxpwHsK7Qui8xUFmOr75gvMsd/dTEDDJdSSxtf6An7xyqpRR90PL2abxM1dEqlXnf2tqw1Ne4Xwl5jlRfdnJLmN0pTy/4lj4/7tv0Sk3iiKkypnEUtR6WfMgH0QZfKHM1+di+y9TFRtv6y//0rb+T+W8a9nsNL/ggjnar86461qO0rOs2cXjp3kOG1FEJ5MVmFmBGtnrKpa73XpXyTqRxB/M0n1n/W9nGqC4FSYa04T6N5RIZGBN2z2MT5IKGbFlbC8UrW0DxW7AYImQQcHtGl/m00QLVWutHQoVJYnFPlXTcHYvASLu+RhhsbDmxMgJJ0mcDpvsC4PjvB+TxywElgS70vE0XmLD+OJtvsBslHZvPBKCOdT0MS+tgSOIfga+z1Z1g7+DVagf7quvmag8jfPioyKvxnK/EgsTUVi2ghzq8wm27ud/mIM7AY2qEORR8Go3TVB4HzWQgpZrt3i5MIlCaY504LzSRiigHCzAPlHws+W0rB5N+er5/2pJKnfBSDiCiFAVtCLOZ7gLiMm0jhO2B6tUXHI/+MRPjy02i59lINMRRev56GKtcd9qO/0kUJWdZTdA2XoS82ixPvZtXQpUpuL12ab+9EaDK8Z4RHJYYfCT3Q5vNAXaiWQ+8PTWm2QgBR/bkwSWc+NpUFgNPN9PvQi8WEg5UmAGMCAwEAAaNjMGEwHQYDVR0OBBYEFDZh4QB8iAUJUYtEbEf/GkzJ6k8SMB8GA1UdIwQYMBaAFDZh4QB8iAUJUYtEbEf/GkzJ6k8SMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgIEMA0GCSqGSIb3DQEBCwUAA4ICAQBOMaBc8oumXb2voc7XCWnuXKhBBK3e2KMGz39t7lA3XXRe2ZLLAkLM5y3J7tURkf5a1SutfdOyXAmeE6SRo83Uh6WszodmMkxK5GM4JGrnt4pBisu5igXEydaW7qq2CdC6DOGjG+mEkN8/TA6p3cnoL/sPyz6evdjLlSeJ8rFBH6xWyIZCbrcpYEJzXaUOEaxxXxgYz5/cTiVKN2M1G2okQBUIYSY6bjEL4aUN5cfo7ogP3UvliEo3Eo0YgwuzR2v0KR6C1cZqZJSTnghIC/vAD32KdNQ+c3N+vl2OTsUVMC1GiWkngNx1OO1+kXW+YTnnTUOtOIswUP/Vqd5SYgAImMAfY8U9/iIgkQj6T2W6FsScy94IN9fFhE1UtzmLoBIuUFsVXJMTz+Jucth+IqoWFua9v1R93/k98p41pjtFX+H8DslVgfP097vju4KDlqN64xV1grw3ZLl4CiOe/A91oeLm2UHOq6wn3esB4r2EIQKb6jTVGu5sYCcdWpXr0AUVqcABPdgL+H7qJguBw09ojm6xNIrw2OocrDKsudk/okr/AwqEyPKw9WnMlQgLIKw1rODG2NvU9oR3GVGdMkUBZutL8VuFkERQGt6vQ2OCw0sV47VMkuYbacK/xyZFiRcrPJPb41zgbQj9XAEyLKCHex0SdDrx+tWUDqG8At2JHA==" diff --git a/pallets/acurast/src/benchmarking.rs b/pallets/acurast/src/benchmarking.rs index 997c701f..2f84453d 100644 --- a/pallets/acurast/src/benchmarking.rs +++ b/pallets/acurast/src/benchmarking.rs @@ -1,6 +1,3 @@ -use super::*; -use crate::utils::validate_and_extract_attestation; -use crate::Pallet as Acurast; use frame_benchmarking::{account, benchmarks, whitelist_account}; use frame_support::{ assert_ok, @@ -10,8 +7,14 @@ use frame_support::{ use frame_system::RawOrigin; use hex_literal::hex; use sp_std::prelude::*; -use types::{AllowedSourcesUpdate, JobAssignmentUpdate}; -use xcm::prelude::*; + +pub use pallet::Config; +use types::{AttestationChain, JobRegistration, JobRegistrationFor, Script}; + +use crate::utils::validate_and_extract_attestation; +use crate::Pallet as Acurast; + +use super::*; pub type Balance = u128; @@ -28,29 +31,15 @@ pub fn assert_last_event(generic_event: ::Event) { frame_system::Pallet::::assert_last_event(generic_event.into()); } -pub fn job_registration_n(extra: T::RegistrationExtra) -> JobRegistrationFor -where - <::RewardManager as RewardManager>::Reward: From, -{ +pub fn job_registration(extra: T::RegistrationExtra) -> JobRegistrationFor { return JobRegistration { script: script(), allowed_sources: None, - extra, - reward: owned_asset().into(), allow_only_verified_sources: false, + extra, }; } -pub fn owned_asset() -> MultiAsset { - MultiAsset { - id: Concrete(MultiLocation { - parents: 1, - interior: X3(Parachain(1000), PalletInstance(50), GeneralIndex(22)), - }), - fun: Fungible(INITIAL_BALANCE / 2), - } -} - pub fn script() -> Script { SCRIPT_BYTES.to_vec().try_into().unwrap() } @@ -84,7 +73,7 @@ where use pallet_assets::Pallet as Assets; let caller: T::AccountId = account("token_account", 0, SEED); whitelist_account!(caller); - let pallet_account: T::AccountId = T::PalletId::get().into_account_truncating(); + let pallet_account: T::AccountId = ::PalletId::get().into_account_truncating(); let pallet_origin: T::Origin = RawOrigin::Signed(pallet_account.clone()).into(); T::Currency::make_free_balance_be(&caller, u32::MAX.into()); @@ -111,14 +100,14 @@ where fn register_job(submit: bool) -> (T::AccountId, JobRegistrationFor) where T: pallet_assets::Config, - ::RegistrationExtra: Default, - <::RewardManager as RewardManager>::Reward: From, ::AssetId: From, ::Balance: From, + ::RegistrationExtra: Default, { let caller: T::AccountId = token_22_funded_account::(); whitelist_account!(caller); - let job = job_registration_n::(Default::default()); + + let job = job_registration::(Default::default()); if submit { let register_call = @@ -145,7 +134,7 @@ where job_id: (caller.clone(), job.script.clone()), }; - // create processor account by giving it over existential deposit + // create processor account by giving t over existential deposit T::Currency::make_free_balance_be(&processor_account, u32::MAX.into()); if submit { @@ -161,32 +150,28 @@ where benchmarks! { where_clause { where - T: pallet_assets::Config, - ::RegistrationExtra: Default, - <::RewardManager as RewardManager>::Reward: From, + T: pallet_assets::Config + pallet_timestamp::Config, ::AssetId: From, ::Balance: From, + ::RegistrationExtra: Default, ::AccountId: From<[u8; 32]>, - T: pallet_timestamp::Config, ::Moment: From, } register { let (caller, job) = register_job::(false); - }: _(RawOrigin::Signed(caller.clone()), job.clone()) verify { - assert_last_event::(Event::JobRegistrationStored( + assert_last_event::(Event::::JobRegistrationStored( job, caller ).into()); } deregister { let (caller, job) = register_job::(true); - }: _(RawOrigin::Signed(caller.clone()), job.script.clone()) verify { - assert_last_event::(Event::JobRegistrationRemoved( + assert_last_event::(Event::::JobRegistrationRemoved( job.script, caller ).into()); } @@ -205,17 +190,6 @@ benchmarks! { ).into()); } - update_job_assignments { - let (caller, job) = register_job::(true); - let job_assignment = assign_job::(false, caller.clone(), job.clone()); - - }: _(RawOrigin::Signed(caller.clone()), vec![job_assignment.clone()]) - verify { - assert_last_event::(Event::JobAssignmentUpdate( - caller, vec![job_assignment] - ).into()); - } - fulfill { let (caller, job) = register_job::(true); let job_assignment = assign_job::(true, caller.clone(), job.clone()); @@ -265,5 +239,5 @@ benchmarks! { ).into()); } - impl_benchmark_test_suite!(Acurast, mock::ExtBuilder::default().build(), mock::Test) + impl_benchmark_test_suite!(Acurast, mock::ExtBuilder::default().build(), mock::Test); } diff --git a/pallets/acurast/src/lib.rs b/pallets/acurast/src/lib.rs index cac0e76f..16737f81 100644 --- a/pallets/acurast/src/lib.rs +++ b/pallets/acurast/src/lib.rs @@ -9,15 +9,13 @@ mod tests; mod benchmarking; mod attestation; -pub mod payments; mod traits; mod types; -mod utils; +pub mod utils; pub mod weights; pub mod xcm_adapters; pub use pallet::*; -pub use payments::*; pub use traits::*; pub use types::*; @@ -37,14 +35,12 @@ pub mod pallet { 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; + type RegistrationExtra: Parameter + Member; /// The fulfillment router to route a job fulfillment to its final destination. type FulfillmentRouter: FulfillmentRouter; /// The max length of the allowed sources list for a registration. #[pallet::constant] type MaxAllowedSources: Get; - /// Logic for locking and paying tokens for job execution - type RewardManager: RewardManager; /// The ID for this pallet #[pallet::constant] type PalletId: Get; @@ -54,12 +50,14 @@ pub mod pallet { type JobAssignmentUpdateBarrier: JobAssignmentUpdateBarrier; /// Timestamp type UnixTime: UnixTime; - // Weight Logic + /// Hooks used by tightly coupled subpallets. + type JobHooks: JobHooks; + /// Weight Info for extrinsics. Needs to include weight of hooks called. The weights in this pallet or only correct when using the default hooks [()]. type WeightInfo: WeightInfo; } #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] + #[pallet::generate_store(pub (super) trait Store)] #[pallet::without_storage_info] pub struct Pallet(_); @@ -94,7 +92,7 @@ pub mod pallet { StorageMap<_, Blake2_128Concat, T::AccountId, Vec>>; #[pallet::event] - #[pallet::generate_deposit(pub(super) fn deposit_event)] + #[pallet::generate_deposit(pub (super) fn deposit_event)] pub enum Event { /// A registration was successfully stored. [registration, who] JobRegistrationStored(JobRegistrationFor, T::AccountId), @@ -167,6 +165,8 @@ pub mod pallet { AttestationPublicKeyDoesNotMatchSource, /// Job assignment update not allowed. JobAssignmentUpdateNotAllowed, + /// Calling a job hook produced an error. + JobHookFailed, } #[pallet::hooks] @@ -175,7 +175,7 @@ pub mod pallet { #[pallet::call] impl Pallet { /// Registers a job by providing a [JobRegistration]. If a job for the same script was previously registered, it will be overwritten. - #[pallet::weight(::WeightInfo::register())] + #[pallet::weight(< T as Config >::WeightInfo::register())] pub fn register( origin: OriginFor, registration: JobRegistrationFor, @@ -203,27 +203,28 @@ pub mod pallet { ); } - T::RewardManager::lock_reward( - registration.reward.clone(), - T::Lookup::unlookup(who.clone()), - )?; - >::insert(&who, ®istration.script, registration.clone()); + + ::JobHooks::register_hook(&who, ®istration)?; + Self::deposit_event(Event::JobRegistrationStored(registration, who)); Ok(().into()) } /// Deregisters a job for the given script. - #[pallet::weight(::WeightInfo::deregister())] + #[pallet::weight(< T as Config >::WeightInfo::deregister())] pub fn deregister(origin: OriginFor, script: Script) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; >::remove(&who, &script); + + ::JobHooks::deregister_hook(&who, &script)?; + Self::deposit_event(Event::JobRegistrationRemoved(script, who)); Ok(().into()) } /// Updates the allowed sources list of a [JobRegistration]. - #[pallet::weight(::WeightInfo::update_allowed_sources())] + #[pallet::weight(< T as Config >::WeightInfo::update_allowed_sources())] pub fn update_allowed_sources( origin: OriginFor, script: Script, @@ -268,17 +269,18 @@ pub mod pallet { allowed_sources, extra: registration.extra.clone(), allow_only_verified_sources: registration.allow_only_verified_sources, - reward: registration.reward.clone(), }, ); + ::JobHooks::update_allowed_sources_hook(&who, &script, &updates)?; + Self::deposit_event(Event::AllowedSourcesUpdated(who, registration, updates)); Ok(().into()) } /// Assigns jobs to [AccountId]s. Those accounts can then later call `fulfill` for those jobs. - #[pallet::weight(::WeightInfo::update_job_assignments())] + #[pallet::weight(< T as Config >::WeightInfo::update_job_assignments())] pub fn update_job_assignments( origin: OriginFor, updates: Vec>, @@ -306,7 +308,7 @@ pub mod pallet { } /// Fulfills a previously registered job. - #[pallet::weight(::WeightInfo::fulfill())] + #[pallet::weight(< T as Config >::WeightInfo::fulfill())] pub fn fulfill( origin: OriginFor, fulfillment: Fulfillment, @@ -315,43 +317,33 @@ pub mod pallet { let who = ensure_signed(origin.clone())?; let requester = T::Lookup::lookup(requester)?; - // 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)?; - // find registration - let registration = >::get(&job_id.0, &fulfillment.script) - .ok_or(Error::::JobRegistrationNotFound)?; + let registration = + >::get(&requester.clone(), &fulfillment.script) + .ok_or(Error::::JobRegistrationNotFound)?; ensure_source_allowed::(&who, ®istration)?; - T::RewardManager::pay_reward( - registration.reward.clone(), - T::Lookup::unlookup(who.clone()), + ::JobHooks::fulfill_hook( + &who, + &fulfillment, + requester.clone(), + ®istration, )?; - // route fulfillment let info = T::FulfillmentRouter::received_fulfillment( origin, who.clone(), fulfillment.clone(), registration.clone(), - job_id.0, + requester.clone(), )?; - // 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, + requester, )); Ok(info) } @@ -363,7 +355,7 @@ pub mod pallet { /// - If the represented chain is valid, the [Attestation] details are stored. An existing attestion for signing account gets overwritten. /// /// Revocation: Each atttestation is stored with the unique IDs of the certificates on the chain proofing the attestation's validity. - #[pallet::weight(::WeightInfo::submit_attestation())] + #[pallet::weight(< T as Config >::WeightInfo::submit_attestation())] pub fn submit_attestation( origin: OriginFor, attestation_chain: AttestationChain, @@ -384,7 +376,7 @@ pub mod pallet { Ok(().into()) } - #[pallet::weight(::WeightInfo::register())] + #[pallet::weight(< T as Config >::WeightInfo::register())] pub fn update_certificate_revocation_list( origin: OriginFor, updates: Vec, diff --git a/pallets/acurast/src/mock.rs b/pallets/acurast/src/mock.rs index b1ff0bb6..735b2d4c 100644 --- a/pallets/acurast/src/mock.rs +++ b/pallets/acurast/src/mock.rs @@ -1,16 +1,15 @@ +use frame_support::sp_runtime; use frame_support::traits::Everything; use frame_support::weights::Weight; use frame_support::{pallet_prelude::GenesisBuild, PalletId}; use hex_literal::hex; use sp_io; use sp_runtime::traits::{AccountIdConversion, AccountIdLookup, BlakeTwo256, ConstU128, ConstU32}; -use sp_runtime::{generic, parameter_types, AccountId32, Percent}; -use xcm::prelude::*; +use sp_runtime::{generic, parameter_types, AccountId32}; use crate::{ - payments, AssetBarrier, AttestationChain, FeeManager, Fulfillment, JobAssignmentUpdate, - JobAssignmentUpdateBarrier, JobRegistration, RevocationListUpdateBarrier, Reward, Script, - SerialNumber, + AttestationChain, Fulfillment, JobAssignmentUpdate, JobAssignmentUpdateBarrier, + JobRegistration, RevocationListUpdateBarrier, Script, SerialNumber, }; type AccountId = AccountId32; @@ -20,6 +19,7 @@ pub type Balance = u128; pub type BlockNumber = u32; pub struct Barrier; + impl RevocationListUpdateBarrier for Barrier { fn can_update_revocation_list( origin: &::AccountId, @@ -28,6 +28,7 @@ impl RevocationListUpdateBarrier for Barrier { AllowedRevocationListUpdate::get().contains(origin) } } + impl JobAssignmentUpdateBarrier for Barrier { fn can_update_assigned_jobs( origin: &::AccountId, @@ -37,36 +38,8 @@ impl JobAssignmentUpdateBarrier for Barrier { } } -impl AssetBarrier for Barrier { - fn can_use_asset(_asset: &MultiAsset) -> bool { - true - } -} - -impl Reward for MultiAsset { - type AssetId = u32; - type Balance = u128; - type Error = (); - - fn try_get_asset_id(&self) -> Result { - match &self.id { - Concrete(location) => match location.last() { - Some(GeneralIndex(id)) => (*id).try_into().map_err(|_| ()), - _ => Err(()), - }, - Abstract(_) => Err(()), - } - } - - fn try_get_amount(&self) -> Result { - match &self.fun { - Fungible(amount) => Ok(*amount), - _ => Err(()), - } - } -} - pub struct Router; + impl crate::FulfillmentRouter for Router { fn received_fulfillment( _origin: frame_system::pallet_prelude::OriginFor, @@ -79,18 +52,8 @@ impl crate::FulfillmentRouter for Router { } } -pub struct FeeManagerImpl; -impl FeeManager for FeeManagerImpl { - fn get_fee_percentage() -> Percent { - Percent::from_percent(30) - } - - fn pallet_id() -> PalletId { - PalletId(*b"acurfees") - } -} - pub struct ExtBuilder; + impl ExtBuilder { pub fn build(self) -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::default() @@ -107,31 +70,6 @@ impl ExtBuilder { ) .unwrap(); - pallet_balances::GenesisConfig:: { - balances: vec![ - (alice_account_id(), INITIAL_BALANCE), - (pallet_assets_account(), INITIAL_BALANCE), - (pallet_fees_account(), INITIAL_BALANCE), - (bob_account_id(), INITIAL_BALANCE), - (processor_account_id(), INITIAL_BALANCE), - ], - } - .assimilate_storage(&mut t) - .unwrap(); - - // give alice an initial balance of token 22 (backed by statemint) to pay for a job - // get the MultiAsset representing token 22 with owned_asset() - pallet_assets::GenesisConfig:: { - assets: vec![(22, pallet_assets_account(), false, 1_000)], - metadata: vec![(22, "test_payment".into(), "tpt".into(), 12.into())], - accounts: vec![ - (22, alice_account_id(), INITIAL_BALANCE), - (22, bob_account_id(), INITIAL_BALANCE), - ], - } - .assimilate_storage(&mut t) - .unwrap(); - let mut ext = sp_io::TestExternalities::new(t); ext.execute_with(|| System::set_block_number(1)); ext @@ -260,12 +198,12 @@ impl crate::Config for Test { type RegistrationExtra = (); type FulfillmentRouter = Router; type MaxAllowedSources = frame_support::traits::ConstU16<4>; - type RewardManager = payments::AssetRewardManager; type PalletId = AcurastPalletId; type RevocationListUpdateBarrier = Barrier; type JobAssignmentUpdateBarrier = Barrier; type UnixTime = pallet_timestamp::Pallet; type WeightInfo = crate::weights::WeightInfo; + type JobHooks = (); } pub fn events() -> Vec { @@ -297,18 +235,17 @@ pub fn invalid_script_2() -> Script { pub fn job_registration( allowed_sources: Option>, allow_only_verified_sources: bool, -) -> JobRegistration { +) -> JobRegistration { JobRegistration { script: script(), allowed_sources, allow_only_verified_sources, extra: (), - reward: owned_asset(), } } pub fn job_assignment_update_for( - registration: &JobRegistration, + registration: &JobRegistration, requester: Option, ) -> Vec> { vec![JobAssignmentUpdate { @@ -321,27 +258,25 @@ pub fn job_assignment_update_for( }] } -pub fn invalid_job_registration_1() -> JobRegistration { +pub fn invalid_job_registration_1() -> JobRegistration { JobRegistration { script: invalid_script_1(), allowed_sources: None, allow_only_verified_sources: false, - reward: owned_asset(), extra: (), } } -pub fn invalid_job_registration_2() -> JobRegistration { +pub fn invalid_job_registration_2() -> JobRegistration { JobRegistration { script: invalid_script_2(), allowed_sources: None, allow_only_verified_sources: false, - reward: owned_asset(), extra: (), } } -pub fn fulfillment_for(registration: &JobRegistration) -> Fulfillment { +pub fn fulfillment_for(registration: &JobRegistration) -> Fulfillment { Fulfillment { script: registration.script.clone(), payload: hex!("00").to_vec(), @@ -404,10 +339,6 @@ pub fn pallet_assets_account() -> ::AccountId { ::PalletId::get().into_account_truncating() } -pub fn pallet_fees_account() -> ::AccountId { - FeeManagerImpl::pallet_id().into_account_truncating() -} - pub fn alice_account_id() -> AccountId { [0; 32].into() } @@ -427,14 +358,3 @@ pub fn dave_account_id() -> AccountId { pub fn eve_account_id() -> AccountId { [4; 32].into() } - -// owned by alice -pub fn owned_asset() -> MultiAsset { - MultiAsset { - id: Concrete(MultiLocation { - parents: 1, - interior: X3(Parachain(1000), PalletInstance(50), GeneralIndex(22)), - }), - fun: Fungible(INITIAL_BALANCE / 2), - } -} diff --git a/pallets/acurast/src/tests.rs b/pallets/acurast/src/tests.rs index 3e48c950..c5adae28 100644 --- a/pallets/acurast/src/tests.rs +++ b/pallets/acurast/src/tests.rs @@ -36,12 +36,6 @@ fn test_job_registration() { assert_eq!( events(), [ - Event::Assets(pallet_assets::Event::Transferred { - asset_id: 22, - from: alice_account_id(), - to: pallet_assets_account(), - amount: INITIAL_BALANCE / 2 as u128, - }), Event::Acurast(crate::Event::JobRegistrationStored( registration.clone(), alice_account_id() @@ -197,12 +191,6 @@ fn test_update_allowed_sources() { assert_eq!( events(), [ - Event::Assets(pallet_assets::Event::Transferred { - asset_id: 22, - from: alice_account_id(), - to: pallet_assets_account(), - amount: INITIAL_BALANCE / 2 as u128, - }), Event::Acurast(crate::Event::JobRegistrationStored( registration_1.clone(), alice_account_id() @@ -259,18 +247,10 @@ fn test_update_allowed_sources_failure() { assert_eq!( events(), - [ - Event::Assets(pallet_assets::Event::Transferred { - asset_id: 22, - from: alice_account_id(), - to: pallet_assets_account(), - amount: INITIAL_BALANCE / 2 as u128, - }), - Event::Acurast(crate::Event::JobRegistrationStored( - registration.clone(), - alice_account_id() - )), - ] + [Event::Acurast(crate::Event::JobRegistrationStored( + registration.clone(), + alice_account_id() + )),] ); }); } @@ -297,12 +277,6 @@ fn test_assign_job() { assert_eq!( events(), [ - Event::Assets(pallet_assets::Event::Transferred { - asset_id: 22, - from: alice_account_id(), - to: pallet_assets_account(), - amount: INITIAL_BALANCE / 2 as u128, - }), Event::Acurast(crate::Event::JobRegistrationStored( registration.clone(), alice_account_id() @@ -334,18 +308,10 @@ fn test_assign_job_failure_1() { ); assert_eq!( events(), - [ - Event::Assets(pallet_assets::Event::Transferred { - asset_id: 22, - from: alice_account_id(), - to: pallet_assets_account(), - amount: INITIAL_BALANCE / 2 as u128, - }), - Event::Acurast(crate::Event::JobRegistrationStored( - registration.clone(), - alice_account_id() - )), - ] + [Event::Acurast(crate::Event::JobRegistrationStored( + registration.clone(), + alice_account_id() + )),] ); }); } @@ -365,18 +331,10 @@ fn test_assign_job_failure_2() { ); assert_eq!( events(), - [ - Event::Assets(pallet_assets::Event::Transferred { - asset_id: 22, - from: alice_account_id(), - to: pallet_assets_account(), - amount: INITIAL_BALANCE / 2 as u128, - }), - Event::Acurast(crate::Event::JobRegistrationStored( - registration.clone(), - alice_account_id() - )) - ] + [Event::Acurast(crate::Event::JobRegistrationStored( + registration.clone(), + alice_account_id() + ))] ); }); } @@ -406,12 +364,6 @@ fn test_fulfill() { assert_eq!( events(), [ - Event::Assets(pallet_assets::Event::Transferred { - asset_id: 22, - from: alice_account_id(), - to: pallet_assets_account(), - amount: INITIAL_BALANCE / 2 as u128, - }), Event::Acurast(crate::Event::JobRegistrationStored( registration.clone(), alice_account_id() @@ -420,18 +372,6 @@ fn test_fulfill() { alice_account_id(), updates )), - Event::Assets(pallet_assets::Event::Transferred { - asset_id: 22, - from: pallet_assets_account(), - to: pallet_fees_account(), - amount: 1_500_000_u128, - }), - Event::Assets(pallet_assets::Event::Transferred { - asset_id: 22, - from: pallet_assets_account(), - to: processor_account_id(), - amount: 3_500_000_u128, - }), Event::Acurast(crate::Event::ReceivedFulfillment( processor_account_id(), fulfillment, @@ -475,12 +415,6 @@ fn test_fulfill_failure_1() { assert_eq!( events(), [ - Event::Assets(pallet_assets::Event::Transferred { - asset_id: 22, - from: alice_account_id(), - to: pallet_assets_account(), - amount: INITIAL_BALANCE / 2 as u128, - }), Event::Acurast(crate::Event::JobRegistrationStored( registration.clone(), alice_account_id() @@ -561,29 +495,11 @@ fn test_submit_attestation_register_fulfill() { attestation, processor_account_id() )), - Event::Assets(pallet_assets::Event::Transferred { - asset_id: 22, - from: bob_account_id(), - to: pallet_assets_account(), - amount: INITIAL_BALANCE / 2 as u128, - }), Event::Acurast(crate::Event::JobRegistrationStored( registration.clone(), bob_account_id() )), Event::Acurast(crate::Event::JobAssignmentUpdate(bob_account_id(), updates)), - Event::Assets(pallet_assets::Event::Transferred { - asset_id: 22, - from: pallet_assets_account(), - to: pallet_fees_account(), - amount: 1_500_000_u128, - }), - Event::Assets(pallet_assets::Event::Transferred { - asset_id: 22, - from: pallet_assets_account(), - to: processor_account_id(), - amount: 3_500_000_u128, - }), Event::Acurast(crate::Event::ReceivedFulfillment( processor_account_id(), fulfillment, @@ -813,12 +729,6 @@ fn test_update_revocation_list_fulfill() { attestation, processor_account_id() )), - Event::Assets(pallet_assets::Event::Transferred { - asset_id: 22, - from: bob_account_id(), - to: pallet_assets_account(), - amount: INITIAL_BALANCE / 2 as u128, - }), Event::Acurast(crate::Event::JobRegistrationStored( registration.clone(), bob_account_id() diff --git a/pallets/acurast/src/traits.rs b/pallets/acurast/src/traits.rs index 69acf00d..d1cc26de 100644 --- a/pallets/acurast/src/traits.rs +++ b/pallets/acurast/src/traits.rs @@ -1,15 +1,16 @@ -use crate::{ - CertificateRevocationListUpdate, Config, Fulfillment, JobAssignmentUpdate, JobRegistrationFor, -}; use frame_support::{ - pallet_prelude::{DispatchResultWithPostInfo, Member}, + pallet_prelude::DispatchResultWithPostInfo, sp_runtime::{traits::StaticLookup, DispatchError}, weights::Weight, - Never, Parameter, }; use frame_system::pallet_prelude::OriginFor; use sp_std::prelude::*; +use crate::{ + AllowedSourcesUpdate, CertificateRevocationListUpdate, Config, Error, Fulfillment, + JobAssignmentUpdate, JobId, JobRegistrationFor, Script, StoredJobAssignment, +}; + /// This trait provides the interface for a fulfillment router. pub trait FulfillmentRouter { fn received_fulfillment( @@ -63,56 +64,74 @@ pub trait WeightInfo { fn update_certificate_revocation_list() -> Weight; } -pub trait Reward { - type AssetId; - type Balance; - type Error; - - fn try_get_asset_id(&self) -> Result; - fn try_get_amount(&self) -> Result; +pub trait JobHooks { + type Error: Into>; + fn register_hook( + who: &::AccountId, + registration: &JobRegistrationFor, + ) -> Result<(), DispatchError>; + fn deregister_hook( + who: &::AccountId, + script: &Script, + ) -> Result<(), DispatchError>; + fn update_allowed_sources_hook( + who: &::AccountId, + script: &Script, + updates: &Vec::AccountId>>, + ) -> Result<(), DispatchError>; + fn fulfill_hook( + who: &::AccountId, + fulfillment: &Fulfillment, + requester: ::Target, + registration: &JobRegistrationFor, + ) -> Result<(), DispatchError>; } -impl Reward for () { - type AssetId = Never; - type Balance = Never; +impl JobHooks for () { type Error = (); - - fn try_get_asset_id(&self) -> Result { - Err(()) + fn register_hook( + _who: &::AccountId, + _registration: &JobRegistrationFor, + ) -> Result<(), DispatchError> { + Ok(()) } - - fn try_get_amount(&self) -> Result { - Err(()) + fn deregister_hook( + _who: &::AccountId, + _script: &Script, + ) -> Result<(), DispatchError> { + Ok(()) } -} - -pub trait RewardManager { - type Reward: Parameter + Member + Reward; - - fn lock_reward( - reward: Self::Reward, - owner: ::Source, - ) -> Result<(), DispatchError>; - fn pay_reward( - reward: Self::Reward, - target: ::Source, - ) -> Result<(), DispatchError>; -} - -impl RewardManager for () { - type Reward = (); - - fn lock_reward( - _reward: Self::Reward, - _owner: <::Lookup as StaticLookup>::Source, + fn update_allowed_sources_hook( + _who: &::AccountId, + _script: &Script, + _updates: &Vec::AccountId>>, ) -> Result<(), DispatchError> { Ok(()) } - - fn pay_reward( - _reward: Self::Reward, - _target: <::Lookup as StaticLookup>::Source, + fn fulfill_hook( + who: &::AccountId, + fulfillment: &Fulfillment, + requester: ::Target, + _registration: &JobRegistrationFor, ) -> Result<(), DispatchError> { + // find assignment + let job_id: JobId = (requester.clone(), 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)?; + + // removed fulfilled job from assigned jobs + assigned_jobs.remove(job_index); + >::set(&who, Some(assigned_jobs)); + Ok(()) } } + +impl From<()> for Error { + fn from(_: ()) -> Self { + Self::JobHookFailed + } +} diff --git a/pallets/acurast/src/types.rs b/pallets/acurast/src/types.rs index eb3cefeb..64cf1f81 100644 --- a/pallets/acurast/src/types.rs +++ b/pallets/acurast/src/types.rs @@ -8,18 +8,14 @@ use crate::{ asn::{self, KeyDescription}, CertificateChainInput, CHAIN_MAX_LENGTH, }, - Config, RewardManager, + Config, }; pub(crate) const SCRIPT_PREFIX: &[u8] = b"ipfs://"; pub(crate) const SCRIPT_LENGTH: u32 = 53; -pub type RewardFor = <::RewardManager as RewardManager>::Reward; -pub type JobRegistrationFor = JobRegistration< - ::AccountId, - ::RegistrationExtra, - RewardFor, ->; +pub type JobRegistrationFor = + JobRegistration<::AccountId, ::RegistrationExtra>; /// Type representing the utf8 bytes of a string containing the value of an ipfs url. /// The ipfs url is expected to point to a script. @@ -88,11 +84,10 @@ pub enum ListUpdateOperation { /// Structure representing a job registration. #[derive(RuntimeDebug, Encode, Decode, TypeInfo, Clone, PartialEq)] -pub struct JobRegistration +pub struct JobRegistration where - AccountId: Parameter + Member + MaybeSerializeDeserialize + MaybeDisplay + Ord + MaxEncodedLen, - Reward: Parameter + Member, - Extra: Parameter + Member + MaxEncodedLen, + AccountId: Parameter + Member + MaybeSerializeDeserialize + MaybeDisplay + Ord, + Extra: Parameter + Member, { /// The script to execute. It is a vector of bytes representing a utf8 string. The string needs to be a ipfs url that points to the script. pub script: Script, @@ -100,8 +95,6 @@ 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: Reward, /// Extra parameters. This type can be configured through [Config::RegistrationExtra]. pub extra: Extra, } diff --git a/pallets/acurast/src/utils.rs b/pallets/acurast/src/utils.rs index d47945a5..22951dbe 100644 --- a/pallets/acurast/src/utils.rs +++ b/pallets/acurast/src/utils.rs @@ -12,7 +12,7 @@ use crate::{ ValidatingCertIds, }; -pub(crate) fn validate_and_extract_attestation( +pub fn validate_and_extract_attestation( source: &T::AccountId, attestation_chain: &AttestationChain, ) -> Result> { diff --git a/pallets/acurast/src/weights.rs b/pallets/acurast/src/weights.rs index a717e334..f96d3ef5 100644 --- a/pallets/acurast/src/weights.rs +++ b/pallets/acurast/src/weights.rs @@ -7,17 +7,17 @@ //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("acurast-dev"), DB CACHE: 1024 // Executed Command: -// ./target/release/acurast-node -// benchmark -// pallet -// --chain=acurast-dev -// --execution=wasm -// --wasm-execution=compiled -// --pallet=pallet_acurast -// --extrinsic=* -// --steps=50 -// --repeat=20 -// --output=./weights.rs +// ./target/release/acurast-node benchmark \ +// pallet \ +// --chain=acurast-dev \ +// --execution=wasm \ +// --wasm-execution=compiled \ +// --pallet=pallet_acurast \ +// --extrinsic \ +// "*" \ +// --steps=50 \ +// --repeat=20 \ +// --output=../acurast-core/pallets/acurast/src/weights.rs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] diff --git a/pallets/fee-manager/Cargo.toml b/pallets/fee-manager/Cargo.toml index d968f162..cc315048 100644 --- a/pallets/fee-manager/Cargo.toml +++ b/pallets/fee-manager/Cargo.toml @@ -4,7 +4,7 @@ authors = ["Papers AG"] description = "Pallet for managing the Acurast fees." version = "0.0.1" license = "Unlicense" -homepage = "https://acurast.com/" +homepage = "https://docs.acurast.com/" edition = "2021" publish = false repository = "https://github.com/acurast/" diff --git a/pallets/marketplace/Cargo.toml b/pallets/marketplace/Cargo.toml new file mode 100644 index 00000000..d03b51a5 --- /dev/null +++ b/pallets/marketplace/Cargo.toml @@ -0,0 +1,92 @@ +[package] +name = "pallet-acurast-marketplace" +authors = ["Papers AG"] +description = "FRAME pallet with the Acurast marketplace." +version = "0.0.1" +license = "Unlicense" +homepage = "https://docs.acurast.com/" +repository = "https://github.com/acurast" +edition = "2021" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", features = [ + "derive", +], default-features = false } +scale-info = { version = "2.0.0", features = [ + "derive", +], default-features = false } + +# Substrate +frame-benchmarking = { git = "https://github.com/paritytech/substrate", default-features = false, optional = true, branch = "polkadot-v0.9.29" } +frame-support = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.29" } +frame-system = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.29" } +sp-std = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.29" } +sp-runtime = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.29" } +sp-io = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.29" } +pallet-assets = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.29" } + +# Polkadot +xcm = { git = "https://github.com/paritytech/polkadot", default-features = false, branch = "release-v0.9.29" } +xcm-executor = { git = "https://github.com/paritytech/polkadot", default-features = false, branch = "release-v0.9.29" } +xcm-builder = { git = "https://github.com/paritytech/polkadot", default-features = false, branch = "release-v0.9.29" } + +# benchmarks +hex-literal = { version = "0.3", optional = true } +parachain-info = { git = "https://github.com/paritytech/cumulus", default-features = false, optional = true, branch = "polkadot-v0.9.29" } +pallet-balances = { git = "https://github.com/paritytech/substrate", default-features = false, optional = true, branch = "polkadot-v0.9.29" } +sp-core = { git = "https://github.com/paritytech/substrate", default-features = false, optional = true, branch = "polkadot-v0.9.29" } +pallet-timestamp = { git = "https://github.com/paritytech/substrate", default-features = false, optional = true, branch = "polkadot-v0.9.29" } +parachains-common = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.29", optional = true, default-features = false } + +pallet-acurast = { path = "../acurast", default-features = false} + +[dev-dependencies] +base64 = { version = "0.13.0", default-features = false, features = ["alloc"] } +hex-literal = "0.3" + +sp-version = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.29" } +sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.29" } +pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.29" } +pallet-timestamp = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.29" } +parachain-info = { git = "https://github.com/paritytech/cumulus", default-features = false, branch = "polkadot-v0.9.29" } +parachains-common = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.29", default-features = false } + +[features] +default = ["std"] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "hex-literal", + "parachain-info", + "pallet-balances", + "sp-core", + "pallet-timestamp", + "parachains-common", + "pallet-acurast/runtime-benchmarks" +] + +std = [ + "base64/std", + "codec/std", + "frame-benchmarking/std", + "frame-support/std", + "frame-system/std", + "pallet-assets/std", + "pallet-balances/std", + "pallet-timestamp/std", + "parachain-info/std", + "parachains-common/std", + "scale-info/std", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", + "sp-version/std", + "xcm-builder/std", + "xcm-executor/std", + "xcm/std", + "pallet-acurast/std" +] +try-runtime = ["frame-support/try-runtime"] diff --git a/pallets/marketplace/README.md b/pallets/marketplace/README.md new file mode 100644 index 00000000..049a2cd6 --- /dev/null +++ b/pallets/marketplace/README.md @@ -0,0 +1,69 @@ +# Acurast Marketplace Pallet +## 🚧🚧🚧 The project is still a work in progress 🚧🚧🚧 + +## Introduction + +The Acurast Marketplace Pallet extends the Acurast Pallet by resource advertisements and matching of registered jobs with suitable sources. + +The Pallet exposes a number of extrinsics additional to the strongly coupled (and required) core Marketplace Pallet. + +### advertise + +Allows the advertisement of resources by a source. An advertisement consists of: + +- A list of `pricing` options, each stating resource pricing for a selected reward type. +- The total `capacity` not to be exceeded in matching. +- A list of `allowed_consumers`. + +## Benchmarking + +Finding weights by means of benchmarking works a bit different for this pallet. The hooks contribute weight to extrinsics +that are defined in parent `pallet_acurast` that is tightly coupled to this pallet and calls into specific behaviour via hooks. + +**Therefore this pallet provides `weights_with_hooks::WeightInfoWithHooks` to be used _in place of_ the default weights for `pallet_acurast`.** + +### Test benchmarks in this pallet + +```shell +cargo test --package pallet-acurast --features runtime-benchmarks +``` + +```shell +cargo test --package pallet-acurast-marketplace --features runtime-benchmarks +``` + +### Generate weights +```shell +cargo clean && cargo build --profile=production --features runtime-benchmarks +``` + +Check if the expected extrinsics are listed +```shell +../../../acurast-substrate/target/production/acurast-node benchmark pallet --list +``` +Should contain: +```text +pallet_acurast, register +pallet_acurast, deregister +pallet_acurast, update_allowed_sources +pallet_acurast, fulfill +pallet_acurast, submit_attestation +pallet_acurast, update_certificate_revocation_list +pallet_acurast_marketplace, advertise +pallet_acurast_marketplace, delete_advertisement +pallet_acurast_marketplace, register +pallet_acurast_marketplace, deregister +pallet_acurast_marketplace, fulfill +``` + +Run benchmarks: +```shell +../../../acurast-substrate/target/release/acurast-node benchmark pallet --chain=acurast-dev --execution=wasm --wasm-execution=compiled --pallet=pallet_acurast --extrinsic "*" --steps=50 --repeat=20 --output=../acurast/src/weights.rs +``` + +```shell +../../../acurast-substrate/target/release/acurast-node benchmark pallet --chain=acurast-dev --execution=wasm --wasm-execution=compiled --pallet=pallet_acurast_marketplace --extrinsic "advertise,delete_advertisement" --steps=50 --repeat=20 --output=./src/weights.rs --template=./src/weights.hbs +``` +```shell +../../../acurast-substrate/target/release/acurast-node benchmark pallet --chain=acurast-dev --execution=wasm --wasm-execution=compiled --pallet=pallet_acurast_marketplace --extrinsic "register,deregister,fulfill,update_allowed_sources" --steps=50 --repeat=20 --output=./src/weights_with_hooks.rs --template=./src/weights_with_hooks.hbs +``` diff --git a/pallets/marketplace/src/benchmarking.rs b/pallets/marketplace/src/benchmarking.rs new file mode 100644 index 00000000..bfe78ce3 --- /dev/null +++ b/pallets/marketplace/src/benchmarking.rs @@ -0,0 +1,244 @@ +use frame_benchmarking::{account, benchmarks, whitelist_account}; +use frame_support::{ + assert_ok, + sp_runtime::traits::{AccountIdConversion, Get, StaticLookup}, + traits::Currency, +}; +use frame_system::RawOrigin; +use hex_literal::hex; +use sp_core::*; +use sp_runtime::traits::ConstU32; +use sp_runtime::BoundedVec; +use sp_std::prelude::*; + +pub use pallet::Config; +use pallet_acurast::Pallet as Acurast; +use pallet_acurast::{Event as AcurastEvent, Fulfillment, JobRegistrationFor, Script}; + +pub use crate::stub::*; +use crate::Pallet as AcurastMarketplace; + +use super::*; + +pub fn assert_last_event(generic_event: ::Event) { + frame_system::Pallet::::assert_last_event(generic_event.into()); +} + +pub fn assert_last_acurast_event(generic_event: ::Event) { + frame_system::Pallet::::assert_last_event(generic_event.into()); +} + +pub fn advertisement( + price_per_cpu_millisecond: u128, + capacity: u32, +) -> AdvertisementFor +where + ::RegistrationExtra: From>, + RewardFor: From, + ::AssetId: From, + ::AssetAmount: From, +{ + let mut pricing: BoundedVec< + PricingVariant<::AssetId, ::AssetAmount>, + ConstU32, + > = Default::default(); + let r = pricing.try_push(PricingVariant { + reward_asset: 0.into(), + price_per_cpu_millisecond: price_per_cpu_millisecond.into(), + bonus: 0.into(), + maximum_slash: 0.into(), + }); + assert!(r.is_ok(), "Expected Ok(_). Got {:#?}", r); + Advertisement { + pricing, + allowed_consumers: None, + capacity, + } +} + +pub fn job_registration_with_reward( + script: Script, + cpu_milliseconds: u128, + reward_value: u128, +) -> JobRegistrationFor +where + ::RegistrationExtra: From>, + RewardFor: From, +{ + let r = JobRequirements { + slots: 1, + cpu_milliseconds, + reward: asset(reward_value).into(), + }; + let r: ::RegistrationExtra = r.into(); + let r: ::RegistrationExtra = r.into(); + JobRegistrationFor:: { + script, + allowed_sources: None, + allow_only_verified_sources: false, + extra: r, + } +} + +pub fn script() -> Script { + SCRIPT_BYTES.to_vec().try_into().unwrap() +} + +fn token_22_funded_account() -> T::AccountId +where + T: pallet_assets::Config, + ::AssetId: From, + ::Balance: From, +{ + use pallet_assets::Pallet as Assets; + let caller: T::AccountId = account("token_account", 0, SEED); + whitelist_account!(caller); + let pallet_account: T::AccountId = ::PalletId::get().into_account_truncating(); + let pallet_origin: T::Origin = RawOrigin::Signed(pallet_account.clone()).into(); + + T::Currency::make_free_balance_be(&caller, u32::MAX.into()); + + // might fail if asset is already created in genesis config. Fail doesn't affect later mint + let _create_token_call = Assets::::create( + pallet_origin.clone(), + 22.into(), + T::Lookup::unlookup(pallet_account.clone()), + 10u32.into(), + ); + + let mint_token_call = Assets::::mint( + pallet_origin, + 22.into(), + T::Lookup::unlookup(caller.clone()), + INITIAL_BALANCE.into(), + ); + assert_ok!(mint_token_call); + + caller +} + +fn advertise_helper(submit: bool) -> (T::AccountId, AdvertisementFor) +where + T: pallet_assets::Config, + ::AssetId: From, + ::AssetAmount: From, + ::AssetId: From, + ::Balance: From, + ::RegistrationExtra: From>, + RewardFor: From, +{ + let caller: T::AccountId = token_22_funded_account::(); + whitelist_account!(caller); + + let ad = advertisement::(10000, 5); + + if submit { + let register_call = AcurastMarketplace::::advertise( + RawOrigin::Signed(caller.clone()).into(), + ad.clone(), + ); + assert_ok!(register_call); + } + + (caller, ad) +} + +fn register_helper(submit: bool) -> (T::AccountId, JobRegistrationFor) +where + T: pallet_assets::Config, + ::AssetId: From, + ::AssetAmount: From, + ::AssetId: From, + ::Balance: From, + ::RegistrationExtra: From>, + RewardFor: From, +{ + let caller: T::AccountId = token_22_funded_account::(); + whitelist_account!(caller); + + let job = job_registration_with_reward::(script(), 2, 20100); + + if submit { + let register_call = + Acurast::::register(RawOrigin::Signed(caller.clone()).into(), job.clone()); + assert_ok!(register_call); + } + + (caller, job) +} + +benchmarks! { + where_clause { where + T: pallet_assets::Config + pallet_acurast::Config, + ::RegistrationExtra: From>, + RewardFor: From, + ::AssetId: From, + ::AssetAmount: From, + ::AssetId: From, + ::Balance: From, + } + + advertise { + // just create the data, do not submit the actual call (we want to benchmark `advertise`) + let (caller, ad) = advertise_helper::(false); + }: _(RawOrigin::Signed(caller.clone()), ad.clone()) + verify { + assert_last_event::(Event::AdvertisementStored( + ad, caller + ).into()); + } + + delete_advertisement { + // create the data and submit so we have an add in storage to delete when benchmarking `delete_advertisement` + let (caller, _) = advertise_helper::(true); + }: _(RawOrigin::Signed(caller.clone())) + verify { + assert_last_event::(Event::AdvertisementRemoved( + caller + ).into()); + } + + register { + let _ = advertise_helper::(true); + let (caller, job) = register_helper::(false); + }: { + pallet_acurast::Pallet::::register(RawOrigin::Signed(caller.clone()).into(), job.clone())? + } + verify { + assert_last_acurast_event::(AcurastEvent::::JobRegistrationStored( + job, caller + ).into()); + } + + deregister { + let (caller, job) = register_helper::(true); + }: { + pallet_acurast::Pallet::::deregister(RawOrigin::Signed(caller.clone()).into(), job.script.clone())? + } + verify { + assert_last_acurast_event::(AcurastEvent::::JobRegistrationRemoved( + job.script, caller + ).into()); + } + + fulfill { + let (source, _) = advertise_helper::(true); + let (requester, job) = register_helper::(true); + let fulfillment = Fulfillment { + script: job.script.clone(), + payload: hex!("00").to_vec(), + }; + }: { + pallet_acurast::Pallet::::fulfill(RawOrigin::Signed(source.clone()).into(), fulfillment.clone(), T::Lookup::unlookup(requester.clone()))? + } + verify { + assert_last_acurast_event::(AcurastEvent::::ReceivedFulfillment( + source.clone(), + fulfillment, + job, + requester + ).into()); + } + + impl_benchmark_test_suite!(AcurastMarketplace, mock::ExtBuilder::default().build(), mock::Test); +} diff --git a/pallets/marketplace/src/lib.rs b/pallets/marketplace/src/lib.rs new file mode 100644 index 00000000..d2de5397 --- /dev/null +++ b/pallets/marketplace/src/lib.rs @@ -0,0 +1,476 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(test)] +pub mod mock; +#[cfg(any(test, feature = "runtime-benchmarks"))] +mod stub; +#[cfg(test)] +mod tests; + +#[cfg(feature = "runtime-benchmarks")] +pub mod benchmarking; + +pub mod payments; +pub mod types; +mod utils; +pub mod weights; +pub mod weights_with_hooks; + +pub use pallet::*; +pub use payments::*; +pub use types::*; + +#[frame_support::pallet] +pub mod pallet { + use frame_support::{ + dispatch::DispatchResultWithPostInfo, ensure, pallet_prelude::*, + sp_runtime::traits::StaticLookup, Blake2_128Concat, PalletId, + }; + use frame_system::pallet_prelude::*; + use pallet_acurast::{ + AllowedSourcesUpdate, Fulfillment, JobHooks, JobId, JobRegistrationFor, Script, + }; + use sp_runtime::traits::CheckedMul; + use sp_std::prelude::*; + + use crate::payments::{Reward, RewardFor}; + use crate::types::*; + use crate::utils::*; + use crate::weights::WeightInfo; + use crate::RewardManager; + + #[pallet::config] + pub trait Config: frame_system::Config + pallet_acurast::Config { + type Event: From> + + IsType<::Event> + + IsType<::Event>; + /// Extra structure to include in the registration of a job. + type RegistrationExtra: IsType<::RegistrationExtra> + + Into>>; + /// The ID for this pallet + #[pallet::constant] + type PalletId: Get; + type AssetId: Parameter + IsType< as Reward>::AssetId>; + type AssetAmount: Parameter + + CheckedMul + + From + + Ord + + IsType< as Reward>::AssetAmount>; + /// Logic for locking and paying tokens for job execution + type RewardManager: RewardManager; + type WeightInfo: WeightInfo; + } + + #[pallet::pallet] + #[pallet::generate_store(pub (super) trait Store)] + #[pallet::without_storage_info] + pub struct Pallet(_); + + /// The storage for jobs' status as a map [AccountId] -> [Script] -> [JobStatus]. + #[pallet::storage] + #[pallet::getter(fn stored_job_status)] + pub type StoredJobStatus = + StorageDoubleMap<_, Blake2_128Concat, T::AccountId, Blake2_128Concat, Script, JobStatus>; + + /// The storage for advertisements. They are stored as a map [AccountId] -> [Advertisement] since only one + /// advertisement per client is allowed. + #[pallet::storage] + #[pallet::getter(fn stored_advertisement)] + pub type StoredAdvertisement = + StorageMap<_, Blake2_128Concat, T::AccountId, AdvertisementFor>; + + /// The storage for remaining capacity for each source. Can be negative if capacity is reduced beyond the number of jobs currently assigned. + #[pallet::storage] + #[pallet::getter(fn stored_capacity)] + pub type StoredCapacity = StorageMap<_, Blake2_128Concat, T::AccountId, i32>; + + /// Index with sorted advertisement by reward asset as a map [AssetId] -> Vec<([AccountId], [Price])> + #[pallet::storage] + #[pallet::getter(fn stored_ad_index)] + pub type StoredAdIndex = StorageMap< + _, + Blake2_128Concat, + ::AssetId, + Vec>, + >; + + /// Job assignments as a map source's [AccountId] -> [JobId] -> SlotId + #[pallet::storage] + #[pallet::getter(fn stored_job_assignment)] + pub type StoredJobAssignment = StorageDoubleMap< + _, + Blake2_128Concat, + T::AccountId, + Blake2_128Concat, + JobId, + u8, + >; + + #[pallet::event] + #[pallet::generate_deposit(pub (super) fn deposit_event)] + pub enum Event { + /// A registration was successfully matched. [JobId] + JobRegistrationMatched(JobId), + /// A advertisement was successfully stored. [advertisement, who] + AdvertisementStored(AdvertisementFor, T::AccountId), + /// A registration was successfully removed. [who] + AdvertisementRemoved(T::AccountId), + } + + #[pallet::error] + pub enum Error { + /// The job registration's reward type is not supported. + JobRegistrationUnsupportedReward, + /// The job registration's reward was overflowing when calculating total amount to be paid. + RewardCalculationOverflow, + /// The reward could not be converted to different amount. + RewardConversionFailed, + /// The job registration's must specify non-zero `cpu_milliseconds`. + JobRegistrationZeroCPUMilliseconds, + /// The job registration's must specify non-zero `slots`. + JobRegistrationZeroSlots, + /// The job registration's must specify non-zero `reward`. + JobRegistrationZeroReward, + /// Job status not found. SEVERE error + JobStatusNotFound, + /// The job registration can't be modified. + JobRegistrationUnmodifiable, + /// Fulfill cannot be called for a job that does not have `JobStatus::Assigned` status. + CannotFulfillJobWhenNotAssigned, + /// Advertisement not found when attempt to delete it. + AdvertisementNotFound, + /// Fulfill was executed for a not registered job. + EmptyPricing, + /// Pricing cannot be changed (for now). + PricingUnmodifiable, + /// Payment wasn't recognized as valid. Probably didn't come from statemint assets pallet + InvalidPayment, + /// Failed to retrieve funds from pallet account to pay source. SEVERE error + FailedToPay, + /// StoredAdIndex holds inconsistent data. SEVERE error + AdIndexInconsistent, + /// Capacity not known for a source. SEVERE error + CapacityNotFound, + } + + #[pallet::hooks] + impl Hooks> for Pallet {} + + #[pallet::call] + impl Pallet { + /// Advertise resources by providing a [AdvertisementFor]. If an advertisement for the same script was previously registered, it will be overwritten. + #[pallet::weight(< T as Config >::WeightInfo::advertise())] + pub fn advertise( + origin: OriginFor, + advertisement: AdvertisementFor, + ) -> DispatchResultWithPostInfo { + let who = ensure_signed(origin)?; + ensure!((&advertisement).pricing.len() > 0, Error::::EmptyPricing); + + // update capacity to save on operations when checking available capacity + if let Some(old) = >::get(&who) { + // TODO: relax this check and resort ads according to updated pricing + ensure!( + old.pricing == advertisement.pricing, + Error::::PricingUnmodifiable + ); + + // allow capacity to become negative (in which case source remains assigned but does not receive new jobs assigned) + >::mutate(&who, |c| { + *c = Some( + c.unwrap_or(0) + .checked_add(advertisement.capacity as i32) + .unwrap_or(i32::MAX) + .checked_sub(old.capacity as i32) + .unwrap_or(0), + ) + }); + } else { + >::insert(&who, advertisement.capacity as i32); + } + + >::insert(&who, &advertisement); + + // update index + for pricing in &advertisement.pricing { + let mut ads = >::get(&pricing.reward_asset).unwrap_or_default(); + + let to_add = (who.clone(), pricing.price_per_cpu_millisecond.clone()); + // partition with predicate such that lower priced ads at start of ved + let pos = ads.partition_point(|v| v.1 < to_add.1); // -> predicate holds for ads[i], i ∈ [0, pos) + ads.insert(pos, to_add); + + >::set(&pricing.reward_asset, Some(ads)); + } + + Self::deposit_event(Event::AdvertisementStored(advertisement, who)); + Ok(().into()) + } + + /// Delete advertisement. + #[pallet::weight(< T as Config >::WeightInfo::delete_advertisement())] + pub fn delete_advertisement(origin: OriginFor) -> DispatchResultWithPostInfo { + let who = ensure_signed(origin)?; + + let ad = + >::get(&who).ok_or(Error::::AdvertisementNotFound)?; + + // update index + for pricing in &ad.pricing { + >::mutate(&pricing.reward_asset, |option_ads| { + option_ads + .as_mut() + .map(|ads| ads.retain(|v| v.0 != who.clone())); + }); + } + + >::remove(&who); + >::remove(&who); + + Self::deposit_event(Event::AdvertisementRemoved(who)); + Ok(().into()) + } + } + + impl From> for pallet_acurast::Error { + fn from(_: Error) -> Self { + Self::JobHookFailed + } + } + + impl JobHooks for Pallet { + type Error = Error; + + /// Registers a job in the marketplace by providing a [JobRegistration]. + /// If a job for the same `(accountId, script)` was previously registered, it will be overwritten. + fn register_hook( + who: &T::AccountId, + registration: &JobRegistrationFor, + ) -> Result<(), DispatchError> { + let e: ::RegistrationExtra = registration.extra.clone().into(); + let extra: JobRequirementsFor = e.into(); + + ensure!( + extra.cpu_milliseconds > 0, + Error::::JobRegistrationZeroCPUMilliseconds + ); + ensure!(extra.slots > 0, Error::::JobRegistrationZeroSlots); + let reward_amount: T::AssetAmount = extra + .reward + .try_get_amount() + .map_err(|_| Error::::JobRegistrationUnsupportedReward)? + .into(); + ensure!( + reward_amount > 0.into(), + Error::::JobRegistrationZeroReward + ); + + match >::get(&who, ®istration.script) { + Some(job_status) => ensure!( + job_status == JobStatus::Open, + Error::::JobRegistrationUnmodifiable + ), + None => {} + } + + // reward is understood per slot + let mut total = extra.reward.clone(); + total + .with_amount( + reward_amount + .checked_mul(&((extra.slots as u128).into())) + .ok_or(Error::::RewardCalculationOverflow)? + .into(), + ) + .map_err(|_| Error::::RewardConversionFailed)?; + + >::insert(&who, ®istration.script, JobStatus::default()); + + if Self::match_job(&who, ®istration)? { + // TODO improve event to contain list of matched sources + let job_id: JobId = (who.clone(), registration.script.clone()); + Self::deposit_event(Event::JobRegistrationMatched(job_id)); + } + + // lock only after all other steps succeeded without errors because locking reward is not revertable + T::RewardManager::lock_reward(total.clone(), T::Lookup::unlookup(who.clone())) + .map_err(|_| Error::::InvalidPayment)?; + + Ok(().into()) + } + + /// Deregisters a job for the given script. + fn deregister_hook(who: &T::AccountId, script: &Script) -> Result<(), DispatchError> { + let job_status = + >::get(&who, &script).ok_or(Error::::JobStatusNotFound)?; + ensure!( + job_status == JobStatus::Open, + Error::::JobRegistrationUnmodifiable + ); + + >::remove(&who, &script); + Ok(().into()) + } + + /// Updates the allowed sources list of a [JobRegistration]. + fn update_allowed_sources_hook( + who: &T::AccountId, + script: &Script, + _updates: &Vec>, + ) -> Result<(), DispatchError> { + let job_status = + >::get(&who, &script).ok_or(Error::::JobStatusNotFound)?; + + ensure!( + job_status == JobStatus::Open, + Error::::JobRegistrationUnmodifiable + ); + + Ok(().into()) + } + + /// Fulfills a previously registered job. + fn fulfill_hook( + who: &T::AccountId, // processor + fulfillment: &Fulfillment, + requester: ::Target, // the consumer that registered the job originally + registration: &JobRegistrationFor, + ) -> Result<(), DispatchError> { + // find assignment + let job_id: JobId = (requester.clone(), fulfillment.script.clone()); + >::get(&who, &job_id) + .ok_or(pallet_acurast::Error::::FulfillSourceNotAllowed)?; + + // find job + let job_status = >::get(&requester, &fulfillment.script) + .ok_or(Error::::JobStatusNotFound)?; + + let e: ::RegistrationExtra = registration.extra.clone().into(); + let extra: JobRequirementsFor = e.into(); + + // validate + ensure!( + job_status == JobStatus::Assigned, + Error::::CannotFulfillJobWhenNotAssigned + ); + + // removed fulfilled job from assigned jobs + >::remove(&who, &job_id); + + >::insert( + &requester, + ®istration.script, + JobStatus::Fulfilled(SLAEvaluation { total: 1, met: 1 }), + ); + + // increase capacity + >::mutate(&who, |c| *c = c.unwrap_or(0).checked_add(1)); + + // pay only after all other steps succeeded without errors because locking reward is not revertable + T::RewardManager::pay_reward(extra.reward.clone(), T::Lookup::unlookup(who.clone())) + .map_err(|_| Error::::FailedToPay)?; + + Ok(().into()) + } + } + + impl Pallet { + // fn match_ad( + // who: &T::AccountId, + // advertisement: &AdvertisementFor, + // ) -> Result> { + // // TODO implement + // Ok(false) + // } + + fn match_job( + who: &T::AccountId, + registration: &JobRegistrationFor, + ) -> Result> { + let e: ::RegistrationExtra = registration.extra.clone().into(); + let extra: JobRequirementsFor = e.into(); + + // strips away the asset amount + let reward_asset: ::AssetId = extra + .reward + .try_get_asset_id() + .map_err(|_| Error::::JobRegistrationUnsupportedReward)? + .into(); + + // filter candidates according to reward asset + let ads_with_reward = >::get(reward_asset); + if let Some(ads) = ads_with_reward { + let reward_amount: T::AssetAmount = extra + .reward + .try_get_amount() + .map_err(|_| Error::::JobRegistrationUnsupportedReward)? + .into(); + + // either all or no candidate gets assigned after checking if all slots can be filled + let mut candidates = Vec::new(); + for ad_with_reward in ads { + // CHECK price not exceeding reward + let total = ad_with_reward + .1 + .checked_mul(&((extra.cpu_milliseconds as u128).into())) + .ok_or(Error::::RewardCalculationOverflow)?; + if total > reward_amount { + break; + } + + // CHECK capacity sufficient + let capacity = >::get(&ad_with_reward.0) + .ok_or(Error::::CapacityNotFound)?; + if capacity <= 0 { + continue; + } + + // CHECK source is whitelisted + if !is_source_whitelisted::(&ad_with_reward.0, registration) { + continue; + } + + let ad = >::get(&ad_with_reward.0) + .ok_or(Error::::AdIndexInconsistent)?; + + // CHECK consumer is whitelisted + if !is_consumer_whitelisted::(&who, &ad) { + continue; + } + + // CANDIDATE FOUND + candidates.push((ad_with_reward.0, capacity)); + + if candidates.len() as u8 == extra.slots { + // all slots matched -> stop looking at pricier ads in sorted list + break; + } + } + + if candidates.len() as u8 == extra.slots { + // all slots matched + for (slot, candidate) in candidates.iter().enumerate() { + >::set( + &candidate.0, + (&who, ®istration.script), + Some(slot as u8), + ); + + // We know this check_sub never goes out of bounds since we selected only candidates with capacity > 0 + // => the hidden code path that deletes the stored capacity therefore never happens + >::set(&candidate.0, candidate.1.checked_sub(1)); + + >::insert( + &who, + ®istration.script, + JobStatus::Assigned, + ); + } + + return Ok(true); + } + } + Ok(false) + } + } +} diff --git a/pallets/marketplace/src/mock.rs b/pallets/marketplace/src/mock.rs new file mode 100644 index 00000000..5e20c578 --- /dev/null +++ b/pallets/marketplace/src/mock.rs @@ -0,0 +1,334 @@ +use frame_support::pallet_prelude::*; +use frame_support::traits::Everything; +use frame_support::weights::Weight; +use frame_support::{pallet_prelude::GenesisBuild, PalletId}; +use hex_literal::hex; +use sp_core::*; +use sp_io; +use sp_runtime::traits::{ + AccountIdConversion, AccountIdLookup, BlakeTwo256, ConstU128, ConstU32, StaticLookup, +}; +use sp_runtime::{bounded_vec, BoundedVec}; +use sp_runtime::{generic, parameter_types, Percent}; +use sp_std::prelude::*; + +use pallet_acurast::Script; +use pallet_acurast::{ + CertificateRevocationListUpdate, Fulfillment, FulfillmentRouter, JobAssignmentUpdate, + JobAssignmentUpdateBarrier, JobRegistrationFor, RevocationListUpdateBarrier, +}; + +use crate::stub::*; +use crate::*; + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +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 { + updates.iter().all(|update| &update.job_id.0 == origin) + } +} + +impl AssetBarrier for Barrier { + fn can_use_asset(_asset: &MockAsset) -> bool { + true + } +} + +pub struct Router; + +impl FulfillmentRouter for Router { + fn received_fulfillment( + _origin: frame_system::pallet_prelude::OriginFor, + _from: ::AccountId, + _fulfillment: Fulfillment, + _registration: JobRegistrationFor, + _requester: <::Lookup as sp_runtime::traits::StaticLookup>::Target, + ) -> frame_support::pallet_prelude::DispatchResultWithPostInfo { + Ok(().into()) + } +} + +pub struct FeeManagerImpl; + +impl FeeManager for FeeManagerImpl { + fn get_fee_percentage() -> Percent { + Percent::from_percent(30) + } + + fn pallet_id() -> PalletId { + PalletId(*b"acurfees") + } +} + +pub struct ExtBuilder; + +impl ExtBuilder { + pub fn build(self) -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::default() + .build_storage::() + .unwrap(); + + let parachain_info_config = parachain_info::GenesisConfig { + parachain_id: 2000.into(), + }; + + >::assimilate_storage( + ¶chain_info_config, + &mut t, + ) + .unwrap(); + + pallet_balances::GenesisConfig:: { + balances: vec![ + (alice_account_id(), INITIAL_BALANCE), + (pallet_assets_account(), INITIAL_BALANCE), + (pallet_fees_account(), INITIAL_BALANCE), + (bob_account_id(), INITIAL_BALANCE), + (processor_account_id(), INITIAL_BALANCE), + ], + } + .assimilate_storage(&mut t) + .unwrap(); + + // give alice an initial balance of token 22 (backed by statemint) to pay for a job + // get the MockAsset representing token 22 with owned_asset() + pallet_assets::GenesisConfig:: { + assets: vec![(22, pallet_assets_account(), false, 1_000)], + metadata: vec![(22, "test_payment".into(), "tpt".into(), 12.into())], + accounts: vec![ + (22, alice_account_id(), INITIAL_BALANCE), + (22, bob_account_id(), INITIAL_BALANCE), + ], + } + .assimilate_storage(&mut t) + .unwrap(); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| System::set_block_number(1)); + ext + } +} + +impl Default for ExtBuilder { + fn default() -> Self { + Self {} + } +} + +frame_support::construct_runtime!( + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Config, Storage, Event} = 0, + Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent}, + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, + Assets: pallet_assets::{Pallet, Config, Event, Storage}, + ParachainInfo: parachain_info::{Pallet, Storage, Config}, + Acurast: pallet_acurast::{Pallet, Call, Storage, Event}, + AcurastMarketplace: crate::{Pallet, Call, Storage, Event} + } +); + +parameter_types! { + pub const BlockHashCount: BlockNumber = 2400; +} +parameter_types! { + pub BlockWeights: frame_system::limits::BlockWeights = frame_system::limits::BlockWeights::simple_max(Weight::from_ref_time(1024)); + pub const MinimumPeriod: u64 = 6000; + pub AllowedRevocationListUpdate: Vec = vec![alice_account_id(), ::PalletId::get().into_account_truncating()]; + pub AllowedJobAssignmentUpdate: Vec = vec![bob_account_id()]; + pub const ExistentialDeposit: AssetAmount = EXISTENTIAL_DEPOSIT; +} +parameter_types! { + pub const MaxReserves: u32 = 50; + pub const MaxLocks: u32 = 50; +} +parameter_types! { + pub const AcurastPalletId: PalletId = PalletId(*b"acrstpid"); +} + +impl frame_system::Config for Test { + type Call = Call; + type Index = u32; + type BlockNumber = BlockNumber; + type Hash = sp_core::H256; + type Hashing = BlakeTwo256; + type AccountId = AccountId; + type Lookup = AccountIdLookup; + type Header = generic::Header; + type Event = Event; + type Origin = Origin; + type BlockHashCount = BlockHashCount; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type DbWeight = (); + type BaseCallFilter = Everything; + type SystemWeightInfo = (); + type BlockWeights = (); + type BlockLength = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +impl pallet_timestamp::Config for Test { + type Moment = u64; + type OnTimestampSet = (); + type MinimumPeriod = MinimumPeriod; + type WeightInfo = (); +} + +impl pallet_balances::Config for Test { + /// The type for recording an account's balance. + type Balance = AssetAmount; + type DustRemoval = (); + /// The ubiquitous event type. + type Event = Event; + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = pallet_balances::weights::SubstrateWeight; + type MaxLocks = MaxLocks; + type MaxReserves = MaxReserves; + type ReserveIdentifier = [u8; 8]; +} + +impl pallet_assets::Config for Test { + type Event = Event; + type Balance = AssetAmount; + type AssetId = AssetId; + type Currency = Balances; + type ForceOrigin = frame_system::EnsureRoot; + type AssetDeposit = ConstU128<0>; + type AssetAccountDeposit = ConstU128<0>; + type MetadataDepositBase = ConstU128<{ UNIT }>; + type MetadataDepositPerByte = ConstU128<{ 10 * MICROUNIT }>; + type ApprovalDeposit = ConstU128<{ 10 * MICROUNIT }>; + type StringLimit = ConstU32<50>; + type Freezer = (); + type Extra = (); + type WeightInfo = (); +} + +impl parachain_info::Config for Test {} + +impl pallet_acurast::Config for Test { + type Event = Event; + type RegistrationExtra = JobRequirementsFor; + type FulfillmentRouter = Router; + type MaxAllowedSources = frame_support::traits::ConstU16<4>; + type PalletId = AcurastPalletId; + type RevocationListUpdateBarrier = Barrier; + type JobAssignmentUpdateBarrier = Barrier; + type UnixTime = pallet_timestamp::Pallet; + type JobHooks = Pallet; + type WeightInfo = pallet_acurast::weights::WeightInfo; +} + +pub struct MockRewardManager {} + +impl RewardManager for MockRewardManager { + type Reward = MockAsset; + + fn lock_reward( + _reward: Self::Reward, + _owner: <::Lookup as StaticLookup>::Source, + ) -> Result<(), DispatchError> { + Ok(()) + } + + fn pay_reward( + _reward: Self::Reward, + _target: <::Lookup as StaticLookup>::Source, + ) -> Result<(), DispatchError> { + Ok(()) + } +} + +impl Config for Test { + type Event = Event; + type RegistrationExtra = JobRequirementsFor; + type PalletId = AcurastPalletId; + type AssetId = AssetId; + type AssetAmount = AssetAmount; + type RewardManager = MockRewardManager; + type WeightInfo = weights::Weights; +} + +pub fn events() -> Vec { + let evt = System::events() + .into_iter() + .map(|evt| evt.event) + .collect::>(); + + System::reset_events(); + + evt +} + +pub fn fulfillment_for(registration: &JobRegistrationFor) -> Fulfillment { + Fulfillment { + script: registration.script.clone(), + payload: hex!("00").to_vec(), + } +} + +pub fn pallet_assets_account() -> ::AccountId { + ::PalletId::get().into_account_truncating() +} + +pub fn pallet_fees_account() -> ::AccountId { + FeeManagerImpl::pallet_id().into_account_truncating() +} + +pub fn advertisement(price_per_cpu_millisecond: u128, capacity: u32) -> AdvertisementFor { + let pricing: BoundedVec, ConstU32> = + bounded_vec![PricingVariant { + reward_asset: 0, + price_per_cpu_millisecond, + bonus: 0, + maximum_slash: 0, + }]; + Advertisement { + pricing, + allowed_consumers: None, + capacity, + } +} + +pub fn job_registration_with_reward( + script: Script, + cpu_milliseconds: u128, + reward_value: u128, +) -> JobRegistrationFor { + JobRegistrationFor:: { + script, + allowed_sources: None, + allow_only_verified_sources: false, + extra: JobRequirements { + slots: 1, + cpu_milliseconds, + reward: asset(reward_value), + }, + } +} diff --git a/pallets/acurast/src/payments.rs b/pallets/marketplace/src/payments.rs similarity index 64% rename from pallets/acurast/src/payments.rs rename to pallets/marketplace/src/payments.rs index f717cadd..a9b635f5 100644 --- a/pallets/acurast/src/payments.rs +++ b/pallets/marketplace/src/payments.rs @@ -6,10 +6,10 @@ use frame_support::{ traits::{AccountIdConversion, Get, StaticLookup}, DispatchError, Percent, }, - PalletId, Parameter, + Never, PalletId, Parameter, }; -use crate::{Config, Reward, RewardManager}; +use crate::Config; pub trait AssetBarrier { fn can_use_asset(asset: &Asset) -> bool; @@ -21,6 +21,67 @@ impl AssetBarrier for () { } } +pub type RewardFor = <::RewardManager as RewardManager>::Reward; + +pub trait Reward { + type AssetId; + type AssetAmount; + type Error; + + fn with_amount(&mut self, amount: Self::AssetAmount) -> Result<&Self, Self::Error>; + fn try_get_asset_id(&self) -> Result; + fn try_get_amount(&self) -> Result; +} + +impl Reward for () { + type AssetId = Never; + type AssetAmount = Never; + type Error = (); + + fn with_amount(&mut self, _: Self::AssetAmount) -> Result<&Self, Self::Error> { + Err(()) + } + + fn try_get_asset_id(&self) -> Result { + Err(()) + } + + fn try_get_amount(&self) -> Result { + Err(()) + } +} + +pub trait RewardManager { + type Reward: Parameter + Member + Reward; + + fn lock_reward( + reward: Self::Reward, + owner: ::Source, + ) -> Result<(), DispatchError>; + fn pay_reward( + reward: Self::Reward, + target: ::Source, + ) -> Result<(), DispatchError>; +} + +impl RewardManager for () { + type Reward = (); + + fn lock_reward( + _reward: Self::Reward, + _owner: <::Lookup as StaticLookup>::Source, + ) -> Result<(), DispatchError> { + Ok(()) + } + + fn pay_reward( + _reward: Self::Reward, + _target: <::Lookup as StaticLookup>::Source, + ) -> Result<(), DispatchError> { + Ok(()) + } +} + // This trait provives methods for managing the fees. pub trait FeeManager { fn get_fee_percentage() -> Percent; @@ -34,8 +95,13 @@ impl RewardManager for AssetRewardManager where T: pallet_assets::Config, - T::AssetId: TryInto, - Asset: Parameter + Member + Reward, + ::AssetId: TryInto, + Asset: Parameter + + Member + + Reward< + AssetId = ::AssetId, + AssetAmount = ::Balance, + >, Barrier: AssetBarrier, AssetSplit: FeeManager, { @@ -48,7 +114,7 @@ where if !Barrier::can_use_asset(&reward) { return Err(DispatchError::Other("Invalid asset.")); } - let pallet_account: T::AccountId = T::PalletId::get().into_account_truncating(); + let pallet_account: T::AccountId = ::PalletId::get().into_account_truncating(); let raw_origin = RawOrigin::::Signed(pallet_account.clone()); let pallet_origin: T::Origin = raw_origin.into(); let (id, amount) = match (reward.try_get_asset_id(), reward.try_get_amount()) { @@ -74,7 +140,7 @@ where reward: Self::Reward, target: ::Source, ) -> Result<(), DispatchError> { - let pallet_account: T::AccountId = T::PalletId::get().into_account_truncating(); + let pallet_account: T::AccountId = ::PalletId::get().into_account_truncating(); let raw_origin = RawOrigin::::Signed(pallet_account.clone()); let pallet_origin: T::Origin = raw_origin.into(); let (id, amount) = match (reward.try_get_asset_id(), reward.try_get_amount()) { diff --git a/pallets/marketplace/src/stub.rs b/pallets/marketplace/src/stub.rs new file mode 100644 index 00000000..7cc73cf7 --- /dev/null +++ b/pallets/marketplace/src/stub.rs @@ -0,0 +1,153 @@ +#![allow(dead_code)] + +use frame_support::pallet_prelude::*; +use hex_literal::hex; +use sp_core::*; +use sp_runtime::AccountId32; +use sp_std::prelude::*; + +use pallet_acurast::{AttestationChain, Script, SerialNumber}; + +use crate::*; + +pub type AccountId = AccountId32; +pub type BlockNumber = u32; + +pub type AssetId = u32; +pub type AssetAmount = u128; + +#[derive(RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo, Clone, PartialEq, Eq)] +pub struct MockAsset { + pub id: AssetId, + pub amount: AssetAmount, +} + +impl Reward for MockAsset { + type AssetId = AssetId; + type AssetAmount = AssetAmount; + type Error = (); + + fn with_amount(&mut self, amount: Self::AssetAmount) -> Result<&Self, Self::Error> { + self.amount = amount; + Ok(self) + } + + fn try_get_asset_id(&self) -> Result { + Ok(self.id) + } + + fn try_get_amount(&self) -> Result { + Ok(self.amount) + } +} + +pub const SEED: u32 = 1337; +pub const INITIAL_BALANCE: u128 = UNIT * 10; +pub const EXISTENTIAL_DEPOSIT: AssetAmount = MILLIUNIT; +pub const UNIT: AssetAmount = 1_000_000; +pub const MILLIUNIT: AssetAmount = UNIT / 1_000; +pub const MICROUNIT: AssetAmount = UNIT / 1_000_000; +pub const SCRIPT_BYTES: [u8; 53] = hex!("697066733A2F2F00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"); +pub const SCRIPT_RANDOM_VALUE_BYTES: [u8; 53] = hex!("697066733a2f2f516d644a4e764d4c66766a7a4a6e48514a6d73454243384b554431667954757346726b5841463559615a6f755432"); +pub const ROOT_CERT: [u8; 1380] = hex!("3082056030820348a003020102020900e8fa196314d2fa18300d06092a864886f70d01010b0500301b311930170603550405131066393230303965383533623662303435301e170d3136303532363136323835325a170d3236303532343136323835325a301b31193017060355040513106639323030396538353362366230343530820222300d06092a864886f70d01010105000382020f003082020a0282020100afb6c7822bb1a701ec2bb42e8bcc541663abef982f32c77f7531030c97524b1b5fe809fbc72aa9451f743cbd9a6f1335744aa55e77f6b6ac3535ee17c25e639517dd9c92e6374a53cbfe258f8ffbb6fd129378a22a4ca99c452d47a59f3201f44197ca1ccd7e762fb2f53151b6feb2fffd2b6fe4fe5bc6bd9ec34bfe08239daafceb8eb5a8ed2b3acd9c5e3a7790e1b51442793159859811ad9eb2a96bbdd7a57c93a91c41fccd27d67fd6f671aa0b815261ad384fa37944864604ddb3d8c4f920a19b1656c2f14ad6d03c56ec060899041c1ed1a5fe6d3440b556bad1d0a152589c53e55d370762f0122eef91861b1b0e6c4c80927499c0e9bec0b83e3bc1f93c72c049604bbd2f1345e62c3f8e26dbec06c94766f3c128239d4f4312fad8123887e06becf567583bf8355a81feeabaf99a83c8df3e2a322afc672bf120b135158b6821ceaf309b6eee77f98833b018daa10e451f06a374d50781f359082966bb778b9308942698e74e0bcd24628a01c2cc03e51f0b3e5b4ac1e4df9eaf9ff6a492a77c1483882885015b422ce67b80b88c9b48e13b607ab545c723ff8c44f8f2d368b9f6520d31145ebf9e862ad71df6a3bfd2450959d653740d97a12f368b13ef66d5d0a54a6e2f5d9a6fef446832bc67844725861f093dd0e6f3405da89643ef0f4d69b6420051fdb93049673e36950580d3cdf4fbd08bc58483952600630203010001a381a63081a3301d0603551d0e041604143661e1007c880509518b446c47ff1a4cc9ea4f12301f0603551d230418301680143661e1007c880509518b446c47ff1a4cc9ea4f12300f0603551d130101ff040530030101ff300e0603551d0f0101ff04040302018630400603551d1f043930373035a033a031862f68747470733a2f2f616e64726f69642e676f6f676c65617069732e636f6d2f6174746573746174696f6e2f63726c2f300d06092a864886f70d01010b0500038202010020c8c38d4bdca9571b468c892fff72aac6f844a11d41a8f0736cc37d16d6426d8e7e9407044cea39e68b07c13dbf1503dd5c85bdafb2c02d5f6cdb4efa8127df8b04f182770fc4e7745b7fceaa87129a8801ce8e9bc0cb96379b4d26a82d30fd9c2f8eed6dc1be2f84b689e4d914258b144bbae624a1c70671132e2f0616a884b2a4d6a46ffa89b602bfbad80c1243711f56eb6056f637c8a0141cc54094268b8c3c7db994b35c0dcd6cb2abc2dafee252023d2dea0cd6c368bea3e6414886f6b1e58b5bd7c730b268c4e3c1fb6424b91febbdb80c586e2ae8368c84d5d10917bda2561789d4687393340e2e254f560ef64b2358fcdc0fbfc6700952e708bffcc627500c1f66e81ea17c098d7a2e9b18801b7ab4ac71587d345dcc8309d5b62a50427aa6d03dcb05996c96ba0c5d71e92162c016ca849ff35f0d52c65d05605a47f3ae917acd2df910efd2326688596ef69b3bf5fe3154f7aeb880a0a73ca04d94c2ce8317eeb43d5eff5883e336f5f249daaca4899237bf267e5c43ab02ea44162403723be6aa692c61bdae9ed409d463c4c97c64306577eef2bc7560b75715cc9c7dc67c86082db751a89c30349762b0782385875cf1a3c6166e0ae3c12d374e2d4f1846f318744bd879b587329bf018217a6c0c77241a4878e435c03079cb451289c5776206069a2f8d65f840e1445287bed877abae24e24435168d553ce4"); +pub const INT_CERT_1: [u8; 987] = hex!("308203d7308201bfa003020102020a038826676065899685f5300d06092a864886f70d01010b0500301b311930170603550405131066393230303965383533623662303435301e170d3139303830393233303332335a170d3239303830363233303332335a302f31193017060355040513103534663539333730353432663561393531123010060355040c0c095374726f6e67426f783076301006072a8648ce3d020106052b8104002203620004e352276f9bfcea4301a5f0427fa6478e573209ae44fd762cfbc57cbbd4713631509e802ea0e940536e54fa2570ca2846154698075509293b3100b3955b4317768b286bf6fe2651c59af6c6b0db3360090a4647c7860e76ecc3b8a7db5ce57acca381b63081b3301d0603551d0e041604146990b10c3b088aee2af88c3387b42c12dadfc3a6301f0603551d230418301680143661e1007c880509518b446c47ff1a4cc9ea4f12300f0603551d130101ff040530030101ff300e0603551d0f0101ff04040302020430500603551d1f044930473045a043a041863f68747470733a2f2f616e64726f69642e676f6f676c65617069732e636f6d2f6174746573746174696f6e2f63726c2f38463637333443394641353034373839300d06092a864886f70d01010b050003820201005c591327a0b0249ecadc949184c9651ed1f2a617a17516439875429e9bd21f87fd2365d0dcde747022c19410f23ab380fe1cef0f47aebc443c2a4531df3eca4101bf96d6bc30dfd878ed6734653111b5e782a03350cc2605e128b48a57e7ff1fe4bf4104de3f7ca9ace6afb01bdd9205fa10b91837a337257afb8290afa456fa629cfae5477b172b009bf28d43dcd4d31edcbf3dc1b6fcfcca5c38a79773d38b5a9d3ccd8152d51f25f9900701d9fb4fbf1307e17fcf5ddc759409863d2f0fb2e6c24468c9c5d85154e104318cb10ae60ba27bb252080e072645681c39e560e8586a64550867162f4bde9db75645882cb9eaff4efe1b0a312f5bd40224298c91f135061b8e04e8fa4c618c33f7b942c028f00d18113bfb6e55a952ccb5d71ee046f9bfdc85aa083e26d94be354545954b70c812ac4e326fdf07703bb79e536d429ff1d099c81722d81714593c7c2bb56740ccbc801332bb548695e28f2c8ac1452a260cfe57f311adc132e8dda01d638f9a4a31288a623a917f5b6c87e1c8316927129a0d11f384251d2df26b942a76844ab91968f4953e7484f2ecd2d6e187f9772d3b4584ac986e2079bc75f20773f8814ba2d16c7266761d6a3505f939fc316efda8787085a5d4f479df944f9d061d2c99acce73ed31770659297113f94140500306887be1b88082b96b18e123cabfcffbd79b68782a0408748cbf4f02f42"); +pub const INT_CERT_2: [u8; 564] = hex!("30820230308201b7a003020102020a15905857467176635834300a06082a8648ce3d040302302f31193017060355040513103534663539333730353432663561393531123010060355040c0c095374726f6e67426f78301e170d3139303732373031353231395a170d3239303732343031353231395a302f31193017060355040513103937333533373739333664306464373431123010060355040c0c095374726f6e67426f783059301306072a8648ce3d020106082a8648ce3d030107034200047639963abb7d336b5f238d8b355efdb395a22b2ccde67bda24328e4bbf802fefa97f204dd8bdb450332cb5e566f759bdc6ffafb9f3bc78e3747dfce8278e5f02a381ba3081b7301d0603551d0e04160414413e3ca9b34bc7a51cbb0125c0421be651ad7ad8301f0603551d230418301680146990b10c3b088aee2af88c3387b42c12dadfc3a6300f0603551d130101ff040530030101ff300e0603551d0f0101ff04040302020430540603551d1f044d304b3049a047a045864368747470733a2f2f616e64726f69642e676f6f676c65617069732e636f6d2f6174746573746174696f6e2f63726c2f3135393035383537343637313736363335383334300a06082a8648ce3d0403020367003064023017a0df3880a22ea1d4b3dfbdb6c04a4e5655d0ba70bdc8a5ac483b270c1e6d520cda9800b3ad775bae8dfccc7a86ecf802302898f95f24867bb3112f440db5dad27769e42be7db8dc51cf0b2af55aa43c11002e340a24f3965032f9a3a7c83c6bbdb"); +pub const LEAF_CERT: [u8; 672] = hex!("3082029c30820241a003020102020101300c06082a8648ce3d0403020500302f31193017060355040513103937333533373739333664306464373431123010060355040c0c095374726f6e67426f783022180f32303232303730393130353135355a180f32303238303532333233353935395a301f311d301b06035504030c14416e64726f6964204b657973746f7265204b65793059301306072a8648ce3d020106082a8648ce3d03010703420004b20c1d15477662623ecf430104898006e0f81c0db1bae87cb96a87c7777404659e585d3d9057b8a2ff8ae61f401a078fc75cf52c8c4268e810f93798c729e862a382015630820152300e0603551d0f0101ff0404030207803082013e060a2b06010401d6790201110482012e3082012a0201040a01020201290a0102040874657374617364660400306cbf853d0802060181e296611fbf85455c045a305831323030042b636f6d2e7562696e657469632e61747465737465642e6578656375746f722e746573742e746573746e657402010e31220420bdcb4560f6b3c41dad920668169c28be1ef9ea49f23d98cd8eb2f37ae4488ff93081a1a1053103020102a203020103a30402020100a5053103020100aa03020101bf8377020500bf853e03020100bf85404c304a0420879cd3f18ea76e244d4d4ac3bcb9c337c13b4667190b19035afe2536550050f10101ff0a010004203f4136ee3581e6aba8ea337a6b43d703de1eca241f9b7f277ecdfafff7a8dcf1bf854105020301d4c0bf85420502030315debf854e06020401348abdbf854f06020401348abd300c06082a8648ce3d04030205000347003044022033a613cce9a6ed25026a492b651f0ac67c3c0289d4e4743168c6903e2faa0bda0220324cd35c4bf2695d71ad12a28868e69232112922eaf0e3699f6add8133d528d9"); + +pub fn script() -> Script { + SCRIPT_BYTES.to_vec().try_into().unwrap() +} + +pub fn invalid_script_1() -> Script { + let end = SCRIPT_BYTES.len() - 2; + SCRIPT_BYTES[0..end].to_vec().try_into().unwrap() +} + +pub fn invalid_script_2() -> Script { + let mut bytes = SCRIPT_BYTES.to_vec(); + bytes[0] = 0; + bytes.try_into().unwrap() +} + +pub fn attestation_chain() -> AttestationChain { + 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(), + } +} + +pub fn invalid_attestation_chain_1() -> AttestationChain { + AttestationChain { + certificate_chain: vec![LEAF_CERT.to_vec().try_into().unwrap()] + .try_into() + .unwrap(), + } +} + +pub fn invalid_attestation_chain_2() -> AttestationChain { + AttestationChain { + certificate_chain: vec![ + INT_CERT_2.to_vec().try_into().unwrap(), + LEAF_CERT.to_vec().try_into().unwrap(), + ] + .try_into() + .unwrap(), + } +} + +pub fn invalid_attestation_chain_3() -> AttestationChain { + AttestationChain { + certificate_chain: vec![ + ROOT_CERT.to_vec().try_into().unwrap(), + INT_CERT_1.to_vec().try_into().unwrap(), + LEAF_CERT.to_vec().try_into().unwrap(), + ] + .try_into() + .unwrap(), + } +} + +pub fn cert_serial_number() -> SerialNumber { + hex!("15905857467176635834").to_vec().try_into().unwrap() +} + +pub fn processor_account_id() -> AccountId { + hex!("b8bc25a2b4c0386b8892b43e435b71fe11fa50533935f027949caf04bcce4694").into() +} + +pub fn alice_account_id() -> AccountId { + [0; 32].into() +} + +pub fn bob_account_id() -> AccountId { + [1; 32].into() +} + +pub fn charlie_account_id() -> AccountId { + [2; 32].into() +} + +pub fn dave_account_id() -> AccountId { + [3; 32].into() +} + +pub fn eve_account_id() -> AccountId { + [4; 32].into() +} + +pub fn script_random_value() -> Script { + SCRIPT_RANDOM_VALUE_BYTES.to_vec().try_into().unwrap() +} + +pub fn asset(value: u128) -> MockAsset { + MockAsset { + id: 0, + amount: value, + } +} diff --git a/pallets/marketplace/src/tests.rs b/pallets/marketplace/src/tests.rs new file mode 100644 index 00000000..631e21a2 --- /dev/null +++ b/pallets/marketplace/src/tests.rs @@ -0,0 +1,149 @@ +#![cfg(test)] + +use frame_support::{assert_err, assert_ok}; +use hex_literal::hex; +use pallet_acurast::Fulfillment; +use sp_runtime::MultiAddress; + +use crate::mock::*; +use crate::stub::*; +use crate::{Error, JobStatus, SLAEvaluation}; + +#[test] +fn test_match() { + // 1000 is the smallest amount accepted by T::AssetTransactor::lock_asset for the asset used + let ad = advertisement(1000, 5); + let registration = job_registration_with_reward(script(), 5, 5000); + ExtBuilder::default().build().execute_with(|| { + assert_ok!(AcurastMarketplace::advertise( + Origin::signed(processor_account_id()).into(), + ad.clone(), + )); + assert_eq!( + Some(ad.clone()), + AcurastMarketplace::stored_advertisement(processor_account_id()) + ); + assert_ok!(Acurast::register( + Origin::signed(alice_account_id()).into(), + registration.clone(), + )); + assert_eq!( + Some(JobStatus::Assigned), + AcurastMarketplace::stored_job_status(alice_account_id(), script()) + ); + assert_eq!( + Some(4), + AcurastMarketplace::stored_capacity(processor_account_id()) + ); + + // updating job registration is prohibited after match found + assert_err!( + Acurast::register( + Origin::signed(alice_account_id()).into(), + registration.clone(), + ), + Error::::JobRegistrationUnmodifiable + ); + + let fulfillment = Fulfillment { + script: registration.script.clone(), + payload: hex!("00").to_vec(), + }; + assert_ok!(Acurast::fulfill( + Origin::signed(processor_account_id()).into(), + fulfillment.clone(), + MultiAddress::Id(alice_account_id()), + )); + assert_eq!( + Some(JobStatus::Fulfilled(SLAEvaluation { total: 1, met: 1 })), + AcurastMarketplace::stored_job_status(alice_account_id(), script()) + ); + assert_eq!( + Some(5), + AcurastMarketplace::stored_capacity(processor_account_id()) + ); + + assert_eq!( + events(), + [ + Event::AcurastMarketplace(crate::Event::AdvertisementStored( + ad.clone(), + processor_account_id() + )), + Event::AcurastMarketplace(crate::Event::JobRegistrationMatched(( + alice_account_id(), + registration.script.clone() + ),)), + Event::Acurast(pallet_acurast::Event::JobRegistrationStored( + registration.clone(), + alice_account_id() + )), + Event::Acurast(pallet_acurast::Event::ReceivedFulfillment( + processor_account_id(), + fulfillment, + registration, + alice_account_id() + )), + ] + ); + }); +} + +#[test] +fn test_no_match_insufficient_capacity() { + // 1000 is the smallest amount accepted by T::AssetTransactor::lock_asset for the asset used + let ad = advertisement(1000, 1); + let registration = job_registration_with_reward(script(), 2, 2000); + let registration2 = job_registration_with_reward(script_random_value(), 2, 2000); + ExtBuilder::default().build().execute_with(|| { + assert_ok!(AcurastMarketplace::advertise( + Origin::signed(processor_account_id()).into(), + ad.clone(), + )); + assert_eq!( + Some(ad.clone()), + AcurastMarketplace::stored_advertisement(processor_account_id()) + ); + + // the first job matches because 1 capacity left + assert_ok!(Acurast::register( + Origin::signed(alice_account_id()).into(), + registration.clone(), + )); + assert_eq!( + Some(0), + AcurastMarketplace::stored_capacity(processor_account_id()) + ); + + // this one does not match anymore + assert_ok!(Acurast::register( + Origin::signed(alice_account_id()).into(), + registration2.clone(), + )); + + assert_eq!( + events(), + [ + Event::AcurastMarketplace(crate::Event::AdvertisementStored( + ad.clone(), + processor_account_id() + )), + // first job + Event::AcurastMarketplace(crate::Event::JobRegistrationMatched(( + alice_account_id(), + registration.script.clone() + ),)), + Event::Acurast(pallet_acurast::Event::JobRegistrationStored( + registration.clone(), + alice_account_id() + )), + // second job + Event::Acurast(pallet_acurast::Event::JobRegistrationStored( + registration2.clone(), + alice_account_id() + )), + // no match event + ] + ); + }); +} diff --git a/pallets/marketplace/src/types.rs b/pallets/marketplace/src/types.rs new file mode 100644 index 00000000..5f7170bd --- /dev/null +++ b/pallets/marketplace/src/types.rs @@ -0,0 +1,82 @@ +use frame_support::{pallet_prelude::*, storage::bounded_vec::BoundedVec}; +use sp_std::prelude::*; + +use pallet_acurast::JobRegistration; + +use crate::payments::RewardFor; +use crate::Config; + +pub const MAX_PRICING_VARIANTS: u32 = 100; + +pub type JobRegistrationForMarketplace = + JobRegistration<::AccountId, ::RegistrationExtra>; + +/// The resource advertisement by a source containing pricing and capacity announcements. +#[derive(RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo, Clone, PartialEq)] +pub struct Advertisement { + /// The reward token accepted. Understood as one-of per job assigned. + pub pricing: BoundedVec, ConstU32>, + // Capacity not to be exceeded in matching. + pub capacity: u32, + /// An optional array of the [AccountId]s of consumers whose jobs should get accepted. If the array is [None], then jobs from all consumers are accepted. + pub allowed_consumers: Option>, +} + +pub type AdvertisementFor = Advertisement< + ::AccountId, + ::AssetId, + ::AssetAmount, +>; + +/// Pricing variant listing cost per resource unit and slash on SLA violation. +/// Specified in specific asset that is payed out or deducted from stake on complete fulfillment. +#[derive(RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo, Clone, PartialEq)] +pub struct PricingVariant { + /// The rewarded asset. Only one per [PricingVariant]. + pub reward_asset: AssetId, + /// Price in [reward_asset] per cpu second. + pub price_per_cpu_millisecond: AssetAmount, + /// A fixed bonus in [reward_asset]. + pub bonus: AssetAmount, + /// The maximum slash to put at stake and that is lost if SLA is violated. + pub maximum_slash: AssetAmount, +} + +pub type AdvertisementIndexValue = (AccountId, AssetAmount); + +/// The allowed sources update operation. +#[derive(RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo, Clone, PartialEq, Copy)] +pub enum JobStatus { + Open, + Assigned, + Fulfilled(SLAEvaluation), +} + +impl Default for JobStatus { + fn default() -> Self { + JobStatus::Open + } +} + +/// Represents an evaluation of the SLA after a job's schedule is completed. +#[derive(RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo, Clone, PartialEq, Copy)] +pub struct SLAEvaluation { + pub total: u8, + pub met: u8, +} + +pub type JobRequirementsFor = JobRequirements>; + +/// Structure representing a job registration. +#[derive(RuntimeDebug, Encode, Decode, TypeInfo, Clone, Eq, PartialEq)] +pub struct JobRequirements +where + Reward: Parameter + Member, +{ + /// The number of execution slots to be assigned to distinct sources. Either all or no slot get assigned by matching. + pub slots: u8, + /// CPU milliseconds (upper bound) required to execute script. + pub cpu_milliseconds: u128, + /// Reward offered for the job + pub reward: Reward, +} diff --git a/pallets/marketplace/src/utils.rs b/pallets/marketplace/src/utils.rs new file mode 100644 index 00000000..27ad525d --- /dev/null +++ b/pallets/marketplace/src/utils.rs @@ -0,0 +1,32 @@ +use pallet_acurast::JobRegistrationFor; + +use crate::{AdvertisementFor, Config}; + +pub(crate) fn is_consumer_whitelisted( + consumer: &T::AccountId, + ad: &AdvertisementFor, +) -> bool { + ad.allowed_consumers + .as_ref() + .map(|allowed_consumers| { + allowed_consumers + .iter() + .any(|allowed_consumer| allowed_consumer == consumer) + }) + .unwrap_or(true) +} + +pub fn is_source_whitelisted( + source: &T::AccountId, + registration: &JobRegistrationFor, +) -> bool { + registration + .allowed_sources + .as_ref() + .map(|allowed_sources| { + allowed_sources + .iter() + .any(|allowed_source| allowed_source == source) + }) + .unwrap_or(true) +} diff --git a/pallets/marketplace/src/weights.hbs b/pallets/marketplace/src/weights.hbs new file mode 100644 index 00000000..191ac2d6 --- /dev/null +++ b/pallets/marketplace/src/weights.hbs @@ -0,0 +1,71 @@ +{{header ~}} +//! Autogenerated weights for {{pallet}} +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION {{version}} +//! DATE: {{date}}, STEPS: `{{cmd.steps}}`, REPEAT: {{cmd.repeat}}, LOW RANGE: `{{cmd.lowest_range_values}}`, HIGH RANGE: `{{cmd.highest_range_values}}` +//! HOSTNAME: `{{hostname}}`, CPU: `{{cpuname}}` +//! EXECUTION: {{cmd.execution}}, WASM-EXECUTION: {{cmd.wasm_execution}}, CHAIN: {{cmd.chain}}, DB CACHE: {{cmd.db_cache}} + +// Executed Command: +{{#each args as |arg|}} +// {{arg}} +{{/each}} + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for {{pallet}}. +pub trait WeightInfo { + {{#each benchmarks as |benchmark|}} + fn {{benchmark.name~}} + ( + {{~#each benchmark.components as |c| ~}} + {{c.name}}: u32, {{/each~}} + ) -> Weight; + {{/each}} +} + +/// Weights for {{pallet}} using the Substrate node and recommended hardware. +pub struct Weights(PhantomData); +{{#if (eq pallet "frame_system")}} +impl WeightInfo for Weights { +{{else}} +impl WeightInfo for Weights { +{{/if}} + {{#each benchmarks as |benchmark|}} + {{#each benchmark.comments as |comment|}} + // {{comment}} + {{/each}} + {{#each benchmark.component_ranges as |range|}} + /// The range of component `{{range.name}}` is `[{{range.min}}, {{range.max}}]`. + {{/each}} + fn {{benchmark.name~}} + ( + {{~#each benchmark.components as |c| ~}} + {{~#if (not c.is_used)}}_{{/if}}{{c.name}}: u32, {{/each~}} + ) -> Weight { + // Minimum execution time: {{underscore benchmark.min_execution_time}} nanoseconds. + Weight::from_ref_time({{underscore benchmark.base_weight}}) + {{#each benchmark.component_weight as |cw|}} + // Standard Error: {{underscore cw.error}} + .saturating_add(Weight::from_ref_time({{underscore cw.slope}}).saturating_mul({{cw.name}}.into())) + {{/each}} + {{#if (ne benchmark.base_reads "0")}} + .saturating_add(T::DbWeight::get().reads({{benchmark.base_reads}})) + {{/if}} + {{#each benchmark.component_reads as |cr|}} + .saturating_add(T::DbWeight::get().reads(({{cr.slope}}_u64).saturating_mul({{cr.name}}.into()))) + {{/each}} + {{#if (ne benchmark.base_writes "0")}} + .saturating_add(T::DbWeight::get().writes({{benchmark.base_writes}})) + {{/if}} + {{#each benchmark.component_writes as |cw|}} + .saturating_add(T::DbWeight::get().writes(({{cw.slope}}_u64).saturating_mul({{cw.name}}.into()))) + {{/each}} + } + {{/each}} +} diff --git a/pallets/marketplace/src/weights.rs b/pallets/marketplace/src/weights.rs new file mode 100644 index 00000000..22b848b1 --- /dev/null +++ b/pallets/marketplace/src/weights.rs @@ -0,0 +1,57 @@ +//! Autogenerated weights for pallet_acurast_marketplace +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2022-11-25, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `smartnuance`, CPU: `Intel(R) Core(TM) i7-10510U CPU @ 1.80GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("acurast-dev"), DB CACHE: 1024 + +// Executed Command: +// ../../../acurast-substrate/target/release/acurast-node +// benchmark +// pallet +// --chain=acurast-dev +// --execution=wasm +// --wasm-execution=compiled +// --pallet=pallet_acurast_marketplace +// --extrinsic +// advertise,delete_advertisement +// --steps=50 +// --repeat=20 +// --output=./src/weights.rs +// --template=./src/weights.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_acurast_marketplace. +pub trait WeightInfo { + fn advertise() -> Weight; + fn delete_advertisement() -> Weight; +} + +/// Weights for pallet_acurast_marketplace using the Substrate node and recommended hardware. +pub struct Weights(PhantomData); +impl WeightInfo for Weights { + // Storage: AcurastMarketplace StoredAdvertisement (r:1 w:1) + // Storage: AcurastMarketplace StoredAdIndex (r:1 w:1) + // Storage: AcurastMarketplace StoredCapacity (r:0 w:1) + fn advertise() -> Weight { + // Minimum execution time: nanoseconds. + Weight::from_ref_time(118_168_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(3)) + } + // Storage: AcurastMarketplace StoredAdvertisement (r:1 w:1) + // Storage: AcurastMarketplace StoredAdIndex (r:1 w:1) + // Storage: AcurastMarketplace StoredCapacity (r:0 w:1) + fn delete_advertisement() -> Weight { + // Minimum execution time: nanoseconds. + Weight::from_ref_time(129_864_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(3)) + } +} diff --git a/pallets/marketplace/src/weights_with_hooks.hbs b/pallets/marketplace/src/weights_with_hooks.hbs new file mode 100644 index 00000000..30f41d34 --- /dev/null +++ b/pallets/marketplace/src/weights_with_hooks.hbs @@ -0,0 +1,69 @@ +{{header ~}} +//! Autogenerated weights for {{pallet}} +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION {{version}} +//! DATE: {{date}}, STEPS: `{{cmd.steps}}`, REPEAT: {{cmd.repeat}}, LOW RANGE: `{{cmd.lowest_range_values}}`, HIGH RANGE: `{{cmd.highest_range_values}}` +//! HOSTNAME: `{{hostname}}`, CPU: `{{cpuname}}` +//! EXECUTION: {{cmd.execution}}, WASM-EXECUTION: {{cmd.wasm_execution}}, CHAIN: {{cmd.chain}}, DB CACHE: {{cmd.db_cache}} + +// Executed Command: +{{#each args as |arg|}} +// {{arg}} +{{/each}} + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weights for {{pallet}} using the Substrate node and recommended hardware. +pub struct Weights(PhantomData); +impl pallet_acurast::WeightInfo for Weights { + {{#each benchmarks as |benchmark|}} + {{#each benchmark.comments as |comment|}} + // {{comment}} + {{/each}} + {{#each benchmark.component_ranges as |range|}} + /// The range of component `{{range.name}}` is `[{{range.min}}, {{range.max}}]`. + {{/each}} + fn {{benchmark.name~}} + ( + {{~#each benchmark.components as |c| ~}} + {{~#if (not c.is_used)}}_{{/if}}{{c.name}}: u32, {{/each~}} + ) -> Weight { + // Minimum execution time: {{underscore benchmark.min_execution_time}} nanoseconds. + Weight::from_ref_time({{underscore benchmark.base_weight}}) + {{#each benchmark.component_weight as |cw|}} + // Standard Error: {{underscore cw.error}} + .saturating_add(Weight::from_ref_time({{underscore cw.slope}}).saturating_mul({{cw.name}}.into())) + {{/each}} + {{#if (ne benchmark.base_reads "0")}} + .saturating_add(T::DbWeight::get().reads({{benchmark.base_reads}})) + {{/if}} + {{#each benchmark.component_reads as |cr|}} + .saturating_add(T::DbWeight::get().reads(({{cr.slope}}_u64).saturating_mul({{cr.name}}.into()))) + {{/each}} + {{#if (ne benchmark.base_writes "0")}} + .saturating_add(T::DbWeight::get().writes({{benchmark.base_writes}})) + {{/if}} + {{#each benchmark.component_writes as |cw|}} + .saturating_add(T::DbWeight::get().writes(({{cw.slope}}_u64).saturating_mul({{cw.name}}.into()))) + {{/each}} + } + {{/each}} + + fn update_allowed_sources() -> Weight { + ::WeightInfo::update_allowed_sources() + } + fn update_job_assignments() -> Weight { + ::WeightInfo::update_job_assignments() + } + fn submit_attestation() -> Weight { + ::WeightInfo::submit_attestation() + } + fn update_certificate_revocation_list() -> Weight { + ::WeightInfo::update_certificate_revocation_list() + } +} diff --git a/pallets/marketplace/src/weights_with_hooks.rs b/pallets/marketplace/src/weights_with_hooks.rs new file mode 100644 index 00000000..c5148cfc --- /dev/null +++ b/pallets/marketplace/src/weights_with_hooks.rs @@ -0,0 +1,75 @@ +//! Autogenerated weights for pallet_acurast_marketplace +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2022-11-25, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `smartnuance`, CPU: `Intel(R) Core(TM) i7-10510U CPU @ 1.80GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("acurast-dev"), DB CACHE: 1024 + +// Executed Command: +// ../../../acurast-substrate/target/release/acurast-node +// benchmark +// pallet +// --chain=acurast-dev +// --execution=wasm +// --wasm-execution=compiled +// --pallet=pallet_acurast_marketplace +// --extrinsic +// register,deregister,update_allowed_sources +// --steps=50 +// --repeat=20 +// --output=./src/weights_with_hooks.rs +// --template=./src/weights_with_hooks.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weights for pallet_acurast_marketplace using the Substrate node and recommended hardware. +pub struct Weights(PhantomData); +impl pallet_acurast::WeightInfo for Weights { + // Storage: AcurastMarketplace StoredJobStatus (r:1 w:1) + // Storage: AcurastMarketplace StoredAdIndex (r:1 w:0) + // Storage: AcurastMarketplace StoredCapacity (r:1 w:1) + // Storage: AcurastMarketplace StoredAdvertisement (r:1 w:0) + // Storage: Assets Asset (r:1 w:1) + // Storage: Assets Account (r:2 w:2) + // Storage: System Account (r:1 w:1) + // Storage: AcurastMarketplace StoredJobAssignment (r:0 w:1) + // Storage: Acurast StoredJobRegistration (r:0 w:1) + fn register() -> Weight { + // Minimum execution time: nanoseconds. + Weight::from_ref_time(155_697_000) + .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().writes(8)) + } + // Storage: AcurastMarketplace StoredJobStatus (r:1 w:1) + // Storage: Acurast StoredJobRegistration (r:0 w:1) + fn deregister() -> Weight { + // Minimum execution time: nanoseconds. + Weight::from_ref_time(50_437_000) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(2)) + } + fn fulfill() -> Weight { + // Minimum execution time: nanoseconds. + Weight::from_ref_time(50_437_000) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(2)) + } + + fn update_allowed_sources() -> Weight { + ::WeightInfo::update_allowed_sources() + } + fn update_job_assignments() -> Weight { + ::WeightInfo::update_job_assignments() + } + fn submit_attestation() -> Weight { + ::WeightInfo::submit_attestation() + } + fn update_certificate_revocation_list() -> Weight { + ::WeightInfo::update_certificate_revocation_list() + } +} diff --git a/pallets/proxy/Cargo.toml b/pallets/proxy/Cargo.toml index d46fb409..fc152dbc 100644 --- a/pallets/proxy/Cargo.toml +++ b/pallets/proxy/Cargo.toml @@ -4,8 +4,8 @@ authors = ["Anonymous"] description = "FRAME pallet template for defining custom runtime logic." version = "0.1.0" license = "Unlicense" -homepage = "https://substrate.io" -repository = "https://github.com/paritytech/substrate/" +homepage = "https://docs.acurast.com/" +repository = "https://github.com/acurast" edition = "2021" [package.metadata.docs.rs] @@ -22,6 +22,7 @@ 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.29" } pallet-acurast = { path = "../acurast", default-features = false} +pallet-acurast-marketplace = { path = "../marketplace", default-features = false} pallet-timestamp = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.29" } pallet-xcm = { git = "https://github.com/paritytech/polkadot", default-features = false, branch = "release-v0.9.29"} xcm = { package = "xcm", git = "https://github.com/paritytech/polkadot", default-features = false, branch = "release-v0.9.29"} @@ -67,6 +68,7 @@ std = [ "frame-support/std", "frame-system/std", "pallet-acurast/std", + "pallet-acurast-marketplace/std", "pallet-timestamp/std", "pallet-xcm/std", "xcm-builder/std", diff --git a/pallets/proxy/README.md b/pallets/proxy/README.md index 48466062..526df4dc 100644 --- a/pallets/proxy/README.md +++ b/pallets/proxy/README.md @@ -73,7 +73,7 @@ to acurast. The pallet id is needed to properly encode the call that we want to ```rust parameter_types! { pub const AcurastParachainId: u32 = 2000; - pub const AcurastPalletId: u8 = 40; + pub const AcurastPalletId: u8 = 41; } ``` the parachain id should be found in the chainspec of acurast, and the pallet id in the definition inside the construct_runtime macro @@ -89,6 +89,6 @@ impl acurast_proxy::Config for Runtime { type AcurastParachainId = AcurastParachainId; type AcurastPalletId = AcurastPalletId; type XcmSender = XcmRouter; - type RegistrationExtra = (); + type RegistrationExtra = JobRequirements; } ``` diff --git a/pallets/proxy/src/lib.rs b/pallets/proxy/src/lib.rs index 17ff160d..487272e2 100644 --- a/pallets/proxy/src/lib.rs +++ b/pallets/proxy/src/lib.rs @@ -17,7 +17,6 @@ pub mod pallet { 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::{ @@ -27,15 +26,20 @@ pub mod pallet { }; use xcm::v2::{OriginKind, SendError}; + use pallet_acurast::{AllowedSourcesUpdate, Fulfillment, JobRegistration, Script}; + use pallet_acurast_marketplace::Advertisement; + /// 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 Reward: Parameter + Member; + type RegistrationExtra: Parameter + Member; + type AssetId: Parameter + Member; + type AssetAmount: Parameter; type XcmSender: SendXcm; type AcurastPalletId: Get; + type AcurastMarketplacePalletId: Get; type AcurastParachainId: Get; } @@ -48,7 +52,7 @@ pub mod pallet { pub enum ProxyCall { #[codec(index = 0u8)] Register { - registration: JobRegistration, + registration: JobRegistration, }, #[codec(index = 1u8)] @@ -65,6 +69,11 @@ pub mod pallet { fulfillment: Fulfillment, requester: ::Source, }, + + #[codec(index = 0u8)] + Advertise { + advertisement: Advertisement, + }, } #[derive(Clone, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] @@ -73,6 +82,7 @@ pub mod pallet { Deregister, UpdateAllowedSources, Fulfill, + Advertise, } impl ProxyCall { @@ -82,6 +92,7 @@ pub mod pallet { ProxyCall::Deregister { .. } => ExtrinsicName::Deregister, ProxyCall::UpdateAllowedSources { .. } => ExtrinsicName::UpdateAllowedSources, ProxyCall::Fulfill { .. } => ExtrinsicName::Fulfill, + ProxyCall::Advertise { .. } => ExtrinsicName::Advertise, } } } @@ -89,6 +100,7 @@ pub mod pallet { pub fn acurast_call( proxy_call: ProxyCall, caller: T::AccountId, + pallet_id: u8, ) -> DispatchResult { // extract bytes from struct let account_bytes = caller.encode().try_into().unwrap(); @@ -98,7 +110,7 @@ pub mod pallet { // 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()); + encoded_call.push(pallet_id); //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()); @@ -164,11 +176,11 @@ pub mod pallet { #[pallet::weight(10_000)] pub fn register( origin: OriginFor, - registration: JobRegistration, + registration: JobRegistration, ) -> DispatchResult { let caller = ensure_signed(origin)?; let proxy_call = ProxyCall::Register { registration }; - acurast_call::(proxy_call, caller) + acurast_call::(proxy_call, caller, T::AcurastPalletId::get()) } /// Deregisters a job for the given script. @@ -176,7 +188,7 @@ pub mod pallet { pub fn deregister(origin: OriginFor, script: Script) -> DispatchResult { let caller = ensure_signed(origin)?; let proxy_call = ProxyCall::Deregister { script }; - acurast_call::(proxy_call, caller) + acurast_call::(proxy_call, caller, T::AcurastPalletId::get()) } /// Updates the allowed sources list of a [Registration]. @@ -188,7 +200,7 @@ pub mod pallet { ) -> DispatchResult { let caller = ensure_signed(origin)?; let proxy_call = ProxyCall::UpdateAllowedSources { script, updates }; - acurast_call::(proxy_call, caller) + acurast_call::(proxy_call, caller, T::AcurastPalletId::get()) } /// Fulfills a previously registered job. @@ -203,7 +215,18 @@ pub mod pallet { fulfillment, requester, }; - acurast_call::(proxy_call, caller) + acurast_call::(proxy_call, caller, T::AcurastPalletId::get()) + } + + /// Advertise resources by providing a [Advertisement]. If an advertisement for the same script was previously registered, it will be overwritten. + #[pallet::weight(10_000)] + pub fn advertise( + origin: OriginFor, + advertisement: Advertisement, + ) -> DispatchResult { + let caller = ensure_signed(origin)?; + let proxy_call = ProxyCall::Advertise { advertisement }; + acurast_call::(proxy_call, caller, T::AcurastMarketplacePalletId::get()) } } } diff --git a/pallets/proxy/src/mock.rs b/pallets/proxy/src/mock.rs index a0569df5..289eba67 100644 --- a/pallets/proxy/src/mock.rs +++ b/pallets/proxy/src/mock.rs @@ -1,25 +1,62 @@ -use frame_support::traits::OriginTrait; use std::marker::PhantomData; + +use frame_support::traits::OriginTrait; +use scale_info::TypeInfo; +use sp_core::*; use xcm::latest::{Junction, MultiLocation, OriginKind}; -use xcm::prelude::X2; +use xcm::prelude::*; use xcm_executor::traits::ConvertOrigin; +use pallet_acurast_marketplace::Reward; + +pub type AcurastAssetId = u32; +pub type AcurastAssetAmount = u128; + +#[derive(Clone, Eq, PartialEq, Debug, Encode, Decode, TypeInfo)] +pub struct AcurastAsset(pub MultiAsset); + +impl Reward for AcurastAsset { + type AssetId = AcurastAssetId; + type AssetAmount = AcurastAssetAmount; + type Error = (); + + fn with_amount(&mut self, amount: Self::AssetAmount) -> Result<&Self, Self::Error> { + self.0 = MultiAsset { + id: self.0.id.clone(), + fun: Fungible(amount), + }; + Ok(self) + } + + fn try_get_asset_id(&self) -> Result { + match &self.0.id { + Concrete(location) => match location.last() { + Some(GeneralIndex(id)) => (*id).try_into().map_err(|_| ()), + _ => Err(()), + }, + Abstract(_) => Err(()), + } + } + + fn try_get_amount(&self) -> Result { + match &self.0.fun { + Fungible(amount) => Ok(*amount), + _ => Err(()), + } + } +} + pub mod acurast_runtime { - use codec::{Decode, Encode}; use frame_support::{ construct_runtime, parameter_types, sp_runtime::{testing::Header, traits::AccountIdLookup, AccountId32}, traits::{Everything, Nothing}, PalletId, }; - pub use pallet_acurast; - use pallet_acurast::{payments, AssetBarrier, JobAssignmentUpdateBarrier, Reward}; - use scale_info::TypeInfo; - use sp_core::H256; - use sp_std::prelude::*; - use pallet_xcm::XcmPassthrough; use polkadot_parachain::primitives::Sibling; + use sp_core::*; + use sp_std::prelude::*; use xcm::latest::prelude::*; use xcm_builder::{ AccountId32Aliases, AllowUnpaidExecutionFrom, CurrencyAdapter as XcmCurrencyAdapter, @@ -29,8 +66,14 @@ pub mod acurast_runtime { }; use xcm_executor::XcmExecutor; + pub use pallet_acurast; + use pallet_acurast::JobAssignmentUpdateBarrier; + pub use pallet_acurast_marketplace; + use pallet_acurast_marketplace::{AssetBarrier, AssetRewardManager, JobRequirements}; + + use crate::mock::{AcurastAsset, AcurastAssetAmount, AcurastAssetId}; + pub type AccountId = AccountId32; - pub type Balance = u128; pub type LocalOriginToLocation = SignedToAccountId32; pub type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; pub type Block = frame_system::mocking::MockBlock; @@ -52,6 +95,7 @@ pub mod acurast_runtime { ); pub struct FulfillmentRouter; + impl pallet_acurast::FulfillmentRouter for FulfillmentRouter { fn received_fulfillment( _origin: frame_system::pallet_prelude::OriginFor, @@ -65,6 +109,7 @@ pub mod acurast_runtime { } pub struct AcurastBarrier; + impl JobAssignmentUpdateBarrier for AcurastBarrier { fn can_update_assigned_jobs( origin: &::AccountId, @@ -82,35 +127,10 @@ pub mod acurast_runtime { } } - #[derive(Clone, Eq, PartialEq, Debug, Encode, Decode, TypeInfo)] - pub struct AcurastAsset(MultiAsset); - impl Reward for AcurastAsset { - type AssetId = u32; - type Balance = u128; - type Error = (); - - fn try_get_asset_id(&self) -> Result { - match &self.0.id { - Concrete(location) => match location.last() { - Some(GeneralIndex(id)) => (*id).try_into().map_err(|_| ()), - _ => Err(()), - }, - Abstract(_) => Err(()), - } - } - - fn try_get_amount(&self) -> Result { - match &self.0.fun { - Fungible(amount) => Ok(*amount), - _ => Err(()), - } - } - } - pub const MILLISECS_PER_BLOCK: u64 = 12000; pub const SLOT_DURATION: u64 = MILLISECS_PER_BLOCK; - pub const UNIT: Balance = 1_000_000; - pub const MICROUNIT: Balance = 1; + pub const UNIT: AcurastAssetAmount = 1_000_000; + pub const MICROUNIT: AcurastAssetAmount = 1; construct_runtime!( pub enum Runtime where @@ -126,6 +146,7 @@ pub mod acurast_runtime { MsgQueue: super::mock_msg_queue::{Pallet, Storage, Event}, PolkadotXcm: pallet_xcm::{Pallet, Call, Event, Origin}, Acurast: pallet_acurast::{Pallet, Call, Storage, Event} = 40, + AcurastMarketplace: pallet_acurast_marketplace::{Pallet, Call, Storage, Event} = 41, } ); @@ -138,7 +159,7 @@ pub mod acurast_runtime { pub const BlockHashCount: u64 = 250; } parameter_types! { - pub ExistentialDeposit: Balance = 1; + pub ExistentialDeposit: AcurastAssetAmount = 1; pub const MaxLocks: u32 = 50; pub const MaxReserves: u32 = 50; } @@ -154,6 +175,7 @@ pub mod acurast_runtime { } pub struct XcmConfig; + impl xcm_executor::Config for XcmConfig { type Call = Call; type XcmSender = XcmRouter; @@ -172,7 +194,7 @@ pub mod acurast_runtime { } impl pallet_balances::Config for Runtime { - type Balance = Balance; + type Balance = AcurastAssetAmount; type DustRemoval = (); type Event = Event; type ExistentialDeposit = ExistentialDeposit; @@ -201,7 +223,7 @@ pub mod acurast_runtime { type DbWeight = (); type Version = (); type PalletInfo = PalletInfo; - type AccountData = pallet_balances::AccountData; + type AccountData = pallet_balances::AccountData; type OnNewAccount = (); type OnKilledAccount = (); type SystemWeightInfo = (); @@ -221,8 +243,8 @@ pub mod acurast_runtime { impl pallet_assets::Config for Runtime { type Event = Event; - type Balance = Balance; - type AssetId = u32; + type Balance = AcurastAssetAmount; + type AssetId = AcurastAssetId; type Currency = Balances; type ForceOrigin = frame_system::EnsureRoot; type AssetDeposit = frame_support::traits::ConstU128<0>; @@ -237,7 +259,8 @@ pub mod acurast_runtime { } pub struct FeeManagerImpl; - impl pallet_acurast::FeeManager for FeeManagerImpl { + + impl pallet_acurast_marketplace::FeeManager for FeeManagerImpl { fn get_fee_percentage() -> sp_runtime::Percent { sp_runtime::Percent::from_percent(30) } @@ -249,18 +272,27 @@ pub mod acurast_runtime { impl pallet_acurast::Config for Runtime { type Event = Event; - type RegistrationExtra = (); + type RegistrationExtra = JobRequirements; type FulfillmentRouter = FulfillmentRouter; type MaxAllowedSources = frame_support::traits::ConstU16<1000>; - type RewardManager = - payments::AssetRewardManager; type PalletId = AcurastPalletId; type RevocationListUpdateBarrier = (); type JobAssignmentUpdateBarrier = AcurastBarrier; type UnixTime = pallet_timestamp::Pallet; + type JobHooks = pallet_acurast_marketplace::Pallet; type WeightInfo = pallet_acurast::weights::WeightInfo; } + impl pallet_acurast_marketplace::Config for Runtime { + type Event = Event; + type RegistrationExtra = JobRequirements; + type PalletId = AcurastPalletId; + type AssetId = AcurastAssetId; + type AssetAmount = AcurastAssetAmount; + type RewardManager = AssetRewardManager; + type WeightInfo = pallet_acurast_marketplace::weights::Weights; + } + impl pallet_xcm::Config for Runtime { type Event = Event; type SendXcmOrigin = EnsureXcmOrigin; @@ -303,8 +335,11 @@ pub mod proxy_runtime { }; use xcm_executor::{Config, XcmExecutor}; + use pallet_acurast_marketplace::JobRequirements; + + use crate::mock::{AcurastAsset, AcurastAssetAmount, AcurastAssetId}; + pub type AccountId = AccountId32; - pub type Balance = u128; pub type LocationToAccountId = ( ParentIsPreset, SiblingParachainConvertsVia, @@ -326,6 +361,7 @@ pub mod proxy_runtime { pub type Barrier = AllowUnpaidExecutionFrom; pub struct XcmConfig; + impl Config for XcmConfig { type Call = Call; type XcmSender = XcmRouter; @@ -364,7 +400,7 @@ pub mod proxy_runtime { pub const BlockHashCount: u64 = 250; } parameter_types! { - pub ExistentialDeposit: Balance = 1; + pub ExistentialDeposit: AcurastAssetAmount = 1; pub const MaxLocks: u32 = 50; pub const MaxReserves: u32 = 50; } @@ -381,6 +417,7 @@ pub mod proxy_runtime { parameter_types! { pub const AcurastParachainId: u32 = 2000; pub const AcurastPalletId: u8 = 40; + pub const AcurastMarketplacePalletId: u8 = 41; } parameter_types! { pub const KsmLocation: MultiLocation = MultiLocation::parent(); @@ -406,7 +443,7 @@ pub mod proxy_runtime { type DbWeight = (); type Version = (); type PalletInfo = PalletInfo; - type AccountData = pallet_balances::AccountData; + type AccountData = pallet_balances::AccountData; type OnNewAccount = (); type OnKilledAccount = (); type SystemWeightInfo = (); @@ -416,7 +453,7 @@ pub mod proxy_runtime { } impl pallet_balances::Config for Runtime { - type Balance = Balance; + type Balance = AcurastAssetAmount; type DustRemoval = (); type Event = Event; type ExistentialDeposit = ExistentialDeposit; @@ -451,10 +488,12 @@ pub mod proxy_runtime { impl crate::Config for Runtime { type Event = Event; - type RegistrationExtra = (); - type Reward = MultiAsset; + type RegistrationExtra = JobRequirements; + type AssetId = AcurastAssetId; + type AssetAmount = AcurastAssetAmount; type XcmSender = XcmRouter; type AcurastPalletId = AcurastPalletId; + type AcurastMarketplacePalletId = AcurastMarketplacePalletId; type AcurastParachainId = AcurastParachainId; } @@ -472,10 +511,9 @@ pub mod relay_chain { sp_runtime::{testing::Header, traits::IdentityLookup, AccountId32}, traits::{Everything, Nothing}, }; - use sp_core::H256; - use polkadot_parachain::primitives::Id as ParaId; use polkadot_runtime_parachains::{configuration, origin, shared, ump}; + use sp_core::H256; use xcm::latest::prelude::*; use xcm_builder::{ AccountId32Aliases, AllowUnpaidExecutionFrom, ChildParachainAsNative, @@ -486,8 +524,9 @@ pub mod relay_chain { }; use xcm_executor::{Config, XcmExecutor}; + use crate::mock::AcurastAssetAmount; + pub type AccountId = AccountId32; - pub type Balance = u128; pub type SovereignAccountOf = ( ChildParachainConvertsVia, AccountId32Aliases, @@ -507,6 +546,7 @@ pub mod relay_chain { pub type Barrier = AllowUnpaidExecutionFrom; pub struct XcmConfig; + impl Config for XcmConfig { type Call = Call; type XcmSender = XcmRouter; @@ -554,7 +594,7 @@ pub mod relay_chain { pub const FirstMessageFactorPercent: u64 = 100; } parameter_types! { - pub ExistentialDeposit: Balance = 1; + pub ExistentialDeposit: AcurastAssetAmount = 1; pub const MaxLocks: u32 = 50; pub const MaxReserves: u32 = 50; } @@ -580,7 +620,7 @@ pub mod relay_chain { type DbWeight = (); type Version = (); type PalletInfo = PalletInfo; - type AccountData = pallet_balances::AccountData; + type AccountData = pallet_balances::AccountData; type OnNewAccount = (); type OnKilledAccount = (); type SystemWeightInfo = (); @@ -590,7 +630,7 @@ pub mod relay_chain { } impl pallet_balances::Config for Runtime { - type Balance = Balance; + type Balance = AcurastAssetAmount; type DustRemoval = (); type Event = Event; type ExistentialDeposit = ExistentialDeposit; @@ -791,6 +831,7 @@ pub mod mock_msg_queue { } pub struct SignedAccountId32FromXcm(PhantomData); + impl ConvertOrigin for SignedAccountId32FromXcm where Origin::AccountId: From<[u8; 32]>, diff --git a/pallets/proxy/src/tests.rs b/pallets/proxy/src/tests.rs index 64209024..eff74d7a 100644 --- a/pallets/proxy/src/tests.rs +++ b/pallets/proxy/src/tests.rs @@ -14,24 +14,31 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -use crate::mock::*; - -use crate::mock::{acurast_runtime::FeeManagerImpl, proxy_runtime::AccountId}; -use acurast_runtime::AccountId as AcurastAccountId; -use acurast_runtime::Runtime as AcurastRuntime; use frame_support::{pallet_prelude::GenesisBuild, sp_runtime::traits::AccountIdConversion}; use hex_literal::hex; -use pallet_acurast::{FeeManager, JobAssignmentUpdate, JobRegistration}; use polkadot_parachain::primitives::Id as ParaId; -use xcm::latest::{MultiAsset, MultiLocation}; -use xcm::prelude::{Concrete, Fungible, GeneralIndex, PalletInstance, Parachain, X3}; +use sp_runtime::traits::ConstU32; +use sp_runtime::{bounded_vec, BoundedVec}; +use xcm::prelude::*; use xcm_simulator::{decl_test_network, decl_test_parachain, decl_test_relay_chain}; +use acurast_runtime::AccountId as AcurastAccountId; +use acurast_runtime::Runtime as AcurastRuntime; +use pallet_acurast::JobRegistration; +use pallet_acurast_marketplace::{ + types::MAX_PRICING_VARIANTS, Advertisement, FeeManager, JobRequirements, PricingVariant, +}; + +use crate::mock::*; +use crate::mock::{acurast_runtime::FeeManagerImpl, proxy_runtime::AccountId}; + pub type RelayChainPalletXcm = pallet_xcm::Pallet; pub type AcurastPalletXcm = pallet_xcm::Pallet; pub const ALICE: frame_support::sp_runtime::AccountId32 = frame_support::sp_runtime::AccountId32::new([0u8; 32]); +pub const BOB: frame_support::sp_runtime::AccountId32 = + frame_support::sp_runtime::AccountId32::new([1u8; 32]); pub const INITIAL_BALANCE: u128 = 1_000_000_000; const SCRIPT_BYTES: [u8; 53] = hex!("697066733A2F2F00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"); @@ -171,44 +178,56 @@ pub fn alice_account_id() -> AcurastAccountId { pub fn bob_account_id() -> AcurastAccountId { [1; 32].into() } -pub fn owned_asset() -> MultiAsset { - MultiAsset { +pub fn owned_asset(amount: u128) -> AcurastAsset { + AcurastAsset(MultiAsset { id: Concrete(MultiLocation { parents: 1, interior: X3(Parachain(1000), PalletInstance(50), GeneralIndex(22)), }), - fun: Fungible(INITIAL_BALANCE / 2), - } + fun: Fungible(amount), + }) } -pub fn registration() -> JobRegistration { +pub fn registration() -> JobRegistration> { JobRegistration { script: SCRIPT_BYTES.to_vec().try_into().unwrap(), allowed_sources: None, allow_only_verified_sources: false, - extra: (), - reward: owned_asset(), + extra: JobRequirements { + slots: 1, + cpu_milliseconds: 2, + reward: owned_asset(20000), + }, } } -pub fn job_assignment_update_for( - registration: JobRegistration, - requester: Option, -) -> Vec> { - vec![JobAssignmentUpdate { - operation: pallet_acurast::ListUpdateOperation::Add, - assignee: processor_account_id(), - job_id: (requester.unwrap_or(alice_account_id()), registration.script), - }] +pub fn advertisement( + price_per_cpu_millisecond: u128, + capacity: u32, +) -> Advertisement { + let pricing: BoundedVec< + PricingVariant, + ConstU32, + > = bounded_vec![PricingVariant { + reward_asset: 22, + price_per_cpu_millisecond, + bonus: 0, + maximum_slash: 0, + }]; + Advertisement { + pricing, + allowed_consumers: None, + capacity, + } } #[cfg(test)] mod network_tests { - use super::*; - use codec::Encode; use frame_support::assert_ok; use xcm::latest::prelude::*; use xcm_simulator::TestExt; + use super::*; + // Helper function for forming buy execution message fn buy_execution(fees: impl Into) -> Instruction { BuyExecution { @@ -440,17 +459,19 @@ mod network_tests { #[cfg(test)] mod proxy_calls { - use super::*; use frame_support::assert_ok; use frame_support::dispatch::Dispatchable; - use pallet_acurast::{Fulfillment, ListUpdateOperation}; use xcm_simulator::TestExt; + use super::*; + #[test] fn register() { Network::reset(); - use pallet_acurast::Script; + register_job_alice(); + } + fn register_job_alice() { CumulusParachain::execute_with(|| { use crate::pallet::Call::register; use proxy_runtime::Call::AcurastProxy; @@ -467,6 +488,7 @@ mod proxy_calls { use acurast_runtime::pallet_acurast::Event::JobRegistrationStored; use acurast_runtime::pallet_acurast::StoredJobRegistration; use acurast_runtime::{Event, Runtime, System}; + use pallet_acurast::Script; let events = System::events(); let script: Script = SCRIPT_BYTES.to_vec().try_into().unwrap(); @@ -533,20 +555,19 @@ mod proxy_calls { AcurastParachain::execute_with(|| { use acurast_runtime::pallet_acurast::StoredJobRegistration; use acurast_runtime::Runtime; + use pallet_acurast::Script; 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 = frame_support::sp_runtime::AccountId32::new(rand_array); CumulusParachain::execute_with(|| { use crate::pallet::Call::update_allowed_sources; + use pallet_acurast::{AllowedSourcesUpdate, ListUpdateOperation}; use proxy_runtime::Call::AcurastProxy; let update = AllowedSourcesUpdate { @@ -568,6 +589,7 @@ mod proxy_calls { use acurast_runtime::pallet_acurast::Event::AllowedSourcesUpdated; use acurast_runtime::pallet_acurast::StoredJobRegistration; use acurast_runtime::{Event, Runtime, System}; + use pallet_acurast::Script; let events = System::events(); let script: Script = SCRIPT_BYTES.to_vec().try_into().unwrap(); @@ -585,30 +607,82 @@ mod proxy_calls { }); } + #[test] + fn advertise() { + advertise_bob(); + } + + fn advertise_bob() { + Network::reset(); + + CumulusParachain::execute_with(|| { + use crate::pallet::Call::advertise; + use proxy_runtime::Call::AcurastProxy; + + let message_call = AcurastProxy(advertise { + advertisement: advertisement(10000u128, 5u32), + }); + let bob_origin = proxy_runtime::Origin::signed(bob_account_id()); + let dispatch_status = message_call.dispatch(bob_origin); + assert_ok!(dispatch_status); + }); + + AcurastParachain::execute_with(|| { + use acurast_runtime::pallet_acurast_marketplace::Event::AdvertisementStored; + use acurast_runtime::pallet_acurast_marketplace::StoredAdvertisement; + use acurast_runtime::{Event, Runtime, System}; + + let events = System::events(); + let p_store = StoredAdvertisement::::get(BOB); + assert!(p_store.is_some()); + assert!(events.iter().any(|event| matches!( + event.event, + Event::AcurastMarketplace(AdvertisementStored { .. }) + ))); + }); + } + #[test] fn fulfill() { use frame_support::dispatch::Dispatchable; Network::reset(); - register(); + // WHEN + advertise_bob(); - // check that job is stored in the context of this test + // THEN check the ad is in index AcurastParachain::execute_with(|| { - use acurast_runtime::{Call::Acurast, Origin}; - use pallet_acurast::Call::update_job_assignments; - // StoredJobAssignment::::set(bob.clone(), Some(vec![(ALICE, script)])); + use acurast_runtime::pallet_acurast_marketplace::StoredAdIndex; + use acurast_runtime::Runtime; - let extrinsic_call = Acurast(update_job_assignments { - updates: job_assignment_update_for(registration(), Some(alice_account_id())), - }); + let p_store = StoredAdIndex::::get(22); + assert!(p_store.is_some()); + }); - let dispatch_status = extrinsic_call.dispatch(Origin::signed(alice_account_id())); - assert_ok!(dispatch_status); + // WHEN + register_job_alice(); + + // THEN check that job got matched + AcurastParachain::execute_with(|| { + use acurast_runtime::pallet_acurast_marketplace::StoredJobAssignment; + use acurast_runtime::{Event, Runtime, System}; + use pallet_acurast::Script; + use pallet_acurast_marketplace::Event::JobRegistrationMatched; + + let events = System::events(); + let script: Script = SCRIPT_BYTES.to_vec().try_into().unwrap(); + let p_store = StoredJobAssignment::::get(bob_account_id(), (ALICE, script)); + assert!(p_store.is_some()); + assert!(events.iter().any(|event| matches!( + event.event, + Event::AcurastMarketplace(JobRegistrationMatched { .. }) + ))); }); CumulusParachain::execute_with(|| { use crate::pallet::Call::fulfill; + use pallet_acurast::Fulfillment; use proxy_runtime::Call::AcurastProxy; let payload: [u8; 32] = rand::random(); @@ -623,8 +697,8 @@ mod proxy_calls { requester: frame_support::sp_runtime::MultiAddress::Id(alice_account_id()), }); - let processor_origin = proxy_runtime::Origin::signed(processor_account_id()); - let dispatch_status = message_call.dispatch(processor_origin); + let origin = proxy_runtime::Origin::signed(bob_account_id()); + let dispatch_status = message_call.dispatch(origin); assert_ok!(dispatch_status); });