git.delta.rocks / unique-network / refs/commits / bb6d71bbdc4d

difftreelog

chore add transfer events for transfering from and to partial ownership

Grigoriy Simonov2022-08-03parent: #abc6b05.patch.diff
in: master

3 files changed

modifiedpallets/refungible/src/erc.rsdiffbeforeafterboth
--- a/pallets/refungible/src/erc.rs
+++ b/pallets/refungible/src/erc.rs
@@ -34,9 +34,8 @@
 		CommonEvmHandler, CollectionCall,
 		static_property::{key, value as property_value},
 	},
-	eth::collection_id_to_address,
 };
-use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm, PrecompileHandle};
+use pallet_evm::{account::CrossAccountId, PrecompileHandle};
 use pallet_evm_coder_substrate::{call, dispatch_to_evm};
 use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _};
 use sp_core::H160;
@@ -51,6 +50,8 @@
 	TokenProperties, TokensMinted, TotalSupply, weights::WeightInfo,
 };
 
+pub const ADDRESS_FOR_PARTIALLY_OWNED_TOKENS: H160 = H160::repeat_byte(0xff);
+
 /// @title A contract that allows to set and delete token properties and change token property permissions.
 #[solidity_interface(name = "TokenProperties")]
 impl<T: Config> RefungibleHandle<T> {
@@ -298,13 +299,20 @@
 		Ok(balance.into())
 	}
 
+	/// @notice Find the owner of an RFT
+	/// @dev RFTs assigned to zero address are considered invalid, and queries
+	///  about them do throw.
+	///  Returns special 0xffffffffffffffffffffffffffffffffffffffff address for
+	///  the tokens that are partially owned.
+	/// @param tokenId The identifier for an RFT
+	/// @return The address of the owner of the RFT
 	fn owner_of(&self, token_id: uint256) -> Result<address> {
 		self.consume_store_reads(2)?;
 		let token = token_id.try_into()?;
 		let owner = <Pallet<T>>::token_owner(self.id, token);
 		Ok(owner
 			.map(|address| *address.as_eth())
-			.unwrap_or_else(|| H160::default()))
+			.unwrap_or_else(|| ADDRESS_FOR_PARTIALLY_OWNED_TOKENS))
 	}
 
 	/// @dev Not implemented
@@ -366,14 +374,6 @@
 		<Pallet<T>>::transfer_from(self, &caller, &from, &to, token, balance, &budget)
 			.map_err(dispatch_to_evm::<T>)?;
 
-		<PalletEvm<T>>::deposit_log(
-			ERC721Events::Transfer {
-				from: *from.as_eth(),
-				to: *to.as_eth(),
-				token_id: token_id.into(),
-			}
-			.to_log(collection_id_to_address(self.id)),
-		);
 		Ok(())
 	}
 
@@ -652,14 +652,6 @@
 
 		<Pallet<T>>::transfer(self, &caller, &to, token, balance, &budget)
 			.map_err(dispatch_to_evm::<T>)?;
-		<PalletEvm<T>>::deposit_log(
-			ERC721Events::Transfer {
-				from: *caller.as_eth(),
-				to: *to.as_eth(),
-				token_id: token_id.into(),
-			}
-			.to_log(collection_id_to_address(self.id)),
-		);
 		Ok(())
 	}
 
modifiedpallets/refungible/src/lib.rsdiffbeforeafterboth
--- a/pallets/refungible/src/lib.rs
+++ b/pallets/refungible/src/lib.rs
@@ -707,12 +707,13 @@
 		}
 		<PalletCommon<T>>::ensure_correct_receiver(to)?;
 
-		let balance_from = <Balance<T>>::get((collection.id, token, from))
+		let initial_balance_from = <Balance<T>>::get((collection.id, token, from));
+		let updated_balance_from = initial_balance_from
 			.checked_sub(amount)
 			.ok_or(<CommonError<T>>::TokenValueTooLow)?;
 		let mut create_target = false;
 		let from_to_differ = from != to;
-		let balance_to = if from != to {
+		let updated_balance_to = if from != to {
 			let old_balance = <Balance<T>>::get((collection.id, token, to));
 			if old_balance == 0 {
 				create_target = true;
@@ -726,7 +727,7 @@
 			None
 		};
 
-		let account_balance_from = if balance_from == 0 {
+		let account_balance_from = if updated_balance_from == 0 {
 			Some(
 				<AccountBalance<T>>::get((collection.id, from))
 					.checked_sub(1)
@@ -762,15 +763,15 @@
 			nesting_budget,
 		)?;
 
-		if let Some(balance_to) = balance_to {
+		if let Some(updated_balance_to) = updated_balance_to {
 			// from != to
-			if balance_from == 0 {
+			if updated_balance_from == 0 {
 				<Balance<T>>::remove((collection.id, token, from));
 				<PalletStructure<T>>::unnest_if_nested(from, collection.id, token);
 			} else {
-				<Balance<T>>::insert((collection.id, token, from), balance_from);
+				<Balance<T>>::insert((collection.id, token, from), updated_balance_from);
 			}
-			<Balance<T>>::insert((collection.id, token, to), balance_to);
+			<Balance<T>>::insert((collection.id, token, to), updated_balance_to);
 			if let Some(account_balance_from) = account_balance_from {
 				<AccountBalance<T>>::insert((collection.id, from), account_balance_from);
 				<Owned<T>>::remove((collection.id, from, token));
@@ -800,6 +801,46 @@
 			to.clone(),
 			amount,
 		));
+
+		let total_supply = <TotalSupply<T>>::get((collection.id, token));
+
+		if amount == total_supply {
+			// if token was fully owned by `from` and will be fully owned by `to` after transfer
+			<PalletEvm<T>>::deposit_log(
+				ERC721Events::Transfer {
+					from: *from.as_eth(),
+					to: *to.as_eth(),
+					token_id: token.into(),
+				}
+				.to_log(collection_id_to_address(collection.id)),
+			);
+		} else if let Some(updated_balance_to) = updated_balance_to {
+			// if `from` not equals `to`. This condition is needed to avoid sending event
+			// when `from` fully owns token and sends part of token pieces to itself.
+			if initial_balance_from == total_supply {
+				// if token was fully owned by `from` and will be only partially owned by `to`
+				// and `from` after transfer
+				<PalletEvm<T>>::deposit_log(
+					ERC721Events::Transfer {
+						from: *from.as_eth(),
+						to: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,
+						token_id: token.into(),
+					}
+					.to_log(collection_id_to_address(collection.id)),
+				);
+			} else if updated_balance_to == total_supply {
+				// if token was partially owned by `from` and will be fully owned by `to` after transfer
+				<PalletEvm<T>>::deposit_log(
+					ERC721Events::Transfer {
+						from: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,
+						to: *to.as_eth(),
+						token_id: token.into(),
+					}
+					.to_log(collection_id_to_address(collection.id)),
+				);
+			}
+		}
+
 		Ok(())
 	}
 
modifiedtests/src/eth/reFungible.test.tsdiffbeforeafterboth
14// You should have received a copy of the GNU General Public License14// You should have received a copy of the GNU General Public License
15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
1616
17import {createCollectionExpectSuccess, UNIQUE} from '../util/helpers';17import {createCollectionExpectSuccess, transfer, UNIQUE} from '../util/helpers';
18import {collectionIdToAddress, createEthAccount, createEthAccountWithBalance, evmCollection, evmCollectionHelpers, GAS_ARGS, getCollectionAddressFromResult, itWeb3, normalizeEvents, recordEthFee, tokenIdToAddress} from './util/helpers';18import {collectionIdToAddress, createEthAccount, createEthAccountWithBalance, evmCollection, evmCollectionHelpers, GAS_ARGS, getCollectionAddressFromResult, itWeb3, normalizeEvents, recordEthFee, tokenIdToAddress} from './util/helpers';
19import reFungibleAbi from './reFungibleAbi.json';19import reFungibleAbi from './reFungibleAbi.json';
20import reFungibleTokenAbi from './reFungibleTokenAbi.json';20import reFungibleTokenAbi from './reFungibleTokenAbi.json';
97 expect(owner).to.equal(receiver);97 expect(owner).to.equal(receiver);
98 });98 });
99
100 itWeb3('ownerOf for partial ownership', async ({api, web3, privateKeyWrapper}) => {
101 const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
102 const receiver = createEthAccount(web3);
103 const helper = evmCollectionHelpers(web3, caller);
104 const result = await helper.methods.createRefungibleCollection('Mint collection', '6', '6').send();
105 const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);
106 const contract = evmCollection(web3, caller, collectionIdAddress, {type: 'ReFungible'});
107
108 const tokenId = await contract.methods.nextTokenId().call();
109 await contract.methods.mint(caller, tokenId).send();
110
111 const tokenAddress = tokenIdToAddress(collectionId, tokenId);
112 const tokenContract = new web3.eth.Contract(reFungibleTokenAbi as any, tokenAddress, {from: caller, ...GAS_ARGS});
113
114 await tokenContract.methods.repartition(2).send();
115 await tokenContract.methods.transfer(receiver, 1).send();
116
117 const owner = await contract.methods.ownerOf(tokenId).call();
118
119 expect(owner).to.equal('0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF');
120 });
99});121});
100122
101describe('Refungible: Plain calls', () => {123describe('Refungible: Plain calls', () => {
294 }316 }
295 });317 });
318
319 itWeb3('transfer event on transfer from partial ownership to full ownership', async ({api, web3, privateKeyWrapper}) => {
320 const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
321 const receiver = createEthAccount(web3);
322 const helper = evmCollectionHelpers(web3, caller);
323 const result = await helper.methods.createRefungibleCollection('Mint collection', '6', '6').send();
324 const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);
325 const contract = evmCollection(web3, caller, collectionIdAddress, {type: 'ReFungible'});
326
327 const tokenId = await contract.methods.nextTokenId().call();
328 await contract.methods.mint(caller, tokenId).send();
329
330 const tokenAddress = tokenIdToAddress(collectionId, tokenId);
331 const tokenContract = new web3.eth.Contract(reFungibleTokenAbi as any, tokenAddress, {from: caller, ...GAS_ARGS});
332
333 await tokenContract.methods.repartition(2).send();
334 await tokenContract.methods.transfer(receiver, 1).send();
335
336 let transfer;
337 contract.events.Transfer({}, function(_error: any, event: any){ transfer = event;});
338 await tokenContract.methods.transfer(receiver, 1).send();
339 const events = normalizeEvents([transfer]);
340 expect(events).to.deep.equal([
341 {
342 address: collectionIdAddress,
343 event: 'Transfer',
344 args: {
345 from: '0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF',
346 to: receiver,
347 tokenId: tokenId.toString(),
348 },
349 },
350 ]);
351 });
352
353 itWeb3('transfer event on transfer from full ownership to partial ownership', async ({api, web3, privateKeyWrapper}) => {
354 const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
355 const receiver = createEthAccount(web3);
356 const helper = evmCollectionHelpers(web3, caller);
357 const result = await helper.methods.createRefungibleCollection('Mint collection', '6', '6').send();
358 const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);
359 const contract = evmCollection(web3, caller, collectionIdAddress, {type: 'ReFungible'});
360
361 const tokenId = await contract.methods.nextTokenId().call();
362 await contract.methods.mint(caller, tokenId).send();
363
364 const tokenAddress = tokenIdToAddress(collectionId, tokenId);
365 const tokenContract = new web3.eth.Contract(reFungibleTokenAbi as any, tokenAddress, {from: caller, ...GAS_ARGS});
366
367 await tokenContract.methods.repartition(2).send();
368
369 let transfer;
370 contract.events.Transfer({}, function(_error: any, event: any){ transfer = event;});
371 await tokenContract.methods.transfer(receiver, 1).send();
372
373 const events = normalizeEvents([transfer]);
374 expect(events).to.deep.equal([
375 {
376 address: collectionIdAddress,
377 event: 'Transfer',
378 args: {
379 from: caller,
380 to: '0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF',
381 tokenId: tokenId.toString(),
382 },
383 },
384 ]);
385 });
296});386});
297387
298describe('RFT: Fees', () => {388describe('RFT: Fees', () => {