From 42f7f34eac79a3e8c6f1c354bf7f62db2e1e2dcf Mon Sep 17 00:00:00 2001 From: Grigoriy Simonov Date: Thu, 11 Aug 2022 09:44:00 +0000 Subject: [PATCH] feat(refungible-pallet): fractionalizer contract --- --- a/tests/package.json +++ b/tests/package.json @@ -8,6 +8,7 @@ "@polkadot/typegen": "8.7.2-15", "@types/chai": "^4.3.1", "@types/chai-as-promised": "^7.1.5", + "@types/chai-like": "^1.1.1", "@types/mocha": "^9.1.1", "@types/node": "^17.0.35", "@typescript-eslint/eslint-plugin": "^5.26.0", @@ -96,6 +97,7 @@ "@polkadot/util-crypto": "9.4.1", "bignumber.js": "^9.0.2", "chai-as-promised": "^7.1.1", + "chai-like": "^1.1.1", "find-process": "^1.4.7", "solc": "0.8.14-fixed", "web3": "^1.7.3" --- /dev/null +++ b/tests/src/eth/fractionalizer/Fractionalizer.bin @@ -0,0 +1 @@ +60c0604052600a60805269526546756e6769626c6560b01b60a0527fcdb72fd4e1d0d6d4eebd7ab142113ec2b4b06ddb24324db5c287ef01ab484d6b60055534801561004a57600080fd5b50600480546001600160a01b031916331790556113cd8061006c6000396000f3fe608060405234801561001057600080fd5b50600436106100575760003560e01c8063115091401461005c578063b12527f814610071578063d470e60f14610084578063dbc38ad214610097578063f0d4cf5f146100aa575b600080fd5b61006f61006a366004610f34565b6100bd565b005b61006f61007f366004610f66565b6102b8565b61006f610092366004610f9f565b610345565b61006f6100a5366004610fcb565b610722565b61006f6100b8366004611062565b610dc4565b6004546001600160a01b031633146100f05760405162461bcd60e51b81526004016100e7906110fc565b60405180910390fd5b6000546001600160a01b0316156101495760405162461bcd60e51b815260206004820152601d60248201527f52465420636f6c6c656374696f6e20697320616c72656164792073657400000060448201526064016100e7565b60008190506000816001600160a01b031663d34b55b86040518163ffffffff1660e01b81526004016000604051808303816000875af1158015610190573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526101b8919081019061113a565b90506005548180519060200120146101e25760405162461bcd60e51b81526004016100e79061120a565b816001600160a01b03166304a460536040518163ffffffff1660e01b81526004016020604051808303816000875af1158015610222573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102469190611267565b6102625760405162461bcd60e51b81526004016100e79061120a565b600080546001600160a01b0319166001600160a01b0385169081179091556040519081527f7186a599bf2297b1f4c8957d30b0965291eee0021a5f9a0aeb54dcbd1ffdceef9060200160405180910390a1505050565b6004546001600160a01b031633146102e25760405162461bcd60e51b81526004016100e7906110fc565b6001600160a01b038216600081815260016020908152604091829020805460ff19168515159081179091558251938452908301527f6dad0aed33f4b7f07095619b668698e17943fd9f4c83e7cfcc7f6dd880a11588910160405180910390a15050565b6000546001600160a01b03166103995760405162461bcd60e51b81526020600482015260196024820152781491950818dbdb1b1958dd1a5bdb881a5cc81b9bdd081cd95d603a1b60448201526064016100e7565b6000546001600160a01b038381169116146103ed5760405162461bcd60e51b81526020600482015260146024820152732bb937b7339029232a1031b7b63632b1ba34b7b760611b60448201526064016100e7565b600080546040516355bb7d6360e11b8152600481018490526001600160a01b039091169190829063ab76fac690602401602060405180830381865afa15801561043a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061045e9190611284565b6001600160a01b0380821660009081526003602090815260409182902082518084019093528054909316808352600190930154908201529192506104e45760405162461bcd60e51b815260206004820181905260248201527f4e6f20636f72726573706f6e64696e67204e465420746f6b656e20666f756e6460448201526064016100e7565b6000829050806001600160a01b03166318160ddd6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610527573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061054b91906112a1565b6040516370a0823160e01b81523360048201526001600160a01b038316906370a0823190602401602060405180830381865afa15801561058f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105b391906112a1565b1461060f5760405162461bcd60e51b815260206004820152602660248201527f4e6f7420616c6c2070696563657320617265206f776e6564206279207468652060448201526531b0b63632b960d11b60648201526084016100e7565b6040516323b872dd60e01b81526001600160a01b038516906323b872dd9061063f90339030908a906004016112ba565b600060405180830381600087803b15801561065957600080fd5b505af115801561066d573d6000803e3d6000fd5b5050835160208501516040516323b872dd60e01b81526001600160a01b0390921693506323b872dd92506106a791309133916004016112ba565b600060405180830381600087803b1580156106c157600080fd5b505af11580156106d5573d6000803e3d6000fd5b5050835160208501516040517fd2628c68a4330ea5ba2e16305b03a4e974e760e86f302d43e6bc00c28918cb2294506107129350879291906112ba565b60405180910390a1505050505050565b6000546001600160a01b03166107765760405162461bcd60e51b81526020600482015260196024820152781491950818dbdb1b1958dd1a5bdb881a5cc81b9bdd081cd95d603a1b60448201526064016100e7565b6000546040516331a9108f60e11b8152600481018490526001600160a01b0391821691339190861690636352211e90602401602060405180830381865afa1580156107c5573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107e99190611284565b6001600160a01b03161461080f5760405162461bcd60e51b81526004016100e7906112de565b6001600160a01b03841660009081526001602081905260409091205460ff161515146108a35760405162461bcd60e51b815260206004820152603c60248201527f4672616374696f6e616c697a6174696f6e206f66207468697320636f6c6c656360448201527f74696f6e206973206e6f7420616c6c6f7765642062792061646d696e0000000060648201526084016100e7565b6040516331a9108f60e11b81526004810184905233906001600160a01b03861690636352211e90602401602060405180830381865afa1580156108ea573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061090e9190611284565b6001600160a01b0316146109345760405162461bcd60e51b81526004016100e7906112de565b6040516323b872dd60e01b81526001600160a01b038516906323b872dd90610964903390309088906004016112ba565b600060405180830381600087803b15801561097e57600080fd5b505af1158015610992573d6000803e3d6000fd5b505050506001600160a01b0384166000908152600260209081526040808320868452909152812054819081908103610bde57836001600160a01b03166375794a3c6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610a02573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a2691906112a1565b6040516340c10f1960e01b8152306004820152602481018290529093506001600160a01b038516906340c10f19906044016020604051808303816000875af1158015610a76573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a9a9190611267565b506040516355bb7d6360e11b8152600481018490526001600160a01b0385169063ab76fac690602401602060405180830381865afa158015610ae0573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b049190611284565b6001600160a01b0388811660008181526002602090815260408083208c84528252808320899055805180820182528481528083018d8152878716808652600390945293829020905181546001600160a01b0319169616959095178555915160019094019390935551630217888360e11b81526004810191909152602481018990529193508392509063042f1106906044016020604051808303816000875af1158015610bb4573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610bd89190611267565b50610c6f565b6001600160a01b0387811660009081526002602090815260408083208a8452909152908190205490516355bb7d6360e11b8152600481018290529094509085169063ab76fac690602401602060405180830381865afa158015610c45573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610c699190611284565b91508190505b60405163d2418ca760e01b81526001600160801b03861660048201526001600160a01b0382169063d2418ca7906024016020604051808303816000875af1158015610cbe573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610ce29190611267565b5060405163a9059cbb60e01b81523360048201526001600160801b03861660248201526001600160a01b0382169063a9059cbb906044016020604051808303816000875af1158015610d38573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610d5c9190611267565b50604080516001600160a01b03808a168252602082018990528416918101919091526001600160801b03861660608201527f29f372538523984f33874da1b50e596ce0f180a6eb04d7ff22bb2ce80e1576b6906080015b60405180910390a150505050505050565b6004546001600160a01b03163314610dee5760405162461bcd60e51b81526004016100e7906110fc565b6000546001600160a01b031615610e475760405162461bcd60e51b815260206004820152601d60248201527f52465420636f6c6c656374696f6e20697320616c72656164792073657400000060448201526064016100e7565b6040516344a68ad560e01b8152736c4e9fe1ae37a41e93cee429e8e1881abdcbb54f9081906344a68ad590610e8a908a908a908a908a908a908a9060040161134e565b6020604051808303816000875af1158015610ea9573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610ecd9190611284565b600080546001600160a01b0319166001600160a01b039290921691821790556040519081527f7186a599bf2297b1f4c8957d30b0965291eee0021a5f9a0aeb54dcbd1ffdceef90602001610db3565b6001600160a01b0381168114610f3157600080fd5b50565b600060208284031215610f4657600080fd5b8135610f5181610f1c565b9392505050565b8015158114610f3157600080fd5b60008060408385031215610f7957600080fd5b8235610f8481610f1c565b91506020830135610f9481610f58565b809150509250929050565b60008060408385031215610fb257600080fd5b8235610fbd81610f1c565b946020939093013593505050565b600080600060608486031215610fe057600080fd5b8335610feb81610f1c565b92506020840135915060408401356001600160801b038116811461100e57600080fd5b809150509250925092565b60008083601f84011261102b57600080fd5b50813567ffffffffffffffff81111561104357600080fd5b60208301915083602082850101111561105b57600080fd5b9250929050565b6000806000806000806060878903121561107b57600080fd5b863567ffffffffffffffff8082111561109357600080fd5b61109f8a838b01611019565b909850965060208901359150808211156110b857600080fd5b6110c48a838b01611019565b909650945060408901359150808211156110dd57600080fd5b506110ea89828a01611019565b979a9699509497509295939492505050565b6020808252600e908201526d27b7363c9037bbb732b91031b0b760911b604082015260600190565b634e487b7160e01b600052604160045260246000fd5b6000602080838503121561114d57600080fd5b825167ffffffffffffffff8082111561116557600080fd5b818501915085601f83011261117957600080fd5b81518181111561118b5761118b611124565b604051601f8201601f19908116603f011681019083821181831017156111b3576111b3611124565b8160405282815288868487010111156111cb57600080fd5b600093505b828410156111ed57848401860151818501870152928501926111d0565b828411156111fe5760008684830101525b98975050505050505050565b6020808252603c908201527f4672616374696f6e616c697a657220636f6e74726163742073686f756c64206260408201527f6520616e2061646d696e206f662074686520636f6c6c656374696f6e00000000606082015260800190565b60006020828403121561127957600080fd5b8151610f5181610f58565b60006020828403121561129657600080fd5b8151610f5181610f1c565b6000602082840312156112b357600080fd5b5051919050565b6001600160a01b039384168152919092166020820152604081019190915260600190565b60208082526027908201527f4f6e6c7920746f6b656e206f776e657220636f756c64206672616374696f6e616040820152661b1a5e99481a5d60ca1b606082015260800190565b81835281816020850137506000828201602090810191909152601f909101601f19169091010190565b60608152600061136260608301888a611325565b8281036020840152611375818789611325565b9050828103604084015261138a818587611325565b999850505050505050505056fea264697066735822122090a1bec1bb2c2216359e5312618175ac644676d83b7b03f8f2c378700aedd5fe64736f6c634300080f0033 \ No newline at end of file --- /dev/null +++ b/tests/src/eth/fractionalizer/Fractionalizer.sol @@ -0,0 +1,144 @@ +// SPDX-License-Identifier: Apache License +pragma solidity >=0.8.0; +import {CollectionHelpers} from "../api/CollectionHelpers.sol"; +import {ContractHelpers} from "../api/ContractHelpers.sol"; +import {UniqueRefungibleToken} from "../api/UniqueRefungibleToken.sol"; +import {UniqueRefungible} from "../api/UniqueRefungible.sol"; +import {UniqueNFT} from "../api/UniqueNFT.sol"; + +contract Fractionalizer { + struct Token { + address _collection; + uint256 _tokenId; + } + address rftCollection; + mapping(address => bool) nftCollectionAllowList; + mapping(address => mapping(uint256 => uint256)) nft2rftMapping; + mapping(address => Token) rft2nftMapping; + address owner; + bytes32 refungibleCollectionType = keccak256(bytes("ReFungible")); + + constructor() { + owner = msg.sender; + } + + modifier onlyOwner() { + require(msg.sender == owner, "Only owner can"); + _; + } + + event RFTCollectionSet(address _collection); + event AllowListSet(address _collection, bool _status); + event Fractionalized(address _collection, uint256 _tokenId, address _rftToken, uint128 _amount); + event DeFractionalized(address _rftToken, address _nftCollection, uint256 _nftTokenId); + + function setRFTCollection(address _collection) public onlyOwner { + require( + rftCollection == address(0), + "RFT collection is already set" + ); + UniqueRefungible refungibleContract = UniqueRefungible(_collection); + string memory collectionType = refungibleContract.uniqueCollectionType(); + + require( + keccak256(bytes(collectionType)) == refungibleCollectionType, + "Fractionalizer contract should be an admin of the collection" + ); + require( + refungibleContract.verifyOwnerOrAdmin(), + "Fractionalizer contract should be an admin of the collection" + ); + rftCollection = _collection; + emit RFTCollectionSet(rftCollection); + } + + function mintRFTCollection(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); + emit RFTCollectionSet(rftCollection); + } + + function setAllowlist(address collection, bool status) public onlyOwner { + nftCollectionAllowList[collection] = status; + emit AllowListSet(collection, status); + } + + function nft2rft(address _collection, uint256 _token, uint128 _pieces) public { + require( + rftCollection != address(0), + "RFT collection is not set" + ); + UniqueRefungible rftCollectionContract = UniqueRefungible(rftCollection); + require( + UniqueNFT(_collection).ownerOf(_token) == msg.sender, + "Only token owner could fractionalize it" + ); + require( + nftCollectionAllowList[_collection] == true, + "Fractionalization of this collection is not allowed by admin" + ); + require( + UniqueNFT(_collection).ownerOf(_token) == msg.sender, + "Only token owner could fractionalize it" + ); + 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); + nft2rftMapping[_collection][_token] = rftTokenId; + rft2nftMapping[rftTokenAddress] = Token(_collection, _token); + + rftTokenContract = UniqueRefungibleToken(rftTokenAddress); + rftTokenContract.setParentNFT(_collection, _token); + } else { + rftTokenId = nft2rftMapping[_collection][_token]; + rftTokenAddress = rftCollectionContract.tokenContractAddress(rftTokenId); + rftTokenContract = UniqueRefungibleToken(rftTokenAddress); + } + rftTokenContract.repartition(_pieces); + rftTokenContract.transfer(msg.sender, _pieces); + emit Fractionalized(_collection, _token, rftTokenAddress, _pieces); + } + + function rft2nft(address _collection, uint256 _token) public { + require( + rftCollection != address(0), + "RFT collection is not set" + ); + require( + rftCollection == _collection, + "Wrong RFT collection" + ); + 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); + require( + rftTokenContract.balanceOf(msg.sender) == rftTokenContract.totalSupply(), + "Not all pieces are owned by the caller" + ); + rftCollectionContract.transferFrom(msg.sender, address(this), _token); + UniqueNFT(nftToken._collection).transferFrom( + address(this), + msg.sender, + nftToken._tokenId + ); + emit DeFractionalized(rftTokenAddress, nftToken._collection, nftToken._tokenId); + } +} \ No newline at end of file --- /dev/null +++ b/tests/src/eth/fractionalizer/FractionalizerAbi.json @@ -0,0 +1 @@ +[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"_collection","type":"address"},{"indexed":false,"internalType":"bool","name":"_status","type":"bool"}],"name":"AllowListSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"_rftToken","type":"address"},{"indexed":false,"internalType":"address","name":"_nftCollection","type":"address"},{"indexed":false,"internalType":"uint256","name":"_nftTokenId","type":"uint256"}],"name":"DeFractionalized","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"_collection","type":"address"},{"indexed":false,"internalType":"uint256","name":"_tokenId","type":"uint256"},{"indexed":false,"internalType":"address","name":"_rftToken","type":"address"},{"indexed":false,"internalType":"uint128","name":"_amount","type":"uint128"}],"name":"Fractionalized","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"_collection","type":"address"}],"name":"RFTCollectionSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"msg","type":"string"}],"name":"Test","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes","name":"msg","type":"bytes"}],"name":"TestB","type":"event"},{"inputs":[{"internalType":"string","name":"_name","type":"string"},{"internalType":"string","name":"_description","type":"string"},{"internalType":"string","name":"_tokenPrefix","type":"string"}],"name":"mintRFTCollection","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_collection","type":"address"},{"internalType":"uint256","name":"_token","type":"uint256"},{"internalType":"uint128","name":"_pieces","type":"uint128"}],"name":"nft2rft","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_collection","type":"address"},{"internalType":"uint256","name":"_token","type":"uint256"}],"name":"rft2nft","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"collection","type":"address"},{"internalType":"bool","name":"status","type":"bool"}],"name":"setAllowlist","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_collection","type":"address"}],"name":"setRFTCollection","outputs":[],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file --- /dev/null +++ b/tests/src/eth/fractionalizer/fractionalizer.test.ts @@ -0,0 +1,176 @@ +// 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 . + + +import Web3 from 'web3'; +import {ApiPromise} from '@polkadot/api'; +import {evmToAddress} from '@polkadot/util-crypto'; +import {readFile} from 'fs/promises'; +import fractionalizerAbi from './FractionalizerAbi.json'; +import {submitTransactionAsync} from '../../substrate/substrate-api'; +import {UNIQUE} from '../../util/helpers'; +import {collectionIdToAddress, createEthAccountWithBalance, createNonfungibleCollection, createRefungibleCollection, GAS_ARGS, itWeb3, tokenIdFromAddress, uniqueNFT, uniqueRefungible, uniqueRefungibleToken} from '../util/helpers'; +import {Contract} from 'web3-eth-contract'; + +import chai from 'chai'; +import chaiAsPromised from 'chai-as-promised'; +import chaiLike from 'chai-like'; +import {IKeyringPair} from '@polkadot/types/types'; +chai.use(chaiAsPromised); +chai.use(chaiLike); +const expect = chai.expect; + +async function deployFractionalizer(api: ApiPromise, web3: Web3, owner: string) { + const fractionalizerContract = new web3.eth.Contract(fractionalizerAbi as any, undefined, { + from: owner, + ...GAS_ARGS, + }); + return await fractionalizerContract.deploy({data: (await readFile(`${__dirname}/Fractionalizer.bin`)).toString()}).send({from: owner}); +} + +async function initFractionalizer(api: ApiPromise, web3: Web3, privateKeyWrapper: (account: string) => IKeyringPair, owner: string) { + const fractionalizer = await deployFractionalizer(api, web3, owner); + const tx = api.tx.balances.transfer(evmToAddress(fractionalizer.options.address), 10n * UNIQUE); + const alice = privateKeyWrapper('//Alice'); + await submitTransactionAsync(alice, tx); + const result = await fractionalizer.methods.mintRFTCollection('A', 'B', 'C').send(); + const rftCollectionAddress = result.events.RFTCollectionSet.returnValues._collection; + return {fractionalizer, rftCollectionAddress}; +} + +async function createRFTToken(api: ApiPromise, web3: Web3, owner: string, fractionalizer: Contract, amount: bigint) { + const {collectionIdAddress: nftCollectionAddress} = await createNonfungibleCollection(api, web3, owner); + const nftContract = uniqueNFT(web3, nftCollectionAddress, owner); + const nftTokenId = await nftContract.methods.nextTokenId().call(); + await nftContract.methods.mint(owner, nftTokenId).send(); + + await fractionalizer.methods.setAllowlist(nftCollectionAddress, true).send(); + await nftContract.methods.approve(fractionalizer.options.address, nftTokenId).send(); + const result = await fractionalizer.methods.nft2rft(nftCollectionAddress, nftTokenId, amount).send(); + const {_collection, _tokenId, _rftToken} = result.events.Fractionalized.returnValues; + return { + nftCollectionAddress: _collection, + nftTokenId: _tokenId, + rftTokenAddress: _rftToken, + }; +} + +describe('Fractionalizer contract usage', () => { + itWeb3('Set RFT collection', async ({api, web3, privateKeyWrapper}) => { + const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); + const fractionalizer = await deployFractionalizer(api, web3, owner); + const {collectionIdAddress} = await createRefungibleCollection(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(); + expect(result.events).to.be.like({ + RFTCollectionSet: { + returnValues: { + _collection: collectionIdAddress, + }, + }, + }); + }); + + itWeb3('Mint RFT collection', async ({api, web3, privateKeyWrapper}) => { + const alice = privateKeyWrapper('//Alice'); + const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); + const fractionalizer = await deployFractionalizer(api, web3, owner); + const tx = api.tx.balances.transfer(evmToAddress(fractionalizer.options.address), 10n * UNIQUE); + await submitTransactionAsync(alice, tx); + + const result = await fractionalizer.methods.mintRFTCollection('A', 'B', 'C').send({from: owner}); + expect(result.events).to.be.like({ + RFTCollectionSet: {}, + }); + expect(result.events.RFTCollectionSet.returnValues._collection).to.be.ok; + }); + + itWeb3('Set Allowlist', async ({api, web3, privateKeyWrapper}) => { + const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); + const {fractionalizer} = await initFractionalizer(api, web3, privateKeyWrapper, owner); + + const {collectionIdAddress: nftCollectionAddress} = await createNonfungibleCollection(api, web3, owner); + const result1 = await fractionalizer.methods.setAllowlist(nftCollectionAddress, true).send({from: owner}); + expect(result1.events).to.be.like({ + AllowListSet: { + returnValues: { + _collection: nftCollectionAddress, + _status: true, + }, + }, + }); + const result2 = await fractionalizer.methods.setAllowlist(nftCollectionAddress, false).send({from: owner}); + expect(result2.events).to.be.like({ + AllowListSet: { + returnValues: { + _collection: nftCollectionAddress, + _status: false, + }, + }, + }); + }); + + itWeb3('NFT to RFT', async ({api, web3, privateKeyWrapper}) => { + const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); + + const {collectionIdAddress: nftCollectionAddress} = await createNonfungibleCollection(api, web3, owner); + const nftContract = uniqueNFT(web3, nftCollectionAddress, owner); + const nftTokenId = await nftContract.methods.nextTokenId().call(); + await nftContract.methods.mint(owner, nftTokenId).send(); + + const {fractionalizer} = await initFractionalizer(api, web3, privateKeyWrapper, owner); + + await fractionalizer.methods.setAllowlist(nftCollectionAddress, true).send(); + await nftContract.methods.approve(fractionalizer.options.address, nftTokenId).send(); + const result = await fractionalizer.methods.nft2rft(nftCollectionAddress, nftTokenId, 100).send(); + expect(result.events).to.be.like({ + Fractionalized: { + returnValues: { + _collection: nftCollectionAddress, + _tokenId: nftTokenId, + _amount: '100', + }, + }, + }); + const rftTokenAddress = result.events.Fractionalized.returnValues._rftToken; + const rftTokenContract = uniqueRefungibleToken(web3, rftTokenAddress, owner); + expect(await rftTokenContract.methods.balanceOf(owner).call()).to.equal('100'); + }); + + itWeb3('RFT to NFT', async ({api, web3, privateKeyWrapper}) => { + const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); + + const {fractionalizer, rftCollectionAddress} = await initFractionalizer(api, web3, privateKeyWrapper, owner); + const {rftTokenAddress, nftCollectionAddress, nftTokenId} = await createRFTToken(api, web3, owner, fractionalizer, 100n); + + const {collectionId, tokenId} = tokenIdFromAddress(rftTokenAddress); + const refungibleAddress = collectionIdToAddress(collectionId); + expect(rftCollectionAddress).to.be.equal(refungibleAddress); + const refungibleTokenContract = uniqueRefungibleToken(web3, rftTokenAddress, owner); + await refungibleTokenContract.methods.approve(fractionalizer.options.address, 100).send(); + const result = await fractionalizer.methods.rft2nft(refungibleAddress, tokenId).send(); + expect(result.events).to.be.like({ + DeFractionalized: { + returnValues: { + _rftToken: rftTokenAddress, + _nftCollection: nftCollectionAddress, + _nftTokenId: nftTokenId, + }, + }, + }); + }); +}); \ No newline at end of file --- a/tests/src/eth/util/helpers.ts +++ b/tests/src/eth/util/helpers.ts @@ -32,6 +32,7 @@ import fungibleAbi from '../fungibleAbi.json'; import nonFungibleAbi from '../nonFungibleAbi.json'; import refungibleAbi from '../reFungibleAbi.json'; +import refungibleTokenAbi from '../reFungibleTokenAbi.json'; import contractHelpersAbi from './contractHelpersAbi.json'; export const GAS_ARGS = {gas: 2500000}; @@ -101,6 +102,18 @@ ]); return Web3.utils.toChecksumAddress('0x' + buf.toString('hex')); } + +export function tokenIdFromAddress(address: string) { + if (!address.startsWith('0x')) + throw 'address not starts with "0x"'; + if (address.length > 42) + throw 'address length is more than 20 bytes'; + return { + collectionId: Number('0x' + address.substring(address.length - 16, address.length - 8)), + tokenId: Number('0x' + address.substring(address.length - 8)), + }; +} + export function tokenIdToCross(collection: number, token: number): CrossAccountId { return { Ethereum: tokenIdToAddress(collection, token), @@ -128,6 +141,44 @@ expect(result.success).to.be.true; } +export async function createRefungibleCollection(api: ApiPromise, web3: Web3, owner: string) { + const collectionHelper = evmCollectionHelpers(web3, owner); + const result = await collectionHelper.methods + .createRefungibleCollection('A', 'B', 'C') + .send(); + return await getCollectionAddressFromResult(api, result); +} + + +export async function createNonfungibleCollection(api: ApiPromise, web3: Web3, owner: string) { + const collectionHelper = evmCollectionHelpers(web3, owner); + const result = await collectionHelper.methods + .createNonfungibleCollection('A', 'B', 'C') + .send(); + return await getCollectionAddressFromResult(api, result); +} + +export function uniqueNFT(web3: Web3, address: string, owner: string) { + return new web3.eth.Contract(nonFungibleAbi as any, address, { + from: owner, + ...GAS_ARGS, + }); +} + +export function uniqueRefungible(web3: Web3, collectionAddress: string, owner: string) { + return new web3.eth.Contract(refungibleAbi as any, collectionAddress, { + from: owner, + ...GAS_ARGS, + }); +} + +export function uniqueRefungibleToken(web3: Web3, tokenAddress: string, owner: string) { + return new web3.eth.Contract(refungibleTokenAbi as any, tokenAddress, { + from: owner, + ...GAS_ARGS, + }); +} + export async function itWeb3(name: string, cb: (apis: { web3: Web3, api: ApiPromise, privateKeyWrapper: (account: string) => IKeyringPair }) => any, opts: { only?: boolean, skip?: boolean } = {}) { let i: any = it; if (opts.only) i = i.only; --- a/tests/yarn.lock +++ b/tests/yarn.lock @@ -963,6 +963,20 @@ dependencies: "@types/chai" "*" +"@types/chai-like@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@types/chai-like/-/chai-like-1.1.1.tgz#c454039b0a2f92664fb5b7b7a2a66c3358783ae7" + integrity sha512-s46EZsupBuVhLn66DbRee5B0SELLmL4nFXVrBiV29BxLGm9Sh7Bful623j3AfiQRu2zAP4cnlZ3ETWB3eWc4bA== + dependencies: + "@types/chai" "*" + +"@types/chai-things@^0.0.35": + version "0.0.35" + resolved "https://registry.yarnpkg.com/@types/chai-things/-/chai-things-0.0.35.tgz#4b5d9ec032067faa62b3bf7bb40dc0bec941945f" + integrity sha512-BC8FwMf9FHj87XT4dgTwbdb8dNRilGqYWGmwLPdJ54YNk6K2PlcFTt68NGHjgPDnms8zIYcOtmPePd0mPNTo/Q== + dependencies: + "@types/chai" "*" + "@types/chai@*", "@types/chai@^4.3.1": version "4.3.1" resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.1.tgz#e2c6e73e0bdeb2521d00756d099218e9f5d90a04" @@ -1545,6 +1559,16 @@ dependencies: check-error "^1.0.2" +chai-like@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/chai-like/-/chai-like-1.1.1.tgz#8c558a414c34514e814d497c772547ceb7958f64" + integrity sha512-VKa9z/SnhXhkT1zIjtPACFWSoWsqVoaz1Vg+ecrKo5DCKVlgL30F/pEyEvXPBOVwCgLZcWUleCM/C1okaKdTTA== + +chai-things@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/chai-things/-/chai-things-0.2.0.tgz#c55128378f9bb399e994f00052151984ed6ebe70" + integrity sha512-6ns0SU21xdRCoEXVKH3HGbwnsgfVMXQ+sU5V8PI9rfxaITos8lss1vUxbF1FAcJKjfqmmmLVlr/z3sLes00w+A== + chai@^4.3.6: version "4.3.6" resolved "https://registry.yarnpkg.com/chai/-/chai-4.3.6.tgz#ffe4ba2d9fa9d6680cc0b370adae709ec9011e9c" -- gitstuff