Skip to content
This repository was archived by the owner on Oct 2, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ members = [
[workspace.dependencies]
parity-scale-codec = { version = "3.2.2", default-features = false, features = ["derive"] }
hex-literal = "0.4.1"
hex = { version = "0.4.3", default-features = false }
scale-info = { version = "2.7.0", default-features = false, features = ["derive"] }
smallvec = "1.10.0"
num_enum = { version = "0.5.3", default-features = false }
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# LAOS Ownership Parachain

For an in-depth understanding of the LAOS system, refer to the [LAOS Technical Whitepaper](https://github.com/freeverseio/laos-whitepaper/blob/main/laos.pdf), which covers all components extensively.

The LAOS Ownership Parachain is a specialized chain built on Polkadot.

It offers several functionalities that enable the management and transfer of LAOS native utility tokens, as well as the ownership of all LA (Living Assets) created directly within LAOS. Additionally, the Parachain handles the runtime upgrades and stores state roots of Evolution Chains (Evochains), providing asset attribute certification methods and rewarding Evochain validators upon receiving new correct roots.
Expand Down
9 changes: 4 additions & 5 deletions pallets/living-assets-ownership/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,13 @@ frame-system = { workspace = true }
sp-arithmetic = { workspace = true }
sp-core = { workspace = true }
sp-std = { workspace = true }
sp-io = { workspace = true }
sp-runtime = { workspace = true }

[dev-dependencies]
serde = { workspace = true }
hex = { version = "0.4.3" }

# Substrate
sp-core = { workspace = true }
sp-io = { workspace = true }
sp-runtime = { workspace = true }

[features]
default = ["std"]
runtime-benchmarks = [
Expand All @@ -49,6 +46,8 @@ std = [
"sp-arithmetic/std",
"sp-core/std",
"sp-std/std",
"sp-io/std",
"sp-runtime/std",
]
try-runtime = [
"frame-system/try-runtime",
Expand Down
50 changes: 0 additions & 50 deletions pallets/living-assets-ownership/src/functions.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
//! Contains helper and utility functions of the pallet
use super::*;
use frame_support::sp_runtime::traits::One;
use sp_core::{H160, U256};

impl<T: Config> Pallet<T> {
/// See [Self::create_collection]
Expand All @@ -25,52 +24,3 @@ impl<T: Config> Pallet<T> {
Ok(collection_id)
}
}

pub fn convert_asset_id_to_owner(value: U256) -> H160 {
let mut bytes = [0u8; 20];
let value_bytes: [u8; 32] = value.into();
bytes.copy_from_slice(&value_bytes[value_bytes.len() - 20..]);
H160::from(bytes)
}

#[cfg(test)]
mod tests {
use crate::{functions::convert_asset_id_to_owner, H160, U256};

#[test]
fn check_convert_asset_id_to_owner() {
let value = U256::from(5);
let expected_address = H160::from_low_u64_be(5);
assert_eq!(convert_asset_id_to_owner(value), expected_address);
}

#[test]
fn check_two_assets_same_owner() {
// create two different assets
let asset1 = U256::from(
hex::decode("01C0F0f4ab324C46e55D02D0033343B4Be8A55532d").unwrap().as_slice(),
);
let asset2 = U256::from(
hex::decode("03C0F0f4ab324C46e55D02D0033343B4Be8A55532d").unwrap().as_slice(),
);
assert_ne!(asset1, asset2);

// check asset in decimal format
assert_eq!(
U256::from_str_radix("01C0F0f4ab324C46e55D02D0033343B4Be8A55532d", 16).unwrap(),
U256::from_dec_str("2563001357829637001682277476112176020532353127213").unwrap()
);
assert_eq!(
U256::from_str_radix("03C0F0f4ab324C46e55D02D0033343B4Be8A55532d", 16).unwrap(),
U256::from_dec_str("5486004632491442838089647141544742059844218213165").unwrap()
);

let mut owner = [0u8; 20];
owner.copy_from_slice(
hex::decode("C0F0f4ab324C46e55D02D0033343B4Be8A55532d").unwrap().as_slice(),
);
let expected_address = H160::from(owner);
assert_eq!(convert_asset_id_to_owner(asset1), expected_address);
assert_eq!(convert_asset_id_to_owner(asset2), expected_address);
}
}
39 changes: 28 additions & 11 deletions pallets/living-assets-ownership/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,22 @@
/// <https://docs.substrate.io/reference/frame-pallets/>
pub use pallet::*;
use parity_scale_codec::alloc::string::ToString;
use sp_core::{H160, U256};
use sp_core::H160;
use sp_std::vec::Vec;

mod functions;
pub mod traits;

#[frame_support::pallet]
pub mod pallet {

use crate::functions::convert_asset_id_to_owner;

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;
Expand All @@ -39,7 +38,7 @@ pub mod pallet {
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;

/// Specifies the advised maximum length for a Base URI.
//s
///
/// 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.
///
Expand All @@ -48,6 +47,20 @@ pub mod pallet {
/// (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 `AssetIdToAddress` trait for a given account ID type.
/// This allows you to specify which account should initially own each new asset.
type AssetIdToAddress: Convert<U256, Self::AccountId>;
}

/// Collection counter
Expand All @@ -64,10 +77,10 @@ pub mod pallet {
/// Asset owner
#[pallet::storage]
pub(super) type AssetOwner<T: Config> =
StorageMap<_, Blake2_128Concat, U256, H160, OptionQuery>;
StorageMap<_, Blake2_128Concat, U256, T::AccountId, OptionQuery>;

fn asset_owner<T: Config>(key: U256) -> H160 {
AssetOwner::<T>::get(key).unwrap_or_else(|| convert_asset_id_to_owner(key))
fn asset_owner<T: Config>(key: U256) -> T::AccountId {
AssetOwner::<T>::get(key).unwrap_or_else(|| T::AssetIdToAddress::convert(key))
}

/// Pallet events
Expand All @@ -79,7 +92,7 @@ pub mod pallet {
CollectionCreated { collection_id: CollectionId, who: T::AccountId },
/// Asset transferred to `who`
/// parameters. [asset_id_id, who]
AssetTransferred { asset_id: U256, receiver: H160 },
AssetTransferred { asset_id: U256, receiver: T::AccountId },
}

// Errors inform users that something went wrong.
Expand Down Expand Up @@ -153,7 +166,7 @@ pub mod pallet {

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(asset_owner::<T>(asset_id))
Ok(T::AccountIdToH160::convert(asset_owner::<T>(asset_id)))
}

fn transfer_from(
Expand All @@ -165,10 +178,14 @@ pub mod pallet {
) -> Result<(), Self::Error> {
Pallet::<T>::collection_base_uri(collection_id).ok_or(Error::CollectionDoesNotExist)?;
ensure!(origin == from, Error::NoPermission);
ensure!(asset_owner::<T>(asset_id) == from, Error::NoPermission);
ensure!(
T::AccountIdToH160::convert(asset_owner::<T>(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(asset_id, Some(to.clone()));
Self::deposit_event(Event::AssetTransferred { asset_id, receiver: to });

Expand Down
35 changes: 31 additions & 4 deletions pallets/living-assets-ownership/src/mock.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
use crate as pallet_livingassets_ownership;
use crate::{self as pallet_livingassets_ownership};
use frame_support::traits::{ConstU16, ConstU64};
use sp_core::{ConstU32, H256};
use sp_core::{ConstU32, H160, H256, U256};
use sp_runtime::{
traits::{BlakeTwo256, IdentityLookup},
traits::{BlakeTwo256, Convert, IdentityLookup},
BuildStorage,
};
use sp_std::{boxed::Box, prelude::*};

type Block = frame_system::mocking::MockBlock<Test>;
type Nonce = u32;
type AccountId = u64;

// Configure a mock runtime to test the pallet.
frame_support::construct_runtime!(
Expand All @@ -30,7 +31,7 @@ impl frame_system::Config for Test {
type Hash = H256;
type Nonce = Nonce;
type Hashing = BlakeTwo256;
type AccountId = u64;
type AccountId = AccountId;
type Lookup = IdentityLookup<Self::AccountId>;
type RuntimeEvent = RuntimeEvent;
type BlockHashCount = ConstU64<250>;
Expand All @@ -48,6 +49,32 @@ impl frame_system::Config for Test {
impl pallet_livingassets_ownership::Config for Test {
type RuntimeEvent = RuntimeEvent;
type BaseURILimit = ConstU32<256>;
type AccountIdToH160 = MockAccountIdToH160;
type H160ToAccountId = MockH160ToAccountId;
type AssetIdToAddress = MockAssetIdToAddress;
}

pub struct MockAccountIdToH160;
impl Convert<AccountId, H160> for MockAccountIdToH160 {
fn convert(account_id: AccountId) -> H160 {
H160::from_low_u64_be(account_id)
}
}
pub struct MockH160ToAccountId;
impl Convert<H160, AccountId> for MockH160ToAccountId {
fn convert(account_id: H160) -> AccountId {
H160::to_low_u64_be(&account_id)
}
}

pub struct MockAssetIdToAddress;
impl Convert<U256, AccountId> for MockAssetIdToAddress {
fn convert(asset_id: U256) -> AccountId {
let mut first_eight_bytes = [0u8; 8];
let asset_id_bytes: [u8; 32] = asset_id.into();
first_eight_bytes.copy_from_slice(&asset_id_bytes[asset_id_bytes.len() - 8..]);
u64::from_be_bytes(first_eight_bytes).into()
}
}

// Build genesis storage according to the mock runtime.
Expand Down
9 changes: 4 additions & 5 deletions pallets/living-assets-ownership/src/tests.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
use core::str::FromStr;

use crate::{
address_to_collection_id, collection_id_to_address, is_collection_address, mock::*, AssetOwner,
CollectionBaseURI, CollectionError, Event,
};
use core::str::FromStr;
use frame_support::assert_ok;
use sp_core::H160;

Expand Down Expand Up @@ -339,7 +338,7 @@ mod traits {
let asset_id = U256::from(
hex::decode("03C0F0f4ab324C46e55D02D0033343B4Be8A55532d").unwrap().as_slice(),
);
let sender = H160::from_str("C0F0f4ab324C46e55D02D0033343B4Be8A55532d").unwrap();
let sender = H160::from_str("0000000000000000000000003343b4be8a55532d").unwrap();
let receiver = H160::from_low_u64_be(BOB);
new_test_ext().execute_with(|| {
System::set_block_number(1);
Expand All @@ -349,9 +348,9 @@ mod traits {
assert_ok!(<LivingAssetsModule as Erc721>::transfer_from(
sender, 1, sender, receiver, asset_id,
));
assert_eq!(AssetOwner::<Test>::get(asset_id).unwrap(), receiver);
assert_eq!(AssetOwner::<Test>::get(asset_id).unwrap(), BOB);
assert_eq!(<LivingAssetsModule as Erc721>::owner_of(1, asset_id).unwrap(), receiver);
System::assert_last_event(Event::AssetTransferred { asset_id, receiver }.into());
System::assert_last_event(Event::AssetTransferred { asset_id, receiver: BOB }.into());
});
}

Expand Down
4 changes: 3 additions & 1 deletion precompile/erc721/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ where
let from: H160 = input.read::<Address>()?.into();
let to: H160 = input.read::<Address>()?.into();
let asset_id: U256 = input.read()?;
let mut asset_id_big_endian = [0u8; 32];
asset_id.to_big_endian(&mut asset_id_big_endian);

AssetManager::transfer_from(handle.context().caller, collection_id, from, to, asset_id)
.map_err(|err| revert(err))?;
Expand All @@ -101,7 +103,7 @@ where
SELECTOR_LOG_TRANSFER_FROM,
from,
to,
H256::from_slice(asset_id.encode().as_slice()),
H256::from_slice(asset_id_big_endian.as_slice()),
Vec::new(),
)
.record(handle)?;
Expand Down
61 changes: 61 additions & 0 deletions precompile/erc721/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,67 @@ mod transfer_from {
assert!(result.is_err());
assert_eq!(result.unwrap_err(), revert("this is an error"));
}

#[test]
fn check_log_selectors() {
assert_eq!(
hex::encode(SELECTOR_LOG_TRANSFER_FROM),
"ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
);
}

#[test]
fn transfer_from_should_generate_log() {
impl_precompile_mock_simple!(
Mock,
// owner_of result
Ok(H160::from_str("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap()),
// token_uri result
Ok(vec![]),
// transfer_from result
Ok(())
);

// test data
let from = H160::repeat_byte(0xAA);
let to = H160::repeat_byte(0xBB);
let asset_id = 4;
let contract_address = H160::from_str("ffffffffffffffffffffffff0000000000000005");

let input_data = EvmDataWriter::new_with_selector(Action::TransferFrom)
.write(Address(from))
.write(Address(to))
.write(U256::from(asset_id))
.build();

let mut handle = create_mock_handle(
input_data,
0,
0,
H160::from_str("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(),
);
handle.code_address = contract_address.unwrap();
assert_ok!(Mock::execute(&mut handle));

let logs = handle.logs;
assert_eq!(logs.len(), 1);
assert_eq!(logs[0].address, H160::zero());
assert_eq!(logs[0].topics.len(), 4);
assert_eq!(logs[0].topics[0], SELECTOR_LOG_TRANSFER_FROM.into());
assert_eq!(
hex::encode(logs[0].topics[1]),
"000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
);
assert_eq!(
hex::encode(logs[0].topics[2]),
"000000000000000000000000bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
);
assert_eq!(
hex::encode(logs[0].topics[3]),
"0000000000000000000000000000000000000000000000000000000000000004"
);
assert_eq!(logs[0].data, Vec::<u8>::new());
}
}
#[test]
fn token_uri_should_return_a_string() {
Expand Down
Loading