difftreelog
chore add transfer events for transfering from and to partial ownership
in: master
3 files changed
pallets/refungible/src/erc.rsdiffbeforeafterboth34 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};5253pub const ADDRESS_FOR_PARTIALLY_OWNED_TOKENS: H160 = H160::repeat_byte(0xff);535454/// @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 }300301302 /// @notice Find the owner of an RFT303 /// @dev RFTs assigned to zero address are considered invalid, and queries304 /// about them do throw.305 /// Returns special 0xffffffffffffffffffffffffffffffffffffffff address for306 /// the tokens that are partially owned.307 /// @param tokenId The identifier for an RFT308 /// @return The address of the owner of the RFT301 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(owner306 .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 }309317310 /// @dev Not implemented318 /// @dev Not implemented366 <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>)?;368376369 <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 }379379652652653 <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 }665657pallets/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(())
}
tests/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', () => {