Skip to content

Commit

Permalink
Feature/evm tocken uri (#88)
Browse files Browse the repository at this point in the history
* laos-evolution pallet trait has tokenURI

* adding infrastructure in precompile for tockenURI

* testing the selector

* added closure

* testing unsexistent collection

* test name refactoring

* add tokenURI to interface

* fmt

* added test

* added test of precompile

* test refactoring

* when asset doesn't exist return error

* typo

* fmt
  • Loading branch information
asiniscalchi authored Oct 24, 2023
1 parent d08a060 commit b95d83e
Show file tree
Hide file tree
Showing 6 changed files with 131 additions and 20 deletions.
4 changes: 4 additions & 0 deletions evolution-chain/pallets/laos-evolution/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,10 @@ impl<T: Config> LaosEvolution<AccountIdOf<T>, TokenUriOf<T>> for Pallet<T> {
fn collection_owner(collection_id: CollectionId) -> Option<AccountIdOf<T>> {
CollectionOwner::<T>::get(collection_id)
}

fn token_uri(collection_id: CollectionId, token_id: TokenId) -> Option<TokenUriOf<T>> {
TokenURI::<T>::get(collection_id, token_id)
}
}

/// Converts `Slot` and `H160` to `TokenId`
Expand Down
36 changes: 35 additions & 1 deletion evolution-chain/pallets/laos-evolution/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use crate::{
};
use frame_support::{assert_noop, assert_ok};
use parity_scale_codec::Encode;
use sp_core::H160;
use sp_core::{H160, U256};

const ALICE: &str = "0x0000000000000000000000000000000000000005";
const BOB: &str = "0x0000000000000000000000000000000000000006";
Expand Down Expand Up @@ -272,3 +272,37 @@ fn collection_owner_works() {
);
})
}

#[test]
fn token_uri_of_unexistent_collection_returns_none() {
new_test_ext().execute_with(|| {
let tocken_id: U256 = 0_u64.into();
assert_eq!(LaosEvolution::token_uri(0_u64, tocken_id), None);
});
}

#[test]
fn token_uri_of_unexistent_token_returns_none() {
new_test_ext().execute_with(|| {
let collection_id = create_collection(ALICE);
let tocken_id: TokenId = 0_u64.into();
assert_eq!(LaosEvolution::token_uri(collection_id, tocken_id), None);
});
}

#[test]
fn token_uri_of_existent_token_returns_correct_token_uri() {
new_test_ext().execute_with(|| {
let who = AccountId::from_str(ALICE).unwrap();
let collection_id = create_collection(ALICE);
let slot = 1;
let to = AccountId::from_str(BOB).unwrap();
let token_uri: TokenUriOf<Test> =
vec![1, MaxTokenUriLength::get() as u8].try_into().unwrap();
let token_id =
LaosEvolution::mint_with_external_uri(who, collection_id, slot, to, token_uri.clone())
.unwrap();

assert_eq!(LaosEvolution::token_uri(collection_id, token_id), Some(token_uri));
});
}
3 changes: 3 additions & 0 deletions evolution-chain/pallets/laos-evolution/src/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,7 @@ pub trait LaosEvolution<AccountId, TokenUri> {

/// Get owner of the collection
fn collection_owner(collection_id: CollectionId) -> Option<AccountId>;

/// Get token URI of a token in a collection
fn token_uri(collection_id: CollectionId, token_id: TokenId) -> Option<TokenUri>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,14 @@ interface LaosEvolution {
/// @dev Call this function to get the owner of a collection
/// @param collectionId the id of the collection
/// @return the owner of the collection
function ownerOfCollection(
uint64 collectionId
) external view returns (address);
function ownerOfCollection(uint64 collectionId) external view returns (address);

/// @notice Provides a distinct Uniform Resource Identifier (URI) for a given token within a specified collection.
/// @dev Implementations must follow the ERC-721 standard for token URIs, which should point to a JSON file conforming to the "ERC721 Metadata JSON Schema".
/// @param collectionId The unique identifier of the collection to which the token belongs.
/// @param tokenId The unique identifier of the token within the specified collection.
/// @return A string representing the URI of the specified token.
function tokenURI(uint64 collectionId, uint256 tokenId) external view returns (string memory);

/// @notice Mint a new token
/// @dev Call this function to mint a new token, the caller must be the owner of the collection
Expand Down
22 changes: 19 additions & 3 deletions evolution-chain/precompile/laos-evolution/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

#![cfg_attr(not(feature = "std"), no_std)]
use fp_evm::{Log, Precompile, PrecompileHandle, PrecompileOutput};
use pallet_laos_evolution::{traits::LaosEvolution as LaosEvolutionT, Slot};
use pallet_laos_evolution::{traits::LaosEvolution as LaosEvolutionT, Slot, TokenId};
use parity_scale_codec::Encode;
use precompile_utils::{
keccak256, revert, revert_dispatch_error, succeed, Address, Bytes, EvmDataWriter, EvmResult,
Expand All @@ -25,6 +25,8 @@ pub enum Action {
CreateCollection = "createCollection(address)",
/// Get owner of the collection
OwnerOfCollection = "ownerOfCollection(uint64)",
/// Get tokenURI of the token in collection
TokenURI = "tokenURI(uint64,uint256)",
/// Mint token
Mint = "mintWithExternalUri(uint64,uint96,address,string)",
}
Expand All @@ -44,7 +46,7 @@ impl<AddressMapping, AccountId, TokenUri, LaosEvolution>
where
AddressMapping: pallet_evm::AddressMapping<AccountId>,
AccountId: From<H160> + Into<H160> + Encode + Debug,
TokenUri: TryFrom<Vec<u8>>,
TokenUri: TryFrom<Vec<u8>> + Into<Vec<u8>>,
LaosEvolution: LaosEvolutionT<AccountId, TokenUri>,
{
fn inner_execute(
Expand Down Expand Up @@ -87,6 +89,19 @@ where
Err(revert("collection does not exist"))
}
},
Action::TokenURI => {
let mut input = handle.read_input()?;
input.expect_arguments(2)?;

let collection_id = input.read::<u64>()?;
let token_id = input.read::<TokenId>()?;

if let Some(token_uri) = LaosEvolution::token_uri(collection_id, token_id) {
Ok(succeed(EvmDataWriter::new().write(Bytes(token_uri.into())).build()))
} else {
Err(revert("asset does not exist"))
}
},
Action::Mint => {
let caller = context.caller;

Expand Down Expand Up @@ -137,7 +152,7 @@ impl<AddressMapping, AccountId, TokenUri, LaosEvolution> Precompile
where
AddressMapping: pallet_evm::AddressMapping<AccountId>,
AccountId: From<H160> + Into<H160> + Encode + Debug,
TokenUri: TryFrom<Vec<u8>>,
TokenUri: TryFrom<Vec<u8>> + Into<Vec<u8>>,
LaosEvolution: LaosEvolutionT<AccountId, TokenUri>,
{
fn execute(handle: &mut impl PrecompileHandle) -> EvmResult<PrecompileOutput> {
Expand All @@ -147,6 +162,7 @@ where
Action::CreateCollection => FunctionModifier::NonPayable,
Action::Mint => FunctionModifier::NonPayable,
Action::OwnerOfCollection => FunctionModifier::View,
Action::TokenURI => FunctionModifier::View,
})?;

Self::inner_execute(handle, &selector)
Expand Down
75 changes: 62 additions & 13 deletions evolution-chain/precompile/laos-evolution/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ fn check_log_selectors() {
fn function_selectors() {
assert_eq!(Action::CreateCollection as u32, 0x2069E953);
assert_eq!(Action::OwnerOfCollection as u32, 0xFB34AE53);
assert_eq!(Action::TokenURI as u32, 0xC8A3F102);
assert_eq!(Action::Mint as u32, 0xEAD3F711);
}

Expand All @@ -45,7 +46,8 @@ fn failing_create_collection_should_return_error() {
Mock,
Err(DispatchError::Other("this is an error")),
None,
Ok(0.into())
Ok(0.into()),
None
);

let input = EvmDataWriter::new_with_selector(Action::CreateCollection)
Expand All @@ -63,7 +65,7 @@ fn failing_create_collection_should_return_error() {

#[test]
fn create_collection_should_return_collection_id() {
impl_precompile_mock_simple!(Mock, Ok(0), None, Ok(0.into()));
impl_precompile_mock_simple!(Mock, Ok(0), None, Ok(0.into()), None);

let input = EvmDataWriter::new_with_selector(Action::CreateCollection)
.write(Address(H160([1u8; 20])))
Expand All @@ -76,7 +78,7 @@ fn create_collection_should_return_collection_id() {

#[test]
fn create_collection_should_generate_log() {
impl_precompile_mock_simple!(Mock, Ok(0), None, Ok(0.into()));
impl_precompile_mock_simple!(Mock, Ok(0), None, Ok(0.into()), None);

let input = EvmDataWriter::new_with_selector(Action::CreateCollection)
.write(Address(H160([1u8; 20])))
Expand All @@ -96,7 +98,7 @@ fn create_collection_should_generate_log() {

#[test]
fn create_collection_on_mock_with_nonzero_value_fails() {
impl_precompile_mock_simple!(Mock, Ok(5), None, Ok(0.into()));
impl_precompile_mock_simple!(Mock, Ok(5), None, Ok(0.into()), None);

let input = EvmDataWriter::new_with_selector(Action::CreateCollection)
.write(Address(H160([1u8; 20])))
Expand All @@ -117,7 +119,8 @@ fn create_collection_assign_collection_to_caller() {
Ok(0)
}, // Closure for create_collection result
|_| { None }, // Closure for collection_owner result
|_, _, _, _, _| { Ok(0.into()) } // Closure for mint result
|_, _, _, _, _| { Ok(0.into()) }, // Closure for mint result
|_, _| { None } // Closure for token_uri result
);

let input = EvmDataWriter::new_with_selector(Action::CreateCollection)
Expand All @@ -131,7 +134,7 @@ fn create_collection_assign_collection_to_caller() {

#[test]
fn call_unexistent_selector_should_fail() {
impl_precompile_mock_simple!(Mock, Ok(0), None, Ok(0.into()));
impl_precompile_mock_simple!(Mock, Ok(0), None, Ok(0.into()), None);

let nonexistent_selector =
hex::decode("fb24ae530000000000000000000000000000000000000000000000000000000000000000")
Expand All @@ -143,7 +146,7 @@ fn call_unexistent_selector_should_fail() {

#[test]
fn call_owner_of_non_existent_collection() {
impl_precompile_mock_simple!(Mock, Ok(0), None, Ok(0.into()));
impl_precompile_mock_simple!(Mock, Ok(0), None, Ok(0.into()), None);

let input = EvmDataWriter::new_with_selector(Action::OwnerOfCollection)
.write(U256::from(0))
Expand All @@ -155,7 +158,13 @@ fn call_owner_of_non_existent_collection() {

#[test]
fn call_owner_of_collection_works() {
impl_precompile_mock_simple!(Mock, Ok(0), Some(H160::from_low_u64_be(0x1234)), Ok(0.into()));
impl_precompile_mock_simple!(
Mock,
Ok(0),
Some(H160::from_low_u64_be(0x1234)),
Ok(0.into()),
None
);

let owner = H160::from_low_u64_be(0x1234);

Expand All @@ -168,9 +177,43 @@ fn call_owner_of_collection_works() {
assert_eq!(result, succeed(EvmDataWriter::new().write(Address(owner.into())).build()));
}

#[test]
fn token_uri_returns_nothing_when_source_token_uri_is_none() {
impl_precompile_mock_simple!(Mock, Ok(0), None, Ok(0.into()), None);

let input = EvmDataWriter::new_with_selector(Action::TokenURI)
.write(0_u64)
.write(TokenId::from(0))
.build();

let mut handle = create_mock_handle_from_input(input);
let result = Mock::execute(&mut handle);
assert_eq!(result.unwrap_err(), revert("asset does not exist"));
}

#[test]
fn token_uri_returns_the_result_from_source() {
impl_precompile_mock_simple!(Mock, Ok(0), None, Ok(0.into()), Some(vec![1_u8, 10]));

let input = EvmDataWriter::new_with_selector(Action::TokenURI)
.write(0_u64)
.write(TokenId::from(0))
.build();

let mut handle = create_mock_handle_from_input(input);
let result = Mock::execute(&mut handle);
assert_eq!(result.unwrap(), succeed(EvmDataWriter::new().write(Bytes(vec![1_u8, 10])).build()));
}

#[test]
fn mint_works() {
impl_precompile_mock_simple!(Mock, Ok(0), Some(H160::from_low_u64_be(0x1234)), Ok(1.into()));
impl_precompile_mock_simple!(
Mock,
Ok(0),
Some(H160::from_low_u64_be(0x1234)),
Ok(1.into()),
None
);

let to = H160::from_low_u64_be(1);

Expand All @@ -193,7 +236,8 @@ fn failing_mint_should_return_error() {
Mock,
Ok(0),
Some(H160::from_low_u64_be(0x1234)),
Err(DispatchError::Other("this is error"))
Err(DispatchError::Other("this is error")),
None
);

let to = H160::from_low_u64_be(1);
Expand Down Expand Up @@ -232,7 +276,7 @@ mod helpers {
/// ```
#[macro_export]
macro_rules! impl_precompile_mock {
($name:ident, $create_collection_result:expr, $collection_owner_result:expr, $mint_result:expr) => {
($name:ident, $create_collection_result:expr, $collection_owner_result:expr, $mint_result:expr, $token_uri_result:expr ) => {
use pallet_laos_evolution::types::*;
use sp_runtime::DispatchError;
type TokenUri = Vec<u8>;
Expand All @@ -259,6 +303,10 @@ mod helpers {
fn collection_owner(collection_id: CollectionId) -> Option<AccountId> {
($collection_owner_result)(collection_id)
}

fn token_uri(_collection_id: CollectionId, _token_id: TokenId) -> Option<TokenUri> {
($token_uri_result)(_collection_id, _token_id)
}
}

type $name =
Expand All @@ -284,12 +332,13 @@ mod helpers {
/// ```
#[macro_export]
macro_rules! impl_precompile_mock_simple {
($name:ident, $create_collection_result:expr, $collection_owner_result:expr, $mint_result:expr) => {
($name:ident, $create_collection_result:expr, $collection_owner_result:expr, $mint_result:expr, $token_uri_result:expr) => {
impl_precompile_mock!(
$name,
|_owner| { $create_collection_result },
|_collection_id| { $collection_owner_result },
|_who, _collection_id, _slot, _to, _token_uri| { $mint_result }
|_who, _collection_id, _slot, _to, _token_uri| { $mint_result },
|_collection_id, _token_id| { $token_uri_result }
);
};
}
Expand Down

0 comments on commit b95d83e

Please sign in to comment.