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
34 CommonEvmHandler, CollectionCall,34 CommonEvmHandler, CollectionCall,
35 static_property::{key, value as property_value},35 static_property::{key, value as property_value},
36 },36 },
37 eth::collection_id_to_address,
38};37};
39use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm, PrecompileHandle};38use pallet_evm::{account::CrossAccountId, PrecompileHandle};
40use pallet_evm_coder_substrate::{call, dispatch_to_evm};39use pallet_evm_coder_substrate::{call, dispatch_to_evm};
41use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _};40use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _};
42use sp_core::H160;41use sp_core::H160;
51 TokenProperties, TokensMinted, TotalSupply, weights::WeightInfo,50 TokenProperties, TokensMinted, TotalSupply, weights::WeightInfo,
52};51};
52
53pub const ADDRESS_FOR_PARTIALLY_OWNED_TOKENS: H160 = H160::repeat_byte(0xff);
5354
54/// @title A contract that allows to set and delete token properties and change token property permissions.55/// @title A contract that allows to set and delete token properties and change token property permissions.
55#[solidity_interface(name = "TokenProperties")]56#[solidity_interface(name = "TokenProperties")]
298 Ok(balance.into())299 Ok(balance.into())
299 }300 }
300301
302 /// @notice Find the owner of an RFT
303 /// @dev RFTs assigned to zero address are considered invalid, and queries
304 /// about them do throw.
305 /// Returns special 0xffffffffffffffffffffffffffffffffffffffff address for
306 /// the tokens that are partially owned.
307 /// @param tokenId The identifier for an RFT
308 /// @return The address of the owner of the RFT
301 fn owner_of(&self, token_id: uint256) -> Result<address> {309 fn owner_of(&self, token_id: uint256) -> Result<address> {
302 self.consume_store_reads(2)?;310 self.consume_store_reads(2)?;
303 let token = token_id.try_into()?;311 let token = token_id.try_into()?;
304 let owner = <Pallet<T>>::token_owner(self.id, token);312 let owner = <Pallet<T>>::token_owner(self.id, token);
305 Ok(owner313 Ok(owner
306 .map(|address| *address.as_eth())314 .map(|address| *address.as_eth())
307 .unwrap_or_else(|| H160::default()))315 .unwrap_or_else(|| ADDRESS_FOR_PARTIALLY_OWNED_TOKENS))
308 }316 }
309317
310 /// @dev Not implemented318 /// @dev Not implemented
366 <Pallet<T>>::transfer_from(self, &caller, &from, &to, token, balance, &budget)374 <Pallet<T>>::transfer_from(self, &caller, &from, &to, token, balance, &budget)
367 .map_err(dispatch_to_evm::<T>)?;375 .map_err(dispatch_to_evm::<T>)?;
368376
369 <PalletEvm<T>>::deposit_log(
370 ERC721Events::Transfer {
371 from: *from.as_eth(),
372 to: *to.as_eth(),
373 token_id: token_id.into(),
374 }
375 .to_log(collection_id_to_address(self.id)),
376 );
377 Ok(())377 Ok(())
378 }378 }
379379
652652
653 <Pallet<T>>::transfer(self, &caller, &to, token, balance, &budget)653 <Pallet<T>>::transfer(self, &caller, &to, token, balance, &budget)
654 .map_err(dispatch_to_evm::<T>)?;654 .map_err(dispatch_to_evm::<T>)?;
655 <PalletEvm<T>>::deposit_log(
656 ERC721Events::Transfer {
657 from: *caller.as_eth(),
658 to: *to.as_eth(),
659 token_id: token_id.into(),
660 }
661 .to_log(collection_id_to_address(self.id)),
662 );
663 Ok(())655 Ok(())
664 }656 }
665657
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
--- a/tests/src/eth/reFungible.test.ts
+++ b/tests/src/eth/reFungible.test.ts
@@ -14,7 +14,7 @@
 // You should have received a copy of the GNU General Public License
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
-import {createCollectionExpectSuccess, UNIQUE} from '../util/helpers';
+import {createCollectionExpectSuccess, transfer, UNIQUE} from '../util/helpers';
 import {collectionIdToAddress, createEthAccount, createEthAccountWithBalance, evmCollection, evmCollectionHelpers, GAS_ARGS, getCollectionAddressFromResult, itWeb3, normalizeEvents, recordEthFee, tokenIdToAddress} from './util/helpers';
 import reFungibleAbi from './reFungibleAbi.json';
 import reFungibleTokenAbi from './reFungibleTokenAbi.json';
@@ -96,6 +96,28 @@
 
     expect(owner).to.equal(receiver);
   });
+
+  itWeb3('ownerOf for partial ownership', async ({api, web3, privateKeyWrapper}) => {
+    const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    const receiver = createEthAccount(web3);
+    const helper = evmCollectionHelpers(web3, caller);
+    const result = await helper.methods.createRefungibleCollection('Mint collection', '6', '6').send();
+    const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);
+    const contract = evmCollection(web3, caller, collectionIdAddress, {type: 'ReFungible'});
+
+    const tokenId = await contract.methods.nextTokenId().call();
+    await contract.methods.mint(caller, tokenId).send();
+
+    const tokenAddress = tokenIdToAddress(collectionId, tokenId);
+    const tokenContract = new web3.eth.Contract(reFungibleTokenAbi as any, tokenAddress, {from: caller, ...GAS_ARGS});
+
+    await tokenContract.methods.repartition(2).send();
+    await tokenContract.methods.transfer(receiver, 1).send();
+
+    const owner = await contract.methods.ownerOf(tokenId).call();
+
+    expect(owner).to.equal('0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF');
+  });
 });
 
 describe('Refungible: Plain calls', () => {
@@ -293,6 +315,74 @@
       expect(+balance).to.equal(1);
     }
   });
+
+  itWeb3('transfer event on transfer from partial ownership to full ownership', async ({api, web3, privateKeyWrapper}) => {
+    const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    const receiver = createEthAccount(web3);
+    const helper = evmCollectionHelpers(web3, caller);
+    const result = await helper.methods.createRefungibleCollection('Mint collection', '6', '6').send();
+    const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);
+    const contract = evmCollection(web3, caller, collectionIdAddress, {type: 'ReFungible'});
+
+    const tokenId = await contract.methods.nextTokenId().call();
+    await contract.methods.mint(caller, tokenId).send();
+
+    const tokenAddress = tokenIdToAddress(collectionId, tokenId);
+    const tokenContract = new web3.eth.Contract(reFungibleTokenAbi as any, tokenAddress, {from: caller, ...GAS_ARGS});
+
+    await tokenContract.methods.repartition(2).send();
+    await tokenContract.methods.transfer(receiver, 1).send();
+
+    let transfer;
+    contract.events.Transfer({}, function(_error: any, event: any){ transfer = event;});
+    await tokenContract.methods.transfer(receiver, 1).send();
+    const events = normalizeEvents([transfer]);
+    expect(events).to.deep.equal([
+      {
+        address: collectionIdAddress,
+        event: 'Transfer',
+        args: {
+          from: '0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF',
+          to: receiver,
+          tokenId: tokenId.toString(),
+        },
+      },
+    ]);
+  });
+
+  itWeb3('transfer event on transfer from full ownership to partial ownership', async ({api, web3, privateKeyWrapper}) => {
+    const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    const receiver = createEthAccount(web3);
+    const helper = evmCollectionHelpers(web3, caller);
+    const result = await helper.methods.createRefungibleCollection('Mint collection', '6', '6').send();
+    const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);
+    const contract = evmCollection(web3, caller, collectionIdAddress, {type: 'ReFungible'});
+
+    const tokenId = await contract.methods.nextTokenId().call();
+    await contract.methods.mint(caller, tokenId).send();
+
+    const tokenAddress = tokenIdToAddress(collectionId, tokenId);
+    const tokenContract = new web3.eth.Contract(reFungibleTokenAbi as any, tokenAddress, {from: caller, ...GAS_ARGS});
+
+    await tokenContract.methods.repartition(2).send();
+    
+    let transfer;
+    contract.events.Transfer({}, function(_error: any, event: any){ transfer = event;});
+    await tokenContract.methods.transfer(receiver, 1).send();
+
+    const events = normalizeEvents([transfer]);
+    expect(events).to.deep.equal([
+      {
+        address: collectionIdAddress,
+        event: 'Transfer',
+        args: {
+          from: caller,
+          to: '0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF',
+          tokenId: tokenId.toString(),
+        },
+      },
+    ]);
+  });
 });
 
 describe('RFT: Fees', () => {