difftreelog
refactor rename function
in: master
11 files changed
pallets/unique/src/eth/mod.rsdiffbeforeafterboth--- a/pallets/unique/src/eth/mod.rs
+++ b/pallets/unique/src/eth/mod.rs
@@ -245,6 +245,7 @@
}
#[weight(<SelfWeightOf<T>>::create_collection())]
+ #[solidity(rename_selector = "createRFTCollection")]
fn create_refungible_collection(
&mut self,
caller: caller,
pallets/unique/src/eth/stubs/CollectionHelpers.rawdiffbeforeafterbothbinary blob — no preview
pallets/unique/src/eth/stubs/CollectionHelpers.soldiffbeforeafterboth--- a/pallets/unique/src/eth/stubs/CollectionHelpers.sol
+++ b/pallets/unique/src/eth/stubs/CollectionHelpers.sol
@@ -30,7 +30,7 @@
}
/// @title Contract, which allows users to operate with collections
-/// @dev the ERC-165 identifier for this interface is 0x675f3074
+/// @dev the ERC-165 identifier for this interface is 0x88ee8ef1
contract CollectionHelpers is Dummy, ERC165, CollectionHelpersEvents {
/// Create an NFT collection
/// @param name Name of the collection
@@ -69,9 +69,9 @@
return 0x0000000000000000000000000000000000000000;
}
- /// @dev EVM selector for this function is: 0x44a68ad5,
- /// or in textual repr: createRefungibleCollection(string,string,string)
- function createRefungibleCollection(
+ /// @dev EVM selector for this function is: 0xab173450,
+ /// or in textual repr: createRFTCollection(string,string,string)
+ function createRFTCollection(
string memory name,
string memory description,
string memory tokenPrefix
tests/src/eth/api/CollectionHelpers.soldiffbeforeafterboth--- a/tests/src/eth/api/CollectionHelpers.sol
+++ b/tests/src/eth/api/CollectionHelpers.sol
@@ -21,7 +21,7 @@
}
/// @title Contract, which allows users to operate with collections
-/// @dev the ERC-165 identifier for this interface is 0x675f3074
+/// @dev the ERC-165 identifier for this interface is 0x88ee8ef1
interface CollectionHelpers is Dummy, ERC165, CollectionHelpersEvents {
/// Create an NFT collection
/// @param name Name of the collection
@@ -45,9 +45,9 @@
string memory baseUri
) external returns (address);
- /// @dev EVM selector for this function is: 0x44a68ad5,
- /// or in textual repr: createRefungibleCollection(string,string,string)
- function createRefungibleCollection(
+ /// @dev EVM selector for this function is: 0xab173450,
+ /// or in textual repr: createRFTCollection(string,string,string)
+ function createRFTCollection(
string memory name,
string memory description,
string memory tokenPrefix
tests/src/eth/collectionHelpersAbi.jsondiffbeforeafterboth--- a/tests/src/eth/collectionHelpersAbi.json
+++ b/tests/src/eth/collectionHelpersAbi.json
@@ -59,7 +59,7 @@
{ "internalType": "string", "name": "description", "type": "string" },
{ "internalType": "string", "name": "tokenPrefix", "type": "string" }
],
- "name": "createRefungibleCollection",
+ "name": "createRFTCollection",
"outputs": [{ "internalType": "address", "name": "", "type": "address" }],
"stateMutability": "nonpayable",
"type": "function"
tests/src/eth/createRFTCollection.test.tsdiffbeforeafterboth--- a/tests/src/eth/createRFTCollection.test.ts
+++ b/tests/src/eth/createRFTCollection.test.ts
@@ -41,7 +41,7 @@
const collectionCountBefore = await getCreatedCollectionCount(api);
const result = await collectionHelper.methods
- .createRefungibleCollection(collectionName, description, tokenPrefix)
+ .createRFTCollection(collectionName, description, tokenPrefix)
.send();
const collectionCountAfter = await getCreatedCollectionCount(api);
@@ -65,7 +65,7 @@
.call()).to.be.false;
await collectionHelpers.methods
- .createRefungibleCollection('A', 'A', 'A')
+ .createRFTCollection('A', 'A', 'A')
.send();
expect(await collectionHelpers.methods
@@ -76,7 +76,7 @@
itWeb3('Set sponsorship', async ({api, web3, privateKeyWrapper}) => {
const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
const collectionHelpers = evmCollectionHelpers(web3, owner);
- let result = await collectionHelpers.methods.createRefungibleCollection('Sponsor collection', '1', '1').send();
+ let result = await collectionHelpers.methods.createRFTCollection('Sponsor collection', '1', '1').send();
const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);
const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
const collectionEvm = evmCollection(web3, owner, collectionIdAddress, {type: 'ReFungible'});
@@ -96,7 +96,7 @@
itWeb3('Set limits', async ({api, web3, privateKeyWrapper}) => {
const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
const collectionHelpers = evmCollectionHelpers(web3, owner);
- const result = await collectionHelpers.methods.createRefungibleCollection('Const collection', '5', '5').send();
+ const result = await collectionHelpers.methods.createRFTCollection('Const collection', '5', '5').send();
const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);
const limits = {
accountTokenOwnershipLimit: 1000,
@@ -141,7 +141,7 @@
.isCollectionExist(collectionAddressForNonexistentCollection).call())
.to.be.false;
- const result = await collectionHelpers.methods.createRefungibleCollection('Collection address exist', '7', '7').send();
+ const result = await collectionHelpers.methods.createRFTCollection('Collection address exist', '7', '7').send();
const {collectionIdAddress} = await getCollectionAddressFromResult(api, result);
expect(await collectionHelpers.methods
.isCollectionExist(collectionIdAddress).call())
@@ -164,7 +164,7 @@
const tokenPrefix = 'A';
await expect(helper.methods
- .createRefungibleCollection(collectionName, description, tokenPrefix)
+ .createRFTCollection(collectionName, description, tokenPrefix)
.call()).to.be.rejectedWith('name is too long. Max length is ' + MAX_NAME_LENGHT);
}
@@ -174,7 +174,7 @@
const description = 'A'.repeat(MAX_DESCRIPTION_LENGHT + 1);
const tokenPrefix = 'A';
await expect(helper.methods
- .createRefungibleCollection(collectionName, description, tokenPrefix)
+ .createRFTCollection(collectionName, description, tokenPrefix)
.call()).to.be.rejectedWith('description is too long. Max length is ' + MAX_DESCRIPTION_LENGHT);
}
{
@@ -183,7 +183,7 @@
const description = 'A';
const tokenPrefix = 'A'.repeat(MAX_TOKEN_PREFIX_LENGHT + 1);
await expect(helper.methods
- .createRefungibleCollection(collectionName, description, tokenPrefix)
+ .createRFTCollection(collectionName, description, tokenPrefix)
.call()).to.be.rejectedWith('token_prefix is too long. Max length is ' + MAX_TOKEN_PREFIX_LENGHT);
}
});
@@ -196,7 +196,7 @@
const tokenPrefix = 'A';
await expect(helper.methods
- .createRefungibleCollection(collectionName, description, tokenPrefix)
+ .createRFTCollection(collectionName, description, tokenPrefix)
.call()).to.be.rejectedWith('NotSufficientFounds');
});
@@ -204,7 +204,7 @@
const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
const notOwner = createEthAccount(web3);
const collectionHelpers = evmCollectionHelpers(web3, owner);
- const result = await collectionHelpers.methods.createRefungibleCollection('A', 'A', 'A').send();
+ const result = await collectionHelpers.methods.createRFTCollection('A', 'A', 'A').send();
const {collectionIdAddress} = await getCollectionAddressFromResult(api, result);
const contractEvmFromNotOwner = evmCollection(web3, notOwner, collectionIdAddress, {type: 'ReFungible'});
const EXPECTED_ERROR = 'NoPermission';
@@ -229,7 +229,7 @@
itWeb3('(!negative test!) Set limits', async ({api, web3, privateKeyWrapper}) => {
const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
const collectionHelpers = evmCollectionHelpers(web3, owner);
- const result = await collectionHelpers.methods.createRefungibleCollection('Schema collection', 'A', 'A').send();
+ const result = await collectionHelpers.methods.createRFTCollection('Schema collection', 'A', 'A').send();
const {collectionIdAddress} = await getCollectionAddressFromResult(api, result);
const collectionEvm = evmCollection(web3, owner, collectionIdAddress, {type: 'ReFungible'});
await expect(collectionEvm.methods
tests/src/eth/fractionalizer/Fractionalizer.soldiffbeforeafterboth--- a/tests/src/eth/fractionalizer/Fractionalizer.sol
+++ b/tests/src/eth/fractionalizer/Fractionalizer.sol
@@ -25,7 +25,9 @@
/// @dev Method modifier to only allow contract owner to call it.
modifier onlyOwner() {
address contracthelpersAddress = 0x842899ECF380553E8a4de75bF534cdf6fBF64049;
- ContractHelpers contractHelpers = ContractHelpers(contracthelpersAddress);
+ ContractHelpers contractHelpers = ContractHelpers(
+ contracthelpersAddress
+ );
address contractOwner = contractHelpers.contractOwner(address(this));
require(msg.sender == contractOwner, "Only owner can");
_;
@@ -38,10 +40,19 @@
event AllowListSet(address _collection, bool _status);
/// @dev This emits when NFT token is fractionalized by contract.
- event Fractionalized(address _collection, uint256 _tokenId, address _rftToken, uint128 _amount);
+ event Fractionalized(
+ address _collection,
+ uint256 _tokenId,
+ address _rftToken,
+ uint128 _amount
+ );
/// @dev This emits when NFT token is defractionalized by contract.
- event Defractionalized(address _rftToken, address _nftCollection, uint256 _nftTokenId);
+ event Defractionalized(
+ address _rftToken,
+ address _nftCollection,
+ uint256 _nftTokenId
+ );
/// Set RFT collection that contract will work with. RFT tokens for fractionalized NFT tokens
/// would be created in this collection.
@@ -52,13 +63,11 @@
/// Can only be called by contract owner.
/// @param _collection address of RFT collection.
function setRFTCollection(address _collection) public onlyOwner {
- require(
- rftCollection == address(0),
- "RFT collection is already set"
- );
+ require(rftCollection == address(0), "RFT collection is already set");
UniqueRefungible refungibleContract = UniqueRefungible(_collection);
- string memory collectionType = refungibleContract.uniqueCollectionType();
-
+ string memory collectionType = refungibleContract
+ .uniqueCollectionType();
+
require(
keccak256(bytes(collectionType)) == refungibleCollectionType,
"Wrong collection type. Collection is not refungible."
@@ -78,13 +87,15 @@
/// @param _name name for created RFT collection.
/// @param _description description for created RFT collection.
/// @param _tokenPrefix token prefix for created RFT collection.
- function createAndSetRFTCollection(string calldata _name, string calldata _description, string calldata _tokenPrefix) public onlyOwner {
- require(
- rftCollection == address(0),
- "RFT collection is already set"
- );
+ function createAndSetRFTCollection(
+ string calldata _name,
+ string calldata _description,
+ string calldata _tokenPrefix
+ ) public onlyOwner {
+ require(rftCollection == address(0), "RFT collection is already set");
address collectionHelpers = 0x6C4E9fE1AE37a41E93CEE429e8E1881aBdcbb54F;
- rftCollection = CollectionHelpers(collectionHelpers).createRefungibleCollection(_name, _description, _tokenPrefix);
+ rftCollection = CollectionHelpers(collectionHelpers)
+ .createRFTCollection(_name, _description, _tokenPrefix);
emit RFTCollectionSet(rftCollection);
}
@@ -92,7 +103,10 @@
/// @dev Can only be called by contract owner.
/// @param collection NFT token address.
/// @param status `true` to allow and `false` to disallow NFT token.
- function setNftCollectionIsAllowed(address collection, bool status) public onlyOwner {
+ function setNftCollectionIsAllowed(address collection, bool status)
+ public
+ onlyOwner
+ {
nftCollectionAllowList[collection] = status;
emit AllowListSet(collection, status);
}
@@ -107,12 +121,15 @@
/// @param _collection NFT collection address
/// @param _token id of NFT token to be fractionalized
/// @param _pieces number of pieces new RFT token would have
- function nft2rft(address _collection, uint256 _token, uint128 _pieces) public {
- require(
- rftCollection != address(0),
- "RFT collection is not set"
+ function nft2rft(
+ address _collection,
+ uint256 _token,
+ uint128 _pieces
+ ) public {
+ require(rftCollection != address(0), "RFT collection is not set");
+ UniqueRefungible rftCollectionContract = UniqueRefungible(
+ rftCollection
);
- UniqueRefungible rftCollectionContract = UniqueRefungible(rftCollection);
require(
nftCollectionAllowList[_collection] == true,
"Fractionalization of this collection is not allowed by admin"
@@ -121,25 +138,25 @@
UniqueNFT(_collection).ownerOf(_token) == msg.sender,
"Only token owner could fractionalize it"
);
- UniqueNFT(_collection).transferFrom(
- msg.sender,
- address(this),
- _token
- );
+ UniqueNFT(_collection).transferFrom(msg.sender, address(this), _token);
uint256 rftTokenId;
address rftTokenAddress;
UniqueRefungibleToken rftTokenContract;
if (nft2rftMapping[_collection][_token] == 0) {
rftTokenId = rftCollectionContract.nextTokenId();
rftCollectionContract.mint(address(this), rftTokenId);
- rftTokenAddress = rftCollectionContract.tokenContractAddress(rftTokenId);
+ rftTokenAddress = rftCollectionContract.tokenContractAddress(
+ rftTokenId
+ );
nft2rftMapping[_collection][_token] = rftTokenId;
rft2nftMapping[rftTokenAddress] = Token(_collection, _token);
rftTokenContract = UniqueRefungibleToken(rftTokenAddress);
} else {
rftTokenId = nft2rftMapping[_collection][_token];
- rftTokenAddress = rftCollectionContract.tokenContractAddress(rftTokenId);
+ rftTokenAddress = rftCollectionContract.tokenContractAddress(
+ rftTokenId
+ );
rftTokenContract = UniqueRefungibleToken(rftTokenAddress);
}
rftTokenContract.repartition(_pieces);
@@ -157,24 +174,25 @@
/// @param _collection RFT collection address
/// @param _token id of RFT token
function rft2nft(address _collection, uint256 _token) public {
- require(
- rftCollection != address(0),
- "RFT collection is not set"
+ require(rftCollection != address(0), "RFT collection is not set");
+ require(rftCollection == _collection, "Wrong RFT collection");
+ UniqueRefungible rftCollectionContract = UniqueRefungible(
+ rftCollection
);
- require(
- rftCollection == _collection,
- "Wrong RFT collection"
+ address rftTokenAddress = rftCollectionContract.tokenContractAddress(
+ _token
);
- UniqueRefungible rftCollectionContract = UniqueRefungible(rftCollection);
- address rftTokenAddress = rftCollectionContract.tokenContractAddress(_token);
Token memory nftToken = rft2nftMapping[rftTokenAddress];
require(
nftToken._collection != address(0),
"No corresponding NFT token found"
);
- UniqueRefungibleToken rftTokenContract = UniqueRefungibleToken(rftTokenAddress);
+ UniqueRefungibleToken rftTokenContract = UniqueRefungibleToken(
+ rftTokenAddress
+ );
require(
- rftTokenContract.balanceOf(msg.sender) == rftTokenContract.totalSupply(),
+ rftTokenContract.balanceOf(msg.sender) ==
+ rftTokenContract.totalSupply(),
"Not all pieces are owned by the caller"
);
rftCollectionContract.transferFrom(msg.sender, address(this), _token);
@@ -183,6 +201,10 @@
msg.sender,
nftToken._tokenId
);
- emit Defractionalized(rftTokenAddress, nftToken._collection, nftToken._tokenId);
+ emit Defractionalized(
+ rftTokenAddress,
+ nftToken._collection,
+ nftToken._tokenId
+ );
}
-}
\ No newline at end of file
+}
tests/src/eth/fractionalizer/fractionalizer.test.tsdiffbeforeafterboth--- a/tests/src/eth/fractionalizer/fractionalizer.test.ts
+++ b/tests/src/eth/fractionalizer/fractionalizer.test.ts
@@ -21,7 +21,7 @@
import {readFile} from 'fs/promises';
import {executeTransaction, submitTransactionAsync} from '../../substrate/substrate-api';
import {getCreateCollectionResult, getCreateItemResult, UNIQUE, requirePallets, Pallets} from '../../util/helpers';
-import {collectionIdToAddress, CompiledContract, createEthAccountWithBalance, createNonfungibleCollection, createRefungibleCollection, GAS_ARGS, itWeb3, tokenIdFromAddress, uniqueNFT, uniqueRefungible, uniqueRefungibleToken} from '../util/helpers';
+import {collectionIdToAddress, CompiledContract, createEthAccountWithBalance, createNonfungibleCollection, createRFTCollection, GAS_ARGS, itWeb3, tokenIdFromAddress, uniqueNFT, uniqueRefungible, uniqueRefungibleToken} from '../util/helpers';
import {Contract} from 'web3-eth-contract';
import * as solc from 'solc';
@@ -123,7 +123,7 @@
itWeb3('Set RFT collection', async ({api, web3, privateKeyWrapper}) => {
const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
const fractionalizer = await deployFractionalizer(web3, owner);
- const {collectionIdAddress} = await createRefungibleCollection(api, web3, owner);
+ const {collectionIdAddress} = await createRFTCollection(api, web3, owner);
const refungibleContract = uniqueRefungible(web3, collectionIdAddress, owner);
await refungibleContract.methods.addCollectionAdmin(fractionalizer.options.address).send();
const result = await fractionalizer.methods.setRFTCollection(collectionIdAddress).send();
@@ -256,7 +256,7 @@
itWeb3('call setRFTCollection twice', async ({api, web3, privateKeyWrapper}) => {
const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
- const {collectionIdAddress} = await createRefungibleCollection(api, web3, owner);
+ const {collectionIdAddress} = await createRFTCollection(api, web3, owner);
const refungibleContract = uniqueRefungible(web3, collectionIdAddress, owner);
const fractionalizer = await deployFractionalizer(web3, owner);
@@ -282,7 +282,7 @@
itWeb3('call setRFTCollection while not collection admin', async ({api, web3, privateKeyWrapper}) => {
const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
const fractionalizer = await deployFractionalizer(web3, owner);
- const {collectionIdAddress} = await createRefungibleCollection(api, web3, owner);
+ const {collectionIdAddress} = await createRFTCollection(api, web3, owner);
await expect(fractionalizer.methods.setRFTCollection(collectionIdAddress).call())
.to.be.rejectedWith(/Fractionalizer contract should be an admin of the collection$/g);
@@ -368,7 +368,7 @@
const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
const fractionalizer = await deployFractionalizer(web3, owner);
- const {collectionIdAddress: rftCollectionAddress} = await createRefungibleCollection(api, web3, owner);
+ const {collectionIdAddress: rftCollectionAddress} = await createRFTCollection(api, web3, owner);
const refungibleContract = uniqueRefungible(web3, rftCollectionAddress, owner);
const rftTokenId = await refungibleContract.methods.nextTokenId().call();
await refungibleContract.methods.mint(owner, rftTokenId).send();
@@ -381,7 +381,7 @@
const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
const {fractionalizer} = await initFractionalizer(api, web3, privateKeyWrapper, owner);
- const {collectionIdAddress: rftCollectionAddress} = await createRefungibleCollection(api, web3, owner);
+ const {collectionIdAddress: rftCollectionAddress} = await createRFTCollection(api, web3, owner);
const refungibleContract = uniqueRefungible(web3, rftCollectionAddress, owner);
const rftTokenId = await refungibleContract.methods.nextTokenId().call();
await refungibleContract.methods.mint(owner, rftTokenId).send();
@@ -392,7 +392,7 @@
itWeb3('call rft2nft for RFT token that was not minted by fractionalizer contract', async ({api, web3, privateKeyWrapper}) => {
const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
- const {collectionIdAddress: rftCollectionAddress} = await createRefungibleCollection(api, web3, owner);
+ const {collectionIdAddress: rftCollectionAddress} = await createRFTCollection(api, web3, owner);
const fractionalizer = await deployFractionalizer(web3, owner);
const refungibleContract = uniqueRefungible(web3, rftCollectionAddress, owner);
tests/src/eth/reFungible.test.tsdiffbeforeafterboth--- a/tests/src/eth/reFungible.test.ts
+++ b/tests/src/eth/reFungible.test.ts
@@ -26,7 +26,7 @@
itWeb3('totalSupply', async ({api, web3, privateKeyWrapper}) => {
const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
const helper = evmCollectionHelpers(web3, caller);
- const result = await helper.methods.createRefungibleCollection('Mint collection', '6', '6').send();
+ const result = await helper.methods.createRFTCollection('Mint collection', '6', '6').send();
const {collectionIdAddress} = await getCollectionAddressFromResult(api, result);
const contract = evmCollection(web3, caller, collectionIdAddress, {type: 'ReFungible'});
const nextTokenId = await contract.methods.nextTokenId().call();
@@ -38,7 +38,7 @@
itWeb3('balanceOf', async ({api, web3, privateKeyWrapper}) => {
const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
const helper = evmCollectionHelpers(web3, caller);
- const result = await helper.methods.createRefungibleCollection('Mint collection', '6', '6').send();
+ const result = await helper.methods.createRFTCollection('Mint collection', '6', '6').send();
const {collectionIdAddress} = await getCollectionAddressFromResult(api, result);
const contract = evmCollection(web3, caller, collectionIdAddress, {type: 'ReFungible'});
@@ -63,7 +63,7 @@
itWeb3('ownerOf', async ({api, web3, privateKeyWrapper}) => {
const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
const helper = evmCollectionHelpers(web3, caller);
- const result = await helper.methods.createRefungibleCollection('Mint collection', '6', '6').send();
+ const result = await helper.methods.createRFTCollection('Mint collection', '6', '6').send();
const {collectionIdAddress} = await getCollectionAddressFromResult(api, result);
const contract = evmCollection(web3, caller, collectionIdAddress, {type: 'ReFungible'});
@@ -79,7 +79,7 @@
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 result = await helper.methods.createRFTCollection('Mint collection', '6', '6').send();
const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);
const contract = evmCollection(web3, caller, collectionIdAddress, {type: 'ReFungible'});
@@ -103,7 +103,7 @@
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 result = await helper.methods.createRFTCollection('Mint collection', '6', '6').send();
const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);
const contract = evmCollection(web3, caller, collectionIdAddress, {type: 'ReFungible'});
@@ -130,7 +130,7 @@
itWeb3('Can perform mint()', async ({web3, api, privateKeyWrapper}) => {
const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
const helper = evmCollectionHelpers(web3, owner);
- let result = await helper.methods.createRefungibleCollection('Mint collection', '6', '6').send();
+ let result = await helper.methods.createRFTCollection('Mint collection', '6', '6').send();
const {collectionIdAddress} = await getCollectionAddressFromResult(api, result);
const receiver = createEthAccount(web3);
const contract = evmCollection(web3, owner, collectionIdAddress, {type: 'ReFungible'});
@@ -163,7 +163,7 @@
itWeb3('Can perform mintBulk()', async ({web3, api, privateKeyWrapper}) => {
const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
const helper = evmCollectionHelpers(web3, caller);
- const result = await helper.methods.createRefungibleCollection('Mint collection', '6', '6').send();
+ const result = await helper.methods.createRFTCollection('Mint collection', '6', '6').send();
const {collectionIdAddress} = await getCollectionAddressFromResult(api, result);
const contract = evmCollection(web3, caller, collectionIdAddress, {type: 'ReFungible'});
@@ -221,7 +221,7 @@
itWeb3('Can perform burn()', async ({web3, api, privateKeyWrapper}) => {
const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
const helper = evmCollectionHelpers(web3, caller);
- const result = await helper.methods.createRefungibleCollection('Mint collection', '6', '6').send();
+ const result = await helper.methods.createRFTCollection('Mint collection', '6', '6').send();
const {collectionIdAddress} = await getCollectionAddressFromResult(api, result);
const contract = evmCollection(web3, caller, collectionIdAddress, {type: 'ReFungible'});
@@ -247,7 +247,7 @@
itWeb3('Can perform transferFrom()', async ({web3, api, privateKeyWrapper}) => {
const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
const helper = evmCollectionHelpers(web3, caller);
- const result = await helper.methods.createRefungibleCollection('Mint collection', '6', '6').send();
+ const result = await helper.methods.createRFTCollection('Mint collection', '6', '6').send();
const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);
const contract = evmCollection(web3, caller, collectionIdAddress, {type: 'ReFungible'});
@@ -304,7 +304,7 @@
itWeb3('Can perform transfer()', async ({web3, api, privateKeyWrapper}) => {
const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
const helper = evmCollectionHelpers(web3, caller);
- const result = await helper.methods.createRefungibleCollection('Mint collection', '6', '6').send();
+ const result = await helper.methods.createRFTCollection('Mint collection', '6', '6').send();
const {collectionIdAddress} = await getCollectionAddressFromResult(api, result);
const contract = evmCollection(web3, caller, collectionIdAddress, {type: 'ReFungible'});
@@ -344,7 +344,7 @@
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 result = await helper.methods.createRFTCollection('Mint collection', '6', '6').send();
const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);
const contract = evmCollection(web3, caller, collectionIdAddress, {type: 'ReFungible'});
@@ -376,7 +376,7 @@
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 result = await helper.methods.createRFTCollection('Mint collection', '6', '6').send();
const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);
const contract = evmCollection(web3, caller, collectionIdAddress, {type: 'ReFungible'});
@@ -413,7 +413,7 @@
itWeb3('transferFrom() call fee is less than 0.2UNQ', async ({web3, api, privateKeyWrapper}) => {
const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
const helper = evmCollectionHelpers(web3, caller);
- const result = await helper.methods.createRefungibleCollection('Mint collection', '6', '6').send();
+ const result = await helper.methods.createRFTCollection('Mint collection', '6', '6').send();
const {collectionIdAddress} = await getCollectionAddressFromResult(api, result);
const contract = evmCollection(web3, caller, collectionIdAddress, {type: 'ReFungible'});
@@ -430,7 +430,7 @@
itWeb3('transfer() call fee is less than 0.2UNQ', async ({web3, api, privateKeyWrapper}) => {
const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
const helper = evmCollectionHelpers(web3, caller);
- const result = await helper.methods.createRefungibleCollection('Mint collection', '6', '6').send();
+ const result = await helper.methods.createRFTCollection('Mint collection', '6', '6').send();
const {collectionIdAddress} = await getCollectionAddressFromResult(api, result);
const contract = evmCollection(web3, caller, collectionIdAddress, {type: 'ReFungible'});
tests/src/eth/reFungibleToken.test.tsdiffbeforeafterboth--- a/tests/src/eth/reFungibleToken.test.ts
+++ b/tests/src/eth/reFungibleToken.test.ts
@@ -15,7 +15,7 @@
// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
import {approve, createCollection, createRefungibleToken, transfer, transferFrom, UNIQUE, requirePallets, Pallets} from '../util/helpers';
-import {collectionIdToAddress, createEthAccount, createEthAccountWithBalance, createRefungibleCollection, evmCollection, evmCollectionHelpers, getCollectionAddressFromResult, itWeb3, normalizeEvents, recordEthFee, recordEvents, subToEth, tokenIdToAddress, transferBalanceToEth, uniqueRefungible, uniqueRefungibleToken} from './util/helpers';
+import {collectionIdToAddress, createEthAccount, createEthAccountWithBalance, createRFTCollection, evmCollection, evmCollectionHelpers, getCollectionAddressFromResult, itWeb3, normalizeEvents, recordEthFee, recordEvents, subToEth, tokenIdToAddress, transferBalanceToEth, uniqueRefungible, uniqueRefungibleToken} from './util/helpers';
import chai from 'chai';
import chaiAsPromised from 'chai-as-promised';
@@ -458,7 +458,7 @@
const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
const receiver = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
const helper = evmCollectionHelpers(web3, caller);
- const result = await helper.methods.createRefungibleCollection('Mint collection', '6', '6').send();
+ const result = await helper.methods.createRFTCollection('Mint collection', '6', '6').send();
const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);
const contract = evmCollection(web3, caller, collectionIdAddress, {type: 'ReFungible'});
@@ -658,7 +658,7 @@
itWeb3('Default parent token address and id', async ({api, web3, privateKeyWrapper}) => {
const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
- const {collectionIdAddress, collectionId} = await createRefungibleCollection(api, web3, owner);
+ const {collectionIdAddress, collectionId} = await createRFTCollection(api, web3, owner);
const refungibleContract = uniqueRefungible(web3, collectionIdAddress, owner);
const refungibleTokenId = await refungibleContract.methods.nextTokenId().call();
await refungibleContract.methods.mint(owner, refungibleTokenId).send();
tests/src/eth/util/helpers.tsdiffbeforeafterboth1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617// eslint-disable-next-line @typescript-eslint/triple-slash-reference18/// <reference path="helpers.d.ts" />1920import {ApiPromise} from '@polkadot/api';21import {IKeyringPair} from '@polkadot/types/types';22import {addressToEvm, evmToAddress} from '@polkadot/util-crypto';23import {expect} from 'chai';24import * as solc from 'solc';25import Web3 from 'web3';26import config from '../../config';27import getBalance from '../../substrate/get-balance';28import usingApi, {submitTransactionAsync} from '../../substrate/substrate-api';29import waitNewBlocks from '../../substrate/wait-new-blocks';30import {CollectionMode, CrossAccountId, getDetailedCollectionInfo, getGenericResult, UNIQUE} from '../../util/helpers';31import collectionHelpersAbi from '../collectionHelpersAbi.json';32import fungibleAbi from '../fungibleAbi.json';33import nonFungibleAbi from '../nonFungibleAbi.json';34import refungibleAbi from '../reFungibleAbi.json';35import refungibleTokenAbi from '../reFungibleTokenAbi.json';36import contractHelpersAbi from './contractHelpersAbi.json';3738export const GAS_ARGS = {gas: 2500000};3940export enum SponsoringMode {41 Disabled = 0,42 Allowlisted = 1,43 Generous = 2,44}4546let web3Connected = false;47export async function usingWeb3<T>(cb: (web3: Web3) => Promise<T> | T): Promise<T> {48 if (web3Connected) throw new Error('do not nest usingWeb3 calls');49 web3Connected = true;5051 const provider = new Web3.providers.WebsocketProvider(config.substrateUrl);52 const web3 = new Web3(provider);5354 try {55 return await cb(web3);56 } finally {57 // provider.disconnect(3000, 'normal disconnect');58 provider.connection.close();59 web3Connected = false;60 }61}6263function encodeIntBE(v: number): number[] {64 if (v >= 0xffffffff || v < 0) throw new Error('id overflow');65 return [66 v >> 24,67 (v >> 16) & 0xff,68 (v >> 8) & 0xff,69 v & 0xff,70 ];71}7273export async function getCollectionAddressFromResult(api: ApiPromise, result: any) {74 const collectionIdAddress = normalizeAddress(result.events.CollectionCreated.returnValues.collectionId);75 const collectionId = collectionIdFromAddress(collectionIdAddress); 76 const collection = (await getDetailedCollectionInfo(api, collectionId))!;77 return {collectionIdAddress, collectionId, collection};78}7980export function collectionIdToAddress(collection: number): string {81 const buf = Buffer.from([0x17, 0xc4, 0xe6, 0x45, 0x3c, 0xc4, 0x9a, 0xaa, 0xae, 0xac, 0xa8, 0x94, 0xe6, 0xd9, 0x68, 0x3e,82 ...encodeIntBE(collection),83 ]);84 return Web3.utils.toChecksumAddress('0x' + buf.toString('hex'));85}86export function collectionIdFromAddress(address: string): number {87 if (!address.startsWith('0x'))88 throw 'address not starts with "0x"';89 if (address.length > 42)90 throw 'address length is more than 20 bytes';91 return Number('0x' + address.substring(address.length - 8));92}93 94export function normalizeAddress(address: string): string {95 return '0x' + address.substring(address.length - 40);96}9798export function tokenIdToAddress(collection: number, token: number): string {99 const buf = Buffer.from([0xf8, 0x23, 0x8c, 0xcf, 0xff, 0x8e, 0xd8, 0x87, 0x46, 0x3f, 0xd5, 0xe0,100 ...encodeIntBE(collection),101 ...encodeIntBE(token),102 ]);103 return Web3.utils.toChecksumAddress('0x' + buf.toString('hex'));104}105106export function tokenIdFromAddress(address: string) {107 if (!address.startsWith('0x'))108 throw 'address not starts with "0x"';109 if (address.length > 42)110 throw 'address length is more than 20 bytes';111 return {112 collectionId: Number('0x' + address.substring(address.length - 16, address.length - 8)),113 tokenId: Number('0x' + address.substring(address.length - 8)),114 };115}116117export function tokenIdToCross(collection: number, token: number): CrossAccountId {118 return {119 Ethereum: tokenIdToAddress(collection, token),120 };121}122123export function createEthAccount(web3: Web3) {124 const account = web3.eth.accounts.create();125 web3.eth.accounts.wallet.add(account.privateKey);126 return account.address;127}128129export async function createEthAccountWithBalance(api: ApiPromise, web3: Web3, privateKeyWrapper: (account: string) => IKeyringPair) {130 const alice = privateKeyWrapper('//Alice');131 const account = createEthAccount(web3);132 await transferBalanceToEth(api, alice, account);133134 return account;135}136137export async function transferBalanceToEth(api: ApiPromise, source: IKeyringPair, target: string, amount = 1000n * UNIQUE) {138 const tx = api.tx.balances.transfer(evmToAddress(target), amount);139 const events = await submitTransactionAsync(source, tx);140 const result = getGenericResult(events);141 expect(result.success).to.be.true;142}143144export async function createRefungibleCollection(api: ApiPromise, web3: Web3, owner: string) {145 const collectionHelper = evmCollectionHelpers(web3, owner);146 const result = await collectionHelper.methods147 .createRefungibleCollection('A', 'B', 'C')148 .send();149 return await getCollectionAddressFromResult(api, result);150}151152153export async function createNonfungibleCollection(api: ApiPromise, web3: Web3, owner: string) {154 const collectionHelper = evmCollectionHelpers(web3, owner);155 const result = await collectionHelper.methods156 .createNonfungibleCollection('A', 'B', 'C')157 .send();158 return await getCollectionAddressFromResult(api, result);159}160161export function uniqueNFT(web3: Web3, address: string, owner: string) {162 return new web3.eth.Contract(nonFungibleAbi as any, address, {163 from: owner,164 ...GAS_ARGS,165 });166}167168export function uniqueRefungible(web3: Web3, collectionAddress: string, owner: string) {169 return new web3.eth.Contract(refungibleAbi as any, collectionAddress, {170 from: owner,171 ...GAS_ARGS,172 });173}174175export function uniqueRefungibleToken(web3: Web3, tokenAddress: string, owner: string | undefined = undefined) {176 return new web3.eth.Contract(refungibleTokenAbi as any, tokenAddress, {177 from: owner,178 ...GAS_ARGS,179 });180}181182export async function itWeb3(name: string, cb: (apis: { web3: Web3, api: ApiPromise, privateKeyWrapper: (account: string) => IKeyringPair }) => any, opts: { only?: boolean, skip?: boolean } = {}) {183 let i: any = it;184 if (opts.only) i = i.only;185 else if (opts.skip) i = i.skip;186 i(name, async () => {187 await usingApi(async (api, privateKeyWrapper) => {188 await usingWeb3(async web3 => {189 await cb({api, web3, privateKeyWrapper});190 });191 });192 });193}194itWeb3.only = (name: string, cb: (apis: { web3: Web3, api: ApiPromise, privateKeyWrapper: (account: string) => IKeyringPair }) => any) => itWeb3(name, cb, {only: true});195itWeb3.skip = (name: string, cb: (apis: { web3: Web3, api: ApiPromise, privateKeyWrapper: (account: string) => IKeyringPair }) => any) => itWeb3(name, cb, {skip: true});196197export async function generateSubstrateEthPair(web3: Web3) {198 const account = web3.eth.accounts.create();199 evmToAddress(account.address);200}201202type NormalizedEvent = {203 address: string,204 event: string,205 args: { [key: string]: string }206};207208export function normalizeEvents(events: any): NormalizedEvent[] {209 const output = [];210 for (const key of Object.keys(events)) {211 if (key.match(/^[0-9]+$/)) {212 output.push(events[key]);213 } else if (Array.isArray(events[key])) {214 output.push(...events[key]);215 } else {216 output.push(events[key]);217 }218 }219 output.sort((a, b) => a.logIndex - b.logIndex);220 return output.map(({address, event, returnValues}) => {221 const args: { [key: string]: string } = {};222 for (const key of Object.keys(returnValues)) {223 if (!key.match(/^[0-9]+$/)) {224 args[key] = returnValues[key];225 }226 }227 return {228 address,229 event,230 args,231 };232 });233}234235export async function recordEvents(contract: any, action: () => Promise<void>): Promise<NormalizedEvent[]> {236 const out: any = [];237 contract.events.allEvents((_: any, event: any) => {238 out.push(event);239 });240 await action();241 return normalizeEvents(out);242}243244export function subToEthLowercase(eth: string): string {245 const bytes = addressToEvm(eth);246 return '0x' + Buffer.from(bytes).toString('hex');247}248249export function subToEth(eth: string): string {250 return Web3.utils.toChecksumAddress(subToEthLowercase(eth));251}252253export interface CompiledContract {254 abi: any,255 object: string,256}257258export function compileContract(name: string, src: string) : CompiledContract {259 const out = JSON.parse(solc.compile(JSON.stringify({260 language: 'Solidity',261 sources: {262 [`${name}.sol`]: {263 content: `264 // SPDX-License-Identifier: UNLICENSED265 pragma solidity ^0.8.6;266267 ${src}268 `,269 },270 },271 settings: {272 outputSelection: {273 '*': {274 '*': ['*'],275 },276 },277 },278 }))).contracts[`${name}.sol`][name];279280 return {281 abi: out.abi,282 object: '0x' + out.evm.bytecode.object,283 };284}285286export async function deployFlipper(web3: Web3, deployer: string) {287 const compiled = compileContract('Flipper', `288 contract Flipper {289 bool value = false;290 function flip() public {291 value = !value;292 }293 function getValue() public view returns (bool) {294 return value;295 }296 }297 `);298 const flipperContract = new web3.eth.Contract(compiled.abi, undefined, {299 data: compiled.object,300 from: deployer,301 ...GAS_ARGS,302 });303 const flipper = await flipperContract.deploy({data: compiled.object}).send({from: deployer});304305 return flipper;306}307308export async function deployCollector(web3: Web3, deployer: string) {309 const compiled = compileContract('Collector', `310 contract Collector {311 uint256 collected;312 fallback() external payable {313 giveMoney();314 }315 function giveMoney() public payable {316 collected += msg.value;317 }318 function getCollected() public view returns (uint256) {319 return collected;320 }321 function getUnaccounted() public view returns (uint256) {322 return address(this).balance - collected;323 }324325 function withdraw(address payable target) public {326 target.transfer(collected);327 collected = 0;328 }329 }330 `);331 const collectorContract = new web3.eth.Contract(compiled.abi, undefined, {332 data: compiled.object,333 from: deployer,334 ...GAS_ARGS,335 });336 const collector = await collectorContract.deploy({data: compiled.object}).send({from: deployer});337338 return collector;339}340341/** 342 * pallet evm_contract_helpers343 * @param web3 344 * @param caller - eth address345 * @returns 346 */347export function contractHelpers(web3: Web3, caller: string) {348 return new web3.eth.Contract(contractHelpersAbi as any, '0x842899ECF380553E8a4de75bF534cdf6fBF64049', {from: caller, ...GAS_ARGS});349}350351/** 352 * evm collection helper353 * @param web3 354 * @param caller - eth address355 * @returns 356 */357export function evmCollectionHelpers(web3: Web3, caller: string) {358 return new web3.eth.Contract(collectionHelpersAbi as any, '0x6c4e9fe1ae37a41e93cee429e8e1881abdcbb54f', {from: caller, ...GAS_ARGS});359}360361/** 362 * evm collection363 * @param web3 364 * @param caller - eth address365 * @returns 366 */367export function evmCollection(web3: Web3, caller: string, collection: string, mode: CollectionMode = {type: 'NFT'}) {368 let abi;369 switch (mode.type) {370 case 'Fungible':371 abi = fungibleAbi;372 break;373 374 case 'NFT':375 abi = nonFungibleAbi;376 break;377 378 case 'ReFungible':379 abi = refungibleAbi;380 break;381382 default:383 throw 'Bad collection mode';384 }385 const contract = new web3.eth.Contract(abi as any, collection, {from: caller, ...GAS_ARGS});386 return contract;387}388389/**390 * Execute ethereum method call using substrate account391 * @param to target contract392 * @param mkTx - closure, receiving `contract.methods`, and returning method call,393 * to be used as following (assuming `to` = erc20 contract):394 * `m => m.transfer(to, amount)`395 *396 * # Example397 * ```ts398 * executeEthTxOnSub(api, alice, erc20Contract, m => m.transfer(target, amount));399 * ```400 */401export async function executeEthTxOnSub(web3: Web3, api: ApiPromise, from: IKeyringPair, to: any, mkTx: (methods: any) => any, {value = 0}: {value?: bigint | number} = { }) {402 const tx = api.tx.evm.call(403 subToEth(from.address),404 to.options.address,405 mkTx(to.methods).encodeABI(),406 value,407 GAS_ARGS.gas,408 await web3.eth.getGasPrice(),409 null,410 null,411 [],412 );413 const events = await submitTransactionAsync(from, tx);414 expect(events.some(({event: {section, method}}) => section == 'evm' && method == 'Executed')).to.be.true;415}416417export async function ethBalanceViaSub(api: ApiPromise, address: string): Promise<bigint> {418 return (await getBalance(api, [evmToAddress(address)]))[0];419}420421/**422 * Measure how much gas given closure consumes423 *424 * @param user which user balance will be checked425 */426export async function recordEthFee(api: ApiPromise, user: string, call: () => Promise<any>): Promise<bigint> {427 const before = await ethBalanceViaSub(api, user);428429 await call();430431 // In dev mode, the transaction might not finish processing in time432 await waitNewBlocks(api, 1);433 const after = await ethBalanceViaSub(api, user);434435 // Can't use .to.be.less, because chai doesn't supports bigint436 expect(after < before).to.be.true;437438 return before - after;439}440441type ElementOf<A> = A extends readonly (infer T)[] ? T : never;442// I want a fancier api, not a memory efficiency443export function* cartesian<T extends Array<Array<any>>, R extends Array<any>>(internalRest: [...R], ...args: [...T]): Generator<[...R, ...{[K in keyof T]: ElementOf<T[K]>}]> {444 if(args.length === 0) {445 yield internalRest as any;446 return;447 }448 for(const value of args[0]) {449 yield* cartesian([...internalRest, value], ...args.slice(1)) as any;450 }451}1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617// eslint-disable-next-line @typescript-eslint/triple-slash-reference18/// <reference path="helpers.d.ts" />1920import {ApiPromise} from '@polkadot/api';21import {IKeyringPair} from '@polkadot/types/types';22import {addressToEvm, evmToAddress} from '@polkadot/util-crypto';23import {expect} from 'chai';24import * as solc from 'solc';25import Web3 from 'web3';26import config from '../../config';27import getBalance from '../../substrate/get-balance';28import usingApi, {submitTransactionAsync} from '../../substrate/substrate-api';29import waitNewBlocks from '../../substrate/wait-new-blocks';30import {CollectionMode, CrossAccountId, getDetailedCollectionInfo, getGenericResult, UNIQUE} from '../../util/helpers';31import collectionHelpersAbi from '../collectionHelpersAbi.json';32import fungibleAbi from '../fungibleAbi.json';33import nonFungibleAbi from '../nonFungibleAbi.json';34import refungibleAbi from '../reFungibleAbi.json';35import refungibleTokenAbi from '../reFungibleTokenAbi.json';36import contractHelpersAbi from './contractHelpersAbi.json';3738export const GAS_ARGS = {gas: 2500000};3940export enum SponsoringMode {41 Disabled = 0,42 Allowlisted = 1,43 Generous = 2,44}4546let web3Connected = false;47export async function usingWeb3<T>(cb: (web3: Web3) => Promise<T> | T): Promise<T> {48 if (web3Connected) throw new Error('do not nest usingWeb3 calls');49 web3Connected = true;5051 const provider = new Web3.providers.WebsocketProvider(config.substrateUrl);52 const web3 = new Web3(provider);5354 try {55 return await cb(web3);56 } finally {57 // provider.disconnect(3000, 'normal disconnect');58 provider.connection.close();59 web3Connected = false;60 }61}6263function encodeIntBE(v: number): number[] {64 if (v >= 0xffffffff || v < 0) throw new Error('id overflow');65 return [66 v >> 24,67 (v >> 16) & 0xff,68 (v >> 8) & 0xff,69 v & 0xff,70 ];71}7273export async function getCollectionAddressFromResult(api: ApiPromise, result: any) {74 const collectionIdAddress = normalizeAddress(result.events.CollectionCreated.returnValues.collectionId);75 const collectionId = collectionIdFromAddress(collectionIdAddress); 76 const collection = (await getDetailedCollectionInfo(api, collectionId))!;77 return {collectionIdAddress, collectionId, collection};78}7980export function collectionIdToAddress(collection: number): string {81 const buf = Buffer.from([0x17, 0xc4, 0xe6, 0x45, 0x3c, 0xc4, 0x9a, 0xaa, 0xae, 0xac, 0xa8, 0x94, 0xe6, 0xd9, 0x68, 0x3e,82 ...encodeIntBE(collection),83 ]);84 return Web3.utils.toChecksumAddress('0x' + buf.toString('hex'));85}86export function collectionIdFromAddress(address: string): number {87 if (!address.startsWith('0x'))88 throw 'address not starts with "0x"';89 if (address.length > 42)90 throw 'address length is more than 20 bytes';91 return Number('0x' + address.substring(address.length - 8));92}93 94export function normalizeAddress(address: string): string {95 return '0x' + address.substring(address.length - 40);96}9798export function tokenIdToAddress(collection: number, token: number): string {99 const buf = Buffer.from([0xf8, 0x23, 0x8c, 0xcf, 0xff, 0x8e, 0xd8, 0x87, 0x46, 0x3f, 0xd5, 0xe0,100 ...encodeIntBE(collection),101 ...encodeIntBE(token),102 ]);103 return Web3.utils.toChecksumAddress('0x' + buf.toString('hex'));104}105106export function tokenIdFromAddress(address: string) {107 if (!address.startsWith('0x'))108 throw 'address not starts with "0x"';109 if (address.length > 42)110 throw 'address length is more than 20 bytes';111 return {112 collectionId: Number('0x' + address.substring(address.length - 16, address.length - 8)),113 tokenId: Number('0x' + address.substring(address.length - 8)),114 };115}116117export function tokenIdToCross(collection: number, token: number): CrossAccountId {118 return {119 Ethereum: tokenIdToAddress(collection, token),120 };121}122123export function createEthAccount(web3: Web3) {124 const account = web3.eth.accounts.create();125 web3.eth.accounts.wallet.add(account.privateKey);126 return account.address;127}128129export async function createEthAccountWithBalance(api: ApiPromise, web3: Web3, privateKeyWrapper: (account: string) => IKeyringPair) {130 const alice = privateKeyWrapper('//Alice');131 const account = createEthAccount(web3);132 await transferBalanceToEth(api, alice, account);133134 return account;135}136137export async function transferBalanceToEth(api: ApiPromise, source: IKeyringPair, target: string, amount = 1000n * UNIQUE) {138 const tx = api.tx.balances.transfer(evmToAddress(target), amount);139 const events = await submitTransactionAsync(source, tx);140 const result = getGenericResult(events);141 expect(result.success).to.be.true;142}143144export async function createRFTCollection(api: ApiPromise, web3: Web3, owner: string) {145 const collectionHelper = evmCollectionHelpers(web3, owner);146 const result = await collectionHelper.methods147 .createRFTCollection('A', 'B', 'C')148 .send();149 return await getCollectionAddressFromResult(api, result);150}151152153export async function createNonfungibleCollection(api: ApiPromise, web3: Web3, owner: string) {154 const collectionHelper = evmCollectionHelpers(web3, owner);155 const result = await collectionHelper.methods156 .createNonfungibleCollection('A', 'B', 'C')157 .send();158 return await getCollectionAddressFromResult(api, result);159}160161export function uniqueNFT(web3: Web3, address: string, owner: string) {162 return new web3.eth.Contract(nonFungibleAbi as any, address, {163 from: owner,164 ...GAS_ARGS,165 });166}167168export function uniqueRefungible(web3: Web3, collectionAddress: string, owner: string) {169 return new web3.eth.Contract(refungibleAbi as any, collectionAddress, {170 from: owner,171 ...GAS_ARGS,172 });173}174175export function uniqueRefungibleToken(web3: Web3, tokenAddress: string, owner: string | undefined = undefined) {176 return new web3.eth.Contract(refungibleTokenAbi as any, tokenAddress, {177 from: owner,178 ...GAS_ARGS,179 });180}181182export async function itWeb3(name: string, cb: (apis: { web3: Web3, api: ApiPromise, privateKeyWrapper: (account: string) => IKeyringPair }) => any, opts: { only?: boolean, skip?: boolean } = {}) {183 let i: any = it;184 if (opts.only) i = i.only;185 else if (opts.skip) i = i.skip;186 i(name, async () => {187 await usingApi(async (api, privateKeyWrapper) => {188 await usingWeb3(async web3 => {189 await cb({api, web3, privateKeyWrapper});190 });191 });192 });193}194itWeb3.only = (name: string, cb: (apis: { web3: Web3, api: ApiPromise, privateKeyWrapper: (account: string) => IKeyringPair }) => any) => itWeb3(name, cb, {only: true});195itWeb3.skip = (name: string, cb: (apis: { web3: Web3, api: ApiPromise, privateKeyWrapper: (account: string) => IKeyringPair }) => any) => itWeb3(name, cb, {skip: true});196197export async function generateSubstrateEthPair(web3: Web3) {198 const account = web3.eth.accounts.create();199 evmToAddress(account.address);200}201202type NormalizedEvent = {203 address: string,204 event: string,205 args: { [key: string]: string }206};207208export function normalizeEvents(events: any): NormalizedEvent[] {209 const output = [];210 for (const key of Object.keys(events)) {211 if (key.match(/^[0-9]+$/)) {212 output.push(events[key]);213 } else if (Array.isArray(events[key])) {214 output.push(...events[key]);215 } else {216 output.push(events[key]);217 }218 }219 output.sort((a, b) => a.logIndex - b.logIndex);220 return output.map(({address, event, returnValues}) => {221 const args: { [key: string]: string } = {};222 for (const key of Object.keys(returnValues)) {223 if (!key.match(/^[0-9]+$/)) {224 args[key] = returnValues[key];225 }226 }227 return {228 address,229 event,230 args,231 };232 });233}234235export async function recordEvents(contract: any, action: () => Promise<void>): Promise<NormalizedEvent[]> {236 const out: any = [];237 contract.events.allEvents((_: any, event: any) => {238 out.push(event);239 });240 await action();241 return normalizeEvents(out);242}243244export function subToEthLowercase(eth: string): string {245 const bytes = addressToEvm(eth);246 return '0x' + Buffer.from(bytes).toString('hex');247}248249export function subToEth(eth: string): string {250 return Web3.utils.toChecksumAddress(subToEthLowercase(eth));251}252253export interface CompiledContract {254 abi: any,255 object: string,256}257258export function compileContract(name: string, src: string) : CompiledContract {259 const out = JSON.parse(solc.compile(JSON.stringify({260 language: 'Solidity',261 sources: {262 [`${name}.sol`]: {263 content: `264 // SPDX-License-Identifier: UNLICENSED265 pragma solidity ^0.8.6;266267 ${src}268 `,269 },270 },271 settings: {272 outputSelection: {273 '*': {274 '*': ['*'],275 },276 },277 },278 }))).contracts[`${name}.sol`][name];279280 return {281 abi: out.abi,282 object: '0x' + out.evm.bytecode.object,283 };284}285286export async function deployFlipper(web3: Web3, deployer: string) {287 const compiled = compileContract('Flipper', `288 contract Flipper {289 bool value = false;290 function flip() public {291 value = !value;292 }293 function getValue() public view returns (bool) {294 return value;295 }296 }297 `);298 const flipperContract = new web3.eth.Contract(compiled.abi, undefined, {299 data: compiled.object,300 from: deployer,301 ...GAS_ARGS,302 });303 const flipper = await flipperContract.deploy({data: compiled.object}).send({from: deployer});304305 return flipper;306}307308export async function deployCollector(web3: Web3, deployer: string) {309 const compiled = compileContract('Collector', `310 contract Collector {311 uint256 collected;312 fallback() external payable {313 giveMoney();314 }315 function giveMoney() public payable {316 collected += msg.value;317 }318 function getCollected() public view returns (uint256) {319 return collected;320 }321 function getUnaccounted() public view returns (uint256) {322 return address(this).balance - collected;323 }324325 function withdraw(address payable target) public {326 target.transfer(collected);327 collected = 0;328 }329 }330 `);331 const collectorContract = new web3.eth.Contract(compiled.abi, undefined, {332 data: compiled.object,333 from: deployer,334 ...GAS_ARGS,335 });336 const collector = await collectorContract.deploy({data: compiled.object}).send({from: deployer});337338 return collector;339}340341/** 342 * pallet evm_contract_helpers343 * @param web3 344 * @param caller - eth address345 * @returns 346 */347export function contractHelpers(web3: Web3, caller: string) {348 return new web3.eth.Contract(contractHelpersAbi as any, '0x842899ECF380553E8a4de75bF534cdf6fBF64049', {from: caller, ...GAS_ARGS});349}350351/** 352 * evm collection helper353 * @param web3 354 * @param caller - eth address355 * @returns 356 */357export function evmCollectionHelpers(web3: Web3, caller: string) {358 return new web3.eth.Contract(collectionHelpersAbi as any, '0x6c4e9fe1ae37a41e93cee429e8e1881abdcbb54f', {from: caller, ...GAS_ARGS});359}360361/** 362 * evm collection363 * @param web3 364 * @param caller - eth address365 * @returns 366 */367export function evmCollection(web3: Web3, caller: string, collection: string, mode: CollectionMode = {type: 'NFT'}) {368 let abi;369 switch (mode.type) {370 case 'Fungible':371 abi = fungibleAbi;372 break;373 374 case 'NFT':375 abi = nonFungibleAbi;376 break;377 378 case 'ReFungible':379 abi = refungibleAbi;380 break;381382 default:383 throw 'Bad collection mode';384 }385 const contract = new web3.eth.Contract(abi as any, collection, {from: caller, ...GAS_ARGS});386 return contract;387}388389/**390 * Execute ethereum method call using substrate account391 * @param to target contract392 * @param mkTx - closure, receiving `contract.methods`, and returning method call,393 * to be used as following (assuming `to` = erc20 contract):394 * `m => m.transfer(to, amount)`395 *396 * # Example397 * ```ts398 * executeEthTxOnSub(api, alice, erc20Contract, m => m.transfer(target, amount));399 * ```400 */401export async function executeEthTxOnSub(web3: Web3, api: ApiPromise, from: IKeyringPair, to: any, mkTx: (methods: any) => any, {value = 0}: {value?: bigint | number} = { }) {402 const tx = api.tx.evm.call(403 subToEth(from.address),404 to.options.address,405 mkTx(to.methods).encodeABI(),406 value,407 GAS_ARGS.gas,408 await web3.eth.getGasPrice(),409 null,410 null,411 [],412 );413 const events = await submitTransactionAsync(from, tx);414 expect(events.some(({event: {section, method}}) => section == 'evm' && method == 'Executed')).to.be.true;415}416417export async function ethBalanceViaSub(api: ApiPromise, address: string): Promise<bigint> {418 return (await getBalance(api, [evmToAddress(address)]))[0];419}420421/**422 * Measure how much gas given closure consumes423 *424 * @param user which user balance will be checked425 */426export async function recordEthFee(api: ApiPromise, user: string, call: () => Promise<any>): Promise<bigint> {427 const before = await ethBalanceViaSub(api, user);428429 await call();430431 // In dev mode, the transaction might not finish processing in time432 await waitNewBlocks(api, 1);433 const after = await ethBalanceViaSub(api, user);434435 // Can't use .to.be.less, because chai doesn't supports bigint436 expect(after < before).to.be.true;437438 return before - after;439}440441type ElementOf<A> = A extends readonly (infer T)[] ? T : never;442// I want a fancier api, not a memory efficiency443export function* cartesian<T extends Array<Array<any>>, R extends Array<any>>(internalRest: [...R], ...args: [...T]): Generator<[...R, ...{[K in keyof T]: ElementOf<T[K]>}]> {444 if(args.length === 0) {445 yield internalRest as any;446 return;447 }448 for(const value of args[0]) {449 yield* cartesian([...internalRest, value], ...args.slice(1)) as any;450 }451}