This repository was archived by the owner on Oct 2, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlib.rs
More file actions
294 lines (257 loc) · 9.79 KB
/
Copy pathlib.rs
File metadata and controls
294 lines (257 loc) · 9.79 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
#![cfg_attr(not(feature = "std"), no_std)]
/// Edit this file to define custom logic or remove it if it is not needed.
/// Learn more about FRAME and the core library of Substrate FRAME pallets:
/// <https://docs.substrate.io/reference/frame-pallets/>
pub use pallet::*;
use parity_scale_codec::alloc::string::ToString;
use sp_core::H160;
use sp_std::vec::Vec;
mod functions;
pub mod traits;
#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_support::{
pallet_prelude::{OptionQuery, ValueQuery, *},
BoundedVec,
};
use frame_system::pallet_prelude::*;
use sp_core::{H160, U256};
use sp_runtime::traits::Convert;
/// Collection id type
pub type CollectionId = u64;
/// Base URI type
pub type BaseURI<T> = BoundedVec<u8, <T as Config>::BaseURILimit>;
#[pallet::pallet]
pub struct Pallet<T>(_);
/// Configure the pallet by specifying the parameters and types on which it depends.
#[pallet::config]
pub trait Config: frame_system::Config {
/// Because this pallet emits events, it depends on the runtime's definition of an event.
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
/// Specifies the advised maximum length for a Base URI.
///
/// The URI standard (RFC 3986) doesn't dictates a limit for the length of URIs.
/// However it seems the max supported length in browsers is 2,048 characters.
///
/// The base should be capped at 2,015 characters in length. This ensures room for
/// the token URI formation, where it combines `BaseURILimit`, a `'/'`, and a `tokenID`
/// (which takes up 33 characters).
#[pallet::constant]
type BaseURILimit: Get<u32>;
/// This associated type defines a conversion from the `AccountId` type, which is internal
/// to the implementing type (represented by `Self`), to an `H160` type. The `H160` type
/// is commonly used to represent Ethereum addresses.
type AccountIdToH160: Convert<Self::AccountId, H160>;
/// This associated type defines a conversion from an `H160` type back to the `AccountId` type,
/// which is internal to the implementing type (represented by `Self`). This conversion is
/// often necessary for mapping Ethereum addresses back to native account IDs.
type H160ToAccountId: Convert<H160, Self::AccountId>;
/// Type alias for implementing the `AssetIdToInitialOwner` trait for a given account ID type.
/// This allows you to specify which account should initially own each new asset.
type AssetIdToInitialOwner: Convert<U256, Self::AccountId>;
}
/// Collection counter
#[pallet::storage]
#[pallet::getter(fn collection_counter)]
pub(super) type CollectionCounter<T: Config> = StorageValue<_, CollectionId, ValueQuery>;
/// Collection base URI
#[pallet::storage]
#[pallet::getter(fn collection_base_uri)]
pub(super) type CollectionBaseURI<T: Config> =
StorageMap<_, Blake2_128Concat, CollectionId, BaseURI<T>, OptionQuery>;
/// Asset owner
#[pallet::storage]
pub(super) type AssetOwner<T: Config> = StorageDoubleMap<
_,
Blake2_128Concat,
CollectionId,
Blake2_128Concat,
U256,
T::AccountId,
OptionQuery,
>;
fn asset_owner<T: Config>(collection_id: CollectionId, asset_id: U256) -> T::AccountId {
AssetOwner::<T>::get(collection_id, asset_id)
.unwrap_or_else(|| T::AssetIdToInitialOwner::convert(asset_id))
}
/// Pallet events
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
/// Collection created
/// parameters. [collection_id, who]
CollectionCreated { collection_id: CollectionId, who: T::AccountId },
/// Asset transferred to `who`
/// parameters. [asset_id_id, who]
AssetTransferred { asset_id: U256, receiver: T::AccountId },
}
// Errors inform users that something went wrong.
#[pallet::error]
#[derive(PartialEq)]
pub enum Error<T> {
/// Collection id overflow
CollectionIdOverflow,
/// Collection does not exist
CollectionDoesNotExist,
// NoPermission,
NoPermission,
// AssetDoesNotExist,
AssetDoesNotExist,
// CannotTransferSelf,
CannotTransferSelf,
// TransferToNullAddress,
TransferToNullAddress,
}
impl<T: Config> AsRef<[u8]> for Error<T> {
fn as_ref(&self) -> &[u8] {
match self {
Error::__Ignore(_, _) => b"__Ignore",
Error::CollectionIdOverflow => b"CollectionIdOverflow",
Error::CollectionDoesNotExist => b"CollectionDoesNotExist",
Error::NoPermission => b"NoPermission",
Error::AssetDoesNotExist => b"AssetDoesNotExist",
Error::CannotTransferSelf => b"CannotTransferSelf",
Error::TransferToNullAddress => b"TransferToNullAddress",
}
}
}
// Dispatchable functions allows users to interact with the pallet and invoke state changes.
// These functions materialize as "extrinsics", which are often compared to transactions.
// Dispatchable functions must be annotated with a weight and must return a DispatchResult.
#[pallet::call]
impl<T: Config> Pallet<T> {
#[pallet::call_index(0)]
#[pallet::weight(10_000 + T::DbWeight::get().writes(1).ref_time())] // TODO set proper weight
pub fn create_collection(origin: OriginFor<T>, base_uri: BaseURI<T>) -> DispatchResult {
let who = ensure_signed(origin)?;
match Self::do_create_collection(who, base_uri) {
Ok(_) => Ok(()),
Err(err) => Err(err.into()),
}
}
}
impl<T: Config> traits::CollectionManager for Pallet<T> {
type Error = Error<T>;
type AccountId = T::AccountId;
type BaseURI = BaseURI<T>;
fn base_uri(collection_id: CollectionId) -> Option<Self::BaseURI> {
CollectionBaseURI::<T>::get(collection_id)
}
fn create_collection(
owner: T::AccountId,
base_uri: Self::BaseURI,
) -> Result<CollectionId, Self::Error> {
Self::do_create_collection(owner, base_uri)
}
}
impl<T: Config> traits::Erc721 for Pallet<T> {
type Error = Error<T>;
fn owner_of(collection_id: CollectionId, asset_id: U256) -> Result<H160, Self::Error> {
Pallet::<T>::collection_base_uri(collection_id).ok_or(Error::CollectionDoesNotExist)?;
Ok(T::AccountIdToH160::convert(asset_owner::<T>(collection_id, asset_id)))
}
fn transfer_from(
origin: H160,
collection_id: CollectionId,
from: H160,
to: H160,
asset_id: U256,
) -> Result<(), Self::Error> {
Pallet::<T>::collection_base_uri(collection_id).ok_or(Error::CollectionDoesNotExist)?;
ensure!(origin == from, Error::NoPermission);
ensure!(
T::AccountIdToH160::convert(asset_owner::<T>(collection_id, asset_id)) == from,
Error::NoPermission
);
ensure!(from != to, Error::CannotTransferSelf);
ensure!(to != H160::zero(), Error::TransferToNullAddress);
let to = T::H160ToAccountId::convert(to.clone());
AssetOwner::<T>::set(collection_id, asset_id, Some(to.clone()));
Self::deposit_event(Event::AssetTransferred { asset_id, receiver: to });
Ok(())
}
fn token_uri(collection_id: CollectionId, asset_id: U256) -> Result<Vec<u8>, Self::Error> {
let base_uri = Pallet::<T>::collection_base_uri(collection_id)
.ok_or(Error::CollectionDoesNotExist)?;
// concatenate base_uri with asset_id
let mut token_uri = base_uri.to_vec();
token_uri.push(b'/');
token_uri.extend_from_slice(asset_id.to_string().as_bytes());
Ok(token_uri)
}
}
}
/// `ASSET_PRECOMPILE_ADDRESS_PREFIX` is a predefined prefix used to identify collection addresses.
///
/// All addresses that start with this prefix are considered as collection addresses.
/// Since `CollectionId` is represented as a `u64`, it leaves these bits free to be
/// utilized for such a prefix.
///
/// Usage of this prefix provides a consistent and recognizable pattern for distinguishing
/// collection addresses from other types of addresses in the system.
pub const ASSET_PRECOMPILE_ADDRESS_PREFIX: &[u8] = &[0xff; 12];
/// Enum representing possible errors related to collections.
#[derive(Debug, PartialEq)]
pub enum CollectionError {
/// Error indicating that the provided address does not have the correct prefix.
InvalidPrefix,
}
/// Converts a `CollectionId` into an `H160` address format.
///
/// This function takes the given `CollectionId`, which is assumed to be a `u64`,
/// and maps it into an `H160` address, prepending it with the `ASSET_PRECOMPILE_ADDRESS_PREFIX`.
///
/// # Arguments
///
/// * `collection_id`: The ID of the collection to be converted.
///
/// # Returns
///
/// * An `H160` representation of the collection ID.
pub fn collection_id_to_address(collection_id: CollectionId) -> H160 {
let mut bytes = [0u8; 20];
bytes[12..20].copy_from_slice(&collection_id.to_be_bytes());
for (i, byte) in ASSET_PRECOMPILE_ADDRESS_PREFIX.iter().enumerate() {
bytes[i] = *byte;
}
H160(bytes)
}
/// Converts an `H160` address into a `CollectionId` format.
///
/// This function takes the given `H160` address, checks for the correct prefix, and extracts
/// the `CollectionId` from it. If the prefix is incorrect, it returns a `CollectionError::InvalidPrefix` error.
///
/// # Arguments
///
/// * `address`: The `H160` address to be converted.
///
/// # Returns
///
/// * A `Result` which is either the `CollectionId` or an error indicating the address is invalid.
pub fn address_to_collection_id(address: H160) -> Result<CollectionId, CollectionError> {
if &address.0[0..12] != ASSET_PRECOMPILE_ADDRESS_PREFIX {
return Err(CollectionError::InvalidPrefix)
}
let id_bytes: [u8; 8] = address.0[12..].try_into().unwrap();
Ok(CollectionId::from_be_bytes(id_bytes))
}
/// Checks if a given `H160` address is a collection address.
///
/// This function examines the prefix of the given `H160` address to determine if it is a
/// collection address, based on the `ASSET_PRECOMPILE_ADDRESS_PREFIX`.
///
/// # Arguments
///
/// * `address`: The `H160` address to be checked.
///
/// # Returns
///
/// * A boolean indicating if the address is a collection address.
pub fn is_collection_address(address: H160) -> bool {
&address.to_fixed_bytes()[0..12] == ASSET_PRECOMPILE_ADDRESS_PREFIX
}
#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;