Skip to content
This repository has been archived by the owner on Oct 2, 2023. It is now read-only.

Commit

Permalink
Feature/precompile erc 721 transfer from (#129)
Browse files Browse the repository at this point in the history
* create mock and testing

* first test

* testing ethereum reserved addresses

* refactoring

* refactoring

* refactoring tests

* fmt

* erc721 starting point

* fmt

* compiling

* check selectors

* create trait Erc721

* Erc721Precompile

* set mocks

* first implermentation

* first integration of erc721 in the runtime

* fmt

* check on the collection id

* test on extract owner form asset_id

* fix tests

* test for erc721 trait

* fix compilation

* refactoring

* returning address

* return value is correctly encoded as a n Address

* is_erc721_contract is not member of PrecompoileSet

* test on precompiled contracts

* erc721 returns hardcoded address

* refactoring

* testing return of asset ownership

* fix errors

* remove unused use

* create collection got an uri

* refactoring

* rewriting testing mod

* refactoring

* moving check for cllection address in pallet

* collection address prefix changed

* test passing

* test passing

* refactoring tests

* do not panic the runtime

* fmt

* solidity tokenId -> _tokenId

* erc721 functions are views

* update docs

* sp-core in std , removed duplicate function

* documentaetion

* using compilator type ifer

* compiler infer the size of the array

* added to std feature

* rename variable

* trait documentation

* using pallet errors

* Erc721Error created

* Erc721Error fix compilation

* tests green

* revise docuemntation

* fmt

* better name of constant

* create collection has param uri

* remove unused error

* using sp-std

* using sp-std

* created CollectionBaseURI

* created CollectionBaseURI

* BaseURILimit is defined

* insering the base uri

* insering the base uri

* base_uri is set in storage

* create_collection trait has base_uri as param

* living_assets_ownership pallet tests are green

* tests passing

* test on base_uri too long

* removing use

* use Vec

* testing minimum arguments

* create_collection has a BondedVec tokenURI

* removed unued error

* fix compilation

* refactoring

* refactoring

* removed legacy doc

* BaseURILimit type is not public

* add transfer_from dummy tests and func definitions

* traits have Error assiciated tyope

* more generiuc signature

* fix compilation

* test moved where concreate impl is tested

* removed harcoded buffer from tests

* refactoring

* checking for BaseURI bounds

* add transfer_from sender and receiver checks

* add dummy erc721 impl to pallet for compiling

* erc721 transfer_from tests

* erc721 precompile final tests

* add transfer_from func signature to sol contract

* WIP add getting initial owner for assets storage

* add check to transfer_from precompile caller must be the owner

* add transfer_from trait impl to pallet

* fmt

* owner_of trait return current owner

* add remix test

* use H160 instead of T::AcountId

* change requests

* add eth event

* from u64 to H160

* wip

* remove pallet_evm could from living asset pallet

* change requests

* change requests

* change requests

* change requests

* change requests

---------

Co-authored-by: Alessandro Siniscalchi <[email protected]>
  • Loading branch information
magecnion and asiniscalchi authored Aug 25, 2023
1 parent ee62088 commit f0284e4
Show file tree
Hide file tree
Showing 8 changed files with 393 additions and 32 deletions.
32 changes: 31 additions & 1 deletion pallets/living-assets-ownership/src/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,42 @@ pub fn convert_asset_id_to_owner(value: U256) -> H160 {

#[cfg(test)]
mod tests {
use super::*;
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);
}
}
58 changes: 50 additions & 8 deletions pallets/living-assets-ownership/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ pub mod traits;

#[frame_support::pallet]
pub mod pallet {

use crate::functions::convert_asset_id_to_owner;

use super::*;
Expand Down Expand Up @@ -60,13 +61,25 @@ pub mod pallet {
pub(super) type CollectionBaseURI<T: Config> =
StorageMap<_, Blake2_128Concat, CollectionId, BaseURI<T>, OptionQuery>;

/// Asset owner
#[pallet::storage]
pub(super) type AssetOwner<T: Config> =
StorageMap<_, Blake2_128Concat, U256, H160, OptionQuery>;

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

/// 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: H160 },
}

// Errors inform users that something went wrong.
Expand All @@ -75,16 +88,28 @@ pub mod pallet {
pub enum Error<T> {
/// Collection id overflow
CollectionIdOverflow,
/// Unexistent collection
UnexistentCollection,
/// 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::UnexistentCollection => b"UnexistentCollection",
Error::CollectionDoesNotExist => b"CollectionDoesNotExist",
Error::NoPermission => b"NoPermission",
Error::AssetDoesNotExist => b"AssetDoesNotExist",
Error::CannotTransferSelf => b"CannotTransferSelf",
Error::TransferToNullAddress => b"TransferToNullAddress",
}
}
}
Expand Down Expand Up @@ -127,15 +152,32 @@ pub mod pallet {
type Error = Error<T>;

fn owner_of(collection_id: CollectionId, asset_id: U256) -> Result<H160, Self::Error> {
match CollectionBaseURI::<T>::get(collection_id) {
Some(_) => Ok(convert_asset_id_to_owner(asset_id)),
None => Err(Error::UnexistentCollection),
}
Pallet::<T>::collection_base_uri(collection_id).ok_or(Error::CollectionDoesNotExist)?;
Ok(asset_owner::<T>(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!(asset_owner::<T>(asset_id) == from, Error::NoPermission);
ensure!(from != to, Error::CannotTransferSelf);
ensure!(to != H160::zero(), Error::TransferToNullAddress);

AssetOwner::<T>::set(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::UnexistentCollection)?;
.ok_or(Error::CollectionDoesNotExist)?;

// concatenate base_uri with asset_id
let mut token_uri = base_uri.to_vec();
Expand Down
1 change: 1 addition & 0 deletions pallets/living-assets-ownership/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use sp_runtime::{
traits::{BlakeTwo256, IdentityLookup},
BuildStorage,
};
use sp_std::{boxed::Box, prelude::*};

type Block = frame_system::mocking::MockBlock<Test>;
type Nonce = u32;
Expand Down
125 changes: 119 additions & 6 deletions pallets/living-assets-ownership/src/tests.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
use core::str::FromStr;

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

type AccountId = <Test as frame_system::Config>::AccountId;
type BaseURI = crate::BaseURI<Test>;
type AccountId = <Test as frame_system::Config>::AccountId;

const ALICE: AccountId = 0x1234;
const BOB: AccountId = 0x2234;

#[test]
fn base_uri_unexistent_collection_is_none() {
Expand Down Expand Up @@ -126,7 +127,8 @@ mod traits {
traits::{CollectionManager, Erc721},
Error, Event,
};
use frame_support::{assert_err, assert_ok};
use frame_support::{assert_err, assert_noop, assert_ok};
use sp_core::U256;

#[test]
fn base_uri_of_unexistent_collection_is_none() {
Expand Down Expand Up @@ -223,7 +225,7 @@ mod traits {
fn owner_of_asset_of_unexistent_collection_should_error() {
new_test_ext().execute_with(|| {
let result = <LivingAssetsModule as Erc721>::owner_of(0, 2.into());
assert_err!(result, Error::UnexistentCollection);
assert_err!(result, Error::CollectionDoesNotExist);
});
}

Expand All @@ -242,11 +244,122 @@ mod traits {
});
}

#[test]
fn caller_is_not_current_owner_should_fail() {
let asset_id = U256::from(5);
let sender = H160::from_str("0000000000000000000000000000000000000006").unwrap();
let receiver = H160::from_low_u64_be(BOB);
new_test_ext().execute_with(|| {
System::set_block_number(1);
assert!(AssetOwner::<Test>::get(asset_id).is_none());
CollectionBaseURI::<Test>::insert(1, BaseURI::default());
assert_noop!(
<LivingAssetsModule as Erc721>::transfer_from(
H160::from_low_u64_be(ALICE),
1,
sender,
receiver,
asset_id,
),
Error::<Test>::NoPermission
);
});
}

#[test]
fn sender_is_not_current_owner_should_fail() {
let asset_id = U256::from(5);
let sender = H160::from_str("0000000000000000000000000000000000000006").unwrap();
let receiver = H160::from_low_u64_be(BOB);
new_test_ext().execute_with(|| {
System::set_block_number(1);
assert!(AssetOwner::<Test>::get(asset_id).is_none());
CollectionBaseURI::<Test>::insert(1, BaseURI::default());
assert_noop!(
<LivingAssetsModule as Erc721>::transfer_from(
sender, 1, sender, receiver, asset_id,
),
Error::<Test>::NoPermission
);
});
}

#[test]
fn same_sender_and_receiver_should_fail() {
let asset_id = U256::from(5);
let sender = H160::from_str("0000000000000000000000000000000000000005").unwrap();
new_test_ext().execute_with(|| {
System::set_block_number(1);
assert!(AssetOwner::<Test>::get(asset_id).is_none());
CollectionBaseURI::<Test>::insert(1, BaseURI::default());
assert_noop!(
<LivingAssetsModule as Erc721>::transfer_from(sender, 1, sender, sender, asset_id,),
Error::<Test>::CannotTransferSelf
);
});
}

#[test]
fn receiver_is_the_zero_address_should_fail() {
let asset_id = U256::from(5);
let sender = H160::from_str("0000000000000000000000000000000000000005").unwrap();
let receiver = H160::from_str("0000000000000000000000000000000000000000").unwrap();
new_test_ext().execute_with(|| {
System::set_block_number(1);
assert!(AssetOwner::<Test>::get(asset_id).is_none());
CollectionBaseURI::<Test>::insert(1, BaseURI::default());
assert_noop!(
<LivingAssetsModule as Erc721>::transfer_from(
sender, 1, sender, receiver, asset_id,
),
Error::<Test>::TransferToNullAddress
);
});
}

#[test]
fn unexistent_collection_when_transfer_from_should_fail() {
let asset_id = U256::from(5);
let sender = H160::from_str("0000000000000000000000000000000000000005").unwrap();
let receiver = H160::from_low_u64_be(BOB);
new_test_ext().execute_with(|| {
System::set_block_number(1);
assert!(AssetOwner::<Test>::get(asset_id).is_none());
assert_noop!(
<LivingAssetsModule as Erc721>::transfer_from(
sender, 1, sender, receiver, asset_id,
),
Error::<Test>::CollectionDoesNotExist
);
});
}

#[test]
fn sucessful_transfer_from_trait_should_work() {
let asset_id = U256::from(
hex::decode("03C0F0f4ab324C46e55D02D0033343B4Be8A55532d").unwrap().as_slice(),
);
let sender = H160::from_str("C0F0f4ab324C46e55D02D0033343B4Be8A55532d").unwrap();
let receiver = H160::from_low_u64_be(BOB);
new_test_ext().execute_with(|| {
System::set_block_number(1);
CollectionBaseURI::<Test>::insert(1, BaseURI::default());
assert!(AssetOwner::<Test>::get(asset_id).is_none());
assert_eq!(<LivingAssetsModule as Erc721>::owner_of(1, asset_id).unwrap(), sender);
assert_ok!(<LivingAssetsModule as Erc721>::transfer_from(
sender, 1, sender, receiver, asset_id,
));
assert_eq!(AssetOwner::<Test>::get(asset_id).unwrap(), receiver);
assert_eq!(<LivingAssetsModule as Erc721>::owner_of(1, asset_id).unwrap(), receiver);
System::assert_last_event(Event::AssetTransferred { asset_id, receiver }.into());
});
}

#[test]
fn token_uri_of_unexistent_collection() {
new_test_ext().execute_with(|| {
let result = <LivingAssetsModule as Erc721>::token_uri(0, 2.into());
assert_err!(result, Error::UnexistentCollection);
assert_err!(result, Error::CollectionDoesNotExist);
});
}

Expand Down
18 changes: 17 additions & 1 deletion pallets/living-assets-ownership/src/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,27 @@ pub trait Erc721 {
///
/// # Arguments
///
/// * `collection_id` - The unique identifier for the collection.
/// * `asset_id` - The unique identifier for the asset within the collection.
///
/// # Returns
///
/// A `Vec<u8>` representing the URI of the asset or an error if retrieval fails.
fn token_uri(collection_id: CollectionId, asset_id: U256) -> Result<Vec<u8>, Self::Error>;

/// Transfers the ownership of a asset from one address to another address
///
/// # Arguments
///
/// * `origin` - The caller's address.
/// * `collection_id` - The unique identifier for the collection.
/// * `from` - The current owner of the asset.
/// * `to` - The new owner.
/// * `asset_id` - The unique identifier for the asset within the collection.
fn transfer_from(
origin: H160,
collection_id: CollectionId,
from: H160,
to: H160,
asset_id: U256,
) -> Result<(), Self::Error>;
}
4 changes: 4 additions & 0 deletions precompile/erc721/contracts/IERC721.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,8 @@ interface IERC721 {
function tokenURI(uint256 _tokenId) external view returns (string memory);

function ownerOf(uint256 _tokenId) external view returns (address);

event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId);

function transferFrom(address _from, address _to, uint256 _tokenId) external;
}
Loading

0 comments on commit f0284e4

Please sign in to comment.