--- a/pallets/refungible/src/erc.rs +++ b/pallets/refungible/src/erc.rs @@ -1,3 +1,19 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// This file is part of Unique Network. + +// Unique Network is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Unique Network is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Unique Network. If not, see . + extern crate alloc; use evm_coder::{generate_stubgen, solidity_interface, types::*}; --- a/pallets/refungible/src/erc_token.rs +++ b/pallets/refungible/src/erc_token.rs @@ -149,6 +149,15 @@ .map_err(dispatch_to_evm::)?; Ok(true) } + + #[weight(>::repartition_item())] + fn repartition(&mut self, caller: caller, amount: uint256) -> Result { + let caller = T::CrossAccountId::from_eth(caller); + let amount = amount.try_into().map_err(|_| "amount overflow")?; + + >::repartition(self, &caller, self.1, amount).map_err(dispatch_to_evm::)?; + Ok(true) + } } impl RefungibleTokenHandle { --- a/pallets/refungible/src/stubs/UniqueRefungibleToken.sol +++ b/pallets/refungible/src/stubs/UniqueRefungibleToken.sol @@ -31,18 +31,6 @@ ); } -// Selector: 79cc6790 -contract ERC20UniqueExtensions is Dummy, ERC165 { - // Selector: burnFrom(address,uint256) 79cc6790 - function burnFrom(address from, uint256 amount) public returns (bool) { - require(false, stub_error); - from; - amount; - dummy = 0; - return false; - } -} - // Selector: 942e8b22 contract ERC20 is Dummy, ERC165, ERC20Events { // Selector: name() 06fdde03 @@ -127,4 +115,24 @@ } } +// Selector: ab8deb37 +contract ERC20UniqueExtensions is Dummy, ERC165 { + // Selector: burnFrom(address,uint256) 79cc6790 + function burnFrom(address from, uint256 amount) public returns (bool) { + require(false, stub_error); + from; + amount; + dummy = 0; + return false; + } + + // Selector: repartition(uint256) d2418ca7 + function repartition(uint256 amount) public returns (bool) { + require(false, stub_error); + amount; + dummy = 0; + return false; + } +} + contract UniqueRefungibleToken is Dummy, ERC165, ERC20, ERC20UniqueExtensions {} --- a/tests/src/eth/api/UniqueRefungibleToken.sol +++ b/tests/src/eth/api/UniqueRefungibleToken.sol @@ -22,12 +22,6 @@ ); } -// Selector: 79cc6790 -interface ERC20UniqueExtensions is Dummy, ERC165 { - // Selector: burnFrom(address,uint256) 79cc6790 - function burnFrom(address from, uint256 amount) external returns (bool); -} - // Selector: 942e8b22 interface ERC20 is Dummy, ERC165, ERC20Events { // Selector: name() 06fdde03 @@ -65,6 +59,15 @@ returns (uint256); } +// Selector: ab8deb37 +interface ERC20UniqueExtensions is Dummy, ERC165 { + // Selector: burnFrom(address,uint256) 79cc6790 + function burnFrom(address from, uint256 amount) external returns (bool); + + // Selector: repartition(uint256) d2418ca7 + function repartition(uint256 amount) external returns (bool); +} + interface UniqueRefungibleToken is Dummy, ERC165, --- a/tests/src/eth/reFungibleToken.test.ts +++ b/tests/src/eth/reFungibleToken.test.ts @@ -210,6 +210,39 @@ expect(+balance).to.equal(50); } }); + + itWeb3('Can perform repartition()', async ({web3, api, privateKeyWrapper}) => { + const alice = privateKeyWrapper('//Alice'); + + const collectionId = (await createCollection(api, alice, {name: 'token name', mode: {type: 'ReFungible'}})).collectionId; + + const owner = createEthAccount(web3); + await transferBalanceToEth(api, alice, owner); + + const receiver = createEthAccount(web3); + await transferBalanceToEth(api, alice, receiver); + + const tokenId = (await createRefungibleToken(api, alice, collectionId, 100n, {Ethereum: owner})).itemId; + + const address = tokenIdToAddress(collectionId, tokenId); + const contract = new web3.eth.Contract(reFungibleTokenAbi as any, address, {from: owner, ...GAS_ARGS}); + + await contract.methods.repartition(200).send({from: owner}); + expect(+await contract.methods.balanceOf(owner).call()).to.be.equal(200); + await contract.methods.transfer(receiver, 110).send({from: owner}); + expect(+await contract.methods.balanceOf(owner).call()).to.be.equal(90); + expect(+await contract.methods.balanceOf(receiver).call()).to.be.equal(110); + + await expect(contract.methods.repartition(80).send({from: owner})).to.eventually.be.rejected; + + await contract.methods.transfer(receiver, 90).send({from: owner}); + expect(+await contract.methods.balanceOf(owner).call()).to.be.equal(0); + expect(+await contract.methods.balanceOf(receiver).call()).to.be.equal(200); + + await contract.methods.repartition(150).send({from: receiver}); + await expect(contract.methods.transfer(owner, 160).send({from: receiver})).to.eventually.be.rejected; + expect(+await contract.methods.balanceOf(receiver).call()).to.be.equal(150); + }); }); describe('Refungible: Fees', () => { --- a/tests/src/eth/reFungibleTokenAbi.json +++ b/tests/src/eth/reFungibleTokenAbi.json @@ -104,6 +104,15 @@ }, { "inputs": [ + { "internalType": "uint256", "name": "amount", "type": "uint256" } + ], + "name": "repartition", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ { "internalType": "bytes4", "name": "interfaceID", "type": "bytes4" } ], "name": "supportsInterface",