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
707 }707 }
708 <PalletCommon<T>>::ensure_correct_receiver(to)?;708 <PalletCommon<T>>::ensure_correct_receiver(to)?;
709709
710 let balance_from = <Balance<T>>::get((collection.id, token, from))710 let initial_balance_from = <Balance<T>>::get((collection.id, token, from));
711 let updated_balance_from = initial_balance_from
711 .checked_sub(amount)712 .checked_sub(amount)
712 .ok_or(<CommonError<T>>::TokenValueTooLow)?;713 .ok_or(<CommonError<T>>::TokenValueTooLow)?;
713 let mut create_target = false;714 let mut create_target = false;
714 let from_to_differ = from != to;715 let from_to_differ = from != to;
715 let balance_to = if from != to {716 let updated_balance_to = if from != to {
716 let old_balance = <Balance<T>>::get((collection.id, token, to));717 let old_balance = <Balance<T>>::get((collection.id, token, to));
717 if old_balance == 0 {718 if old_balance == 0 {
718 create_target = true;719 create_target = true;
726 None727 None
727 };728 };
728729
729 let account_balance_from = if balance_from == 0 {730 let account_balance_from = if updated_balance_from == 0 {
730 Some(731 Some(
731 <AccountBalance<T>>::get((collection.id, from))732 <AccountBalance<T>>::get((collection.id, from))
732 .checked_sub(1)733 .checked_sub(1)
762 nesting_budget,763 nesting_budget,
763 )?;764 )?;
764765
765 if let Some(balance_to) = balance_to {766 if let Some(updated_balance_to) = updated_balance_to {
766 // from != to767 // from != to
767 if balance_from == 0 {768 if updated_balance_from == 0 {
768 <Balance<T>>::remove((collection.id, token, from));769 <Balance<T>>::remove((collection.id, token, from));
769 <PalletStructure<T>>::unnest_if_nested(from, collection.id, token);770 <PalletStructure<T>>::unnest_if_nested(from, collection.id, token);
770 } else {771 } else {
771 <Balance<T>>::insert((collection.id, token, from), balance_from);772 <Balance<T>>::insert((collection.id, token, from), updated_balance_from);
772 }773 }
773 <Balance<T>>::insert((collection.id, token, to), balance_to);774 <Balance<T>>::insert((collection.id, token, to), updated_balance_to);
774 if let Some(account_balance_from) = account_balance_from {775 if let Some(account_balance_from) = account_balance_from {
775 <AccountBalance<T>>::insert((collection.id, from), account_balance_from);776 <AccountBalance<T>>::insert((collection.id, from), account_balance_from);
776 <Owned<T>>::remove((collection.id, from, token));777 <Owned<T>>::remove((collection.id, from, token));
801 amount,802 amount,
802 ));803 ));
804
805 let total_supply = <TotalSupply<T>>::get((collection.id, token));
806
807 if amount == total_supply {
808 // if token was fully owned by `from` and will be fully owned by `to` after transfer
809 <PalletEvm<T>>::deposit_log(
810 ERC721Events::Transfer {
811 from: *from.as_eth(),
812 to: *to.as_eth(),
813 token_id: token.into(),
814 }
815 .to_log(collection_id_to_address(collection.id)),
816 );
817 } else if let Some(updated_balance_to) = updated_balance_to {
818 // if `from` not equals `to`. This condition is needed to avoid sending event
819 // when `from` fully owns token and sends part of token pieces to itself.
820 if initial_balance_from == total_supply {
821 // if token was fully owned by `from` and will be only partially owned by `to`
822 // and `from` after transfer
823 <PalletEvm<T>>::deposit_log(
824 ERC721Events::Transfer {
825 from: *from.as_eth(),
826 to: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,
827 token_id: token.into(),
828 }
829 .to_log(collection_id_to_address(collection.id)),
830 );
831 } else if updated_balance_to == total_supply {
832 // if token was partially owned by `from` and will be fully owned by `to` after transfer
833 <PalletEvm<T>>::deposit_log(
834 ERC721Events::Transfer {
835 from: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,
836 to: *to.as_eth(),
837 token_id: token.into(),
838 }
839 .to_log(collection_id_to_address(collection.id)),
840 );
841 }
842 }
843
803 Ok(())844 Ok(())
804 }845 }
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', () => {