git.delta.rocks / unique-network / refs/commits / d4443286d44f

difftreelog

tests(eth): more small tests refactored to playgrounds

Fahrrader2022-09-30parent: #e15be01.patch.diff
in: master

8 files changed

modifiedtests/src/eth/createNFTCollection.test.tsdiffbeforeafterboth
15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
1616
17import {evmToAddress} from '@polkadot/util-crypto';17import {evmToAddress} from '@polkadot/util-crypto';
18import {expect} from 'chai';18import {IKeyringPair} from '@polkadot/types/types';
19import {getCreatedCollectionCount, getDetailedCollectionInfo} from '../util/helpers';
20import {19import {expect, itEth, usingEthPlaygrounds} from './util/playgrounds';
21 evmCollectionHelpers,
22 collectionIdToAddress,
23 createEthAccount,
24 createEthAccountWithBalance,
25 evmCollection,
26 itWeb3,
27 getCollectionAddressFromResult,
28} from './util/helpers';
2920
30describe('Create NFT collection from EVM', () => {21describe('Create NFT collection from EVM', () => {
31 itWeb3('Create collection', async ({api, web3, privateKeyWrapper}) => {22 let donor: IKeyringPair;
23
24 before(async function() {
25 await usingEthPlaygrounds(async (_helper, privateKey) => {
32 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);26 donor = privateKey('//Alice');
27 });
28 });
29
30 itEth('Create collection', async ({helper}) => {
33 const collectionHelper = evmCollectionHelpers(web3, owner);31 const owner = await helper.eth.createAccountWithBalance(donor);
32
34 const collectionName = 'CollectionEVM';33 const name = 'CollectionEVM';
35 const description = 'Some description';34 const description = 'Some description';
36 const tokenPrefix = 'token prefix';35 const prefix = 'token prefix';
36
37 37 // todo:playgrounds this might fail when in async environment.
38 const collectionCountBefore = await getCreatedCollectionCount(api);38 const collectionCountBefore = +(await helper.callRpc('api.rpc.unique.collectionStats')).created;
39 const result = await collectionHelper.methods39 const {collectionId} = await helper.eth.createNonfungibleCollection(owner, name, description, prefix);
40 .createNonfungibleCollection(collectionName, description, tokenPrefix)
41 .send();40 const collectionCountAfter = +(await helper.callRpc('api.rpc.unique.collectionStats')).created;
41
42 const collectionCountAfter = await getCreatedCollectionCount(api);42 const collection = helper.nft.getCollectionObject(collectionId);
43 43 const data = (await collection.getData())!;
44 const {collectionId, collection} = await getCollectionAddressFromResult(api, result);44
45 expect(collectionCountAfter - collectionCountBefore).to.be.eq(1);45 expect(collectionCountAfter - collectionCountBefore).to.be.eq(1);
46 expect(collectionId).to.be.eq(collectionCountAfter);46 expect(collectionId).to.be.eq(collectionCountAfter);
47 expect(collection.name.map(v => String.fromCharCode(v.toNumber())).join('')).to.be.eq(collectionName);47 expect(data.name).to.be.eq(name);
48 expect(collection.description.map(v => String.fromCharCode(v.toNumber())).join('')).to.be.eq(description);48 expect(data.description).to.be.eq(description);
49 expect(collection.tokenPrefix.toHuman()).to.be.eq(tokenPrefix);49 expect(data.raw.tokenPrefix).to.be.eq(prefix);
50 expect(collection.mode.isNft).to.be.true;50 expect(data.raw.mode).to.be.eq('NFT');
51 });51 });
5252
53 itWeb3('Check collection address exist', async ({api, web3, privateKeyWrapper}) => {53 // todo:playgrounds this test will fail when in async environment.
54 itEth('Check collection address exist', async ({helper}) => {
54 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);55 const owner = await helper.eth.createAccountWithBalance(donor);
56
55 const collectionHelpers = evmCollectionHelpers(web3, owner);57 const expectedCollectionId = +(await helper.callRpc('api.rpc.unique.collectionStats')).created + 1;
56
57 const expectedCollectionId = await getCreatedCollectionCount(api) + 1;58 const expectedCollectionAddress = helper.ethAddress.fromCollectionId(expectedCollectionId);
58 const expectedCollectionAddress = collectionIdToAddress(expectedCollectionId);59 const collectionHelpers = helper.ethNativeContract.collectionHelpers(owner);
60
59 expect(await collectionHelpers.methods61 expect(await collectionHelpers.methods
60 .isCollectionExist(expectedCollectionAddress)62 .isCollectionExist(expectedCollectionAddress)
69 .call()).to.be.true;71 .call()).to.be.true;
70 });72 });
71 73
72 itWeb3('Set sponsorship', async ({api, web3, privateKeyWrapper}) => {74 itEth('Set sponsorship', async ({helper}) => {
73 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);75 const owner = await helper.eth.createAccountWithBalance(donor);
74 const collectionHelpers = evmCollectionHelpers(web3, owner);76 const sponsor = await helper.eth.createAccountWithBalance(donor);
75 let result = await collectionHelpers.methods.createNonfungibleCollection('Sponsor collection', '1', '1').send();77 const ss58Format = helper.chain.getChainProperties().ss58Format;
76 const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);78 const {collectionId, collectionAddress} = await helper.eth.createNonfungibleCollection(owner, 'Sponsor', 'absolutely anything', 'ROC');
77 const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper);79
78 const collectionEvm = evmCollection(web3, owner, collectionIdAddress);80 const collection = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
79 result = await collectionEvm.methods.setCollectionSponsor(sponsor).send();81 await collection.methods.setCollectionSponsor(sponsor).send();
82
80 let collectionSub = (await getDetailedCollectionInfo(api, collectionId))!;83 let data = (await helper.nft.getData(collectionId))!;
81 expect(collectionSub.sponsorship.isUnconfirmed).to.be.true;
82 const ss58Format = (api.registry.getChainProperties())!.toJSON().ss58Format;
83 expect(collectionSub.sponsorship.asUnconfirmed.toHuman()).to.be.eq(evmToAddress(sponsor, Number(ss58Format)));84 expect(data.raw.sponsorship.Unconfirmed).to.be.equal(evmToAddress(sponsor, Number(ss58Format)));
85
84 await expect(collectionEvm.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('caller is not set as sponsor');86 await expect(collection.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('caller is not set as sponsor');
87
85 const sponsorCollection = evmCollection(web3, sponsor, collectionIdAddress);88 const sponsorCollection = helper.ethNativeContract.collection(collectionAddress, 'nft', sponsor);
86 await sponsorCollection.methods.confirmCollectionSponsorship().send();89 await sponsorCollection.methods.confirmCollectionSponsorship().send();
90
87 collectionSub = (await getDetailedCollectionInfo(api, collectionId))!;91 data = (await helper.nft.getData(collectionId))!;
88 expect(collectionSub.sponsorship.isConfirmed).to.be.true;92 expect(data.raw.sponsorship.Confirmed).to.be.equal(evmToAddress(sponsor, Number(ss58Format)));
89 expect(collectionSub.sponsorship.asConfirmed.toHuman()).to.be.eq(evmToAddress(sponsor, Number(ss58Format)));
90 });93 });
9194
92 itWeb3('Set limits', async ({api, web3, privateKeyWrapper}) => {95 itEth('Set limits', async ({helper}) => {
93 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);96 const owner = await helper.eth.createAccountWithBalance(donor);
94 const collectionHelpers = evmCollectionHelpers(web3, owner);97 const {collectionId, collectionAddress} = await helper.eth.createNonfungibleCollection(owner, 'Limits', 'absolutely anything', 'FLO');
95 const result = await collectionHelpers.methods.createNonfungibleCollection('Const collection', '5', '5').send();
96 const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);
97 const limits = {98 const limits = {
98 accountTokenOwnershipLimit: 1000,99 accountTokenOwnershipLimit: 1000,
99 sponsoredDataSize: 1024,100 sponsoredDataSize: 1024,
106 transfersEnabled: false,107 transfersEnabled: false,
107 };108 };
108109
109 const collectionEvm = evmCollection(web3, owner, collectionIdAddress);110 const collection = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
110 await collectionEvm.methods['setCollectionLimit(string,uint32)']('accountTokenOwnershipLimit', limits.accountTokenOwnershipLimit).send();111 await collection.methods['setCollectionLimit(string,uint32)']('accountTokenOwnershipLimit', limits.accountTokenOwnershipLimit).send();
111 await collectionEvm.methods['setCollectionLimit(string,uint32)']('sponsoredDataSize', limits.sponsoredDataSize).send();112 await collection.methods['setCollectionLimit(string,uint32)']('sponsoredDataSize', limits.sponsoredDataSize).send();
112 await collectionEvm.methods['setCollectionLimit(string,uint32)']('sponsoredDataRateLimit', limits.sponsoredDataRateLimit).send();113 await collection.methods['setCollectionLimit(string,uint32)']('sponsoredDataRateLimit', limits.sponsoredDataRateLimit).send();
113 await collectionEvm.methods['setCollectionLimit(string,uint32)']('tokenLimit', limits.tokenLimit).send();114 await collection.methods['setCollectionLimit(string,uint32)']('tokenLimit', limits.tokenLimit).send();
114 await collectionEvm.methods['setCollectionLimit(string,uint32)']('sponsorTransferTimeout', limits.sponsorTransferTimeout).send();115 await collection.methods['setCollectionLimit(string,uint32)']('sponsorTransferTimeout', limits.sponsorTransferTimeout).send();
115 await collectionEvm.methods['setCollectionLimit(string,uint32)']('sponsorApproveTimeout', limits.sponsorApproveTimeout).send();116 await collection.methods['setCollectionLimit(string,uint32)']('sponsorApproveTimeout', limits.sponsorApproveTimeout).send();
116 await collectionEvm.methods['setCollectionLimit(string,bool)']('ownerCanTransfer', limits.ownerCanTransfer).send();117 await collection.methods['setCollectionLimit(string,bool)']('ownerCanTransfer', limits.ownerCanTransfer).send();
117 await collectionEvm.methods['setCollectionLimit(string,bool)']('ownerCanDestroy', limits.ownerCanDestroy).send();118 await collection.methods['setCollectionLimit(string,bool)']('ownerCanDestroy', limits.ownerCanDestroy).send();
118 await collectionEvm.methods['setCollectionLimit(string,bool)']('transfersEnabled', limits.transfersEnabled).send();119 await collection.methods['setCollectionLimit(string,bool)']('transfersEnabled', limits.transfersEnabled).send();
119 120
120 const collectionSub = (await getDetailedCollectionInfo(api, collectionId))!;121 const data = (await helper.nft.getData(collectionId))!;
121 expect(collectionSub.limits.accountTokenOwnershipLimit.unwrap().toNumber()).to.be.eq(limits.accountTokenOwnershipLimit);122 expect(data.raw.limits.accountTokenOwnershipLimit).to.be.eq(limits.accountTokenOwnershipLimit);
122 expect(collectionSub.limits.sponsoredDataSize.unwrap().toNumber()).to.be.eq(limits.sponsoredDataSize);123 expect(data.raw.limits.sponsoredDataSize).to.be.eq(limits.sponsoredDataSize);
123 expect(collectionSub.limits.sponsoredDataRateLimit.unwrap().asBlocks.toNumber()).to.be.eq(limits.sponsoredDataRateLimit);124 expect(data.raw.limits.sponsoredDataRateLimit.blocks).to.be.eq(limits.sponsoredDataRateLimit);
124 expect(collectionSub.limits.tokenLimit.unwrap().toNumber()).to.be.eq(limits.tokenLimit);125 expect(data.raw.limits.tokenLimit).to.be.eq(limits.tokenLimit);
125 expect(collectionSub.limits.sponsorTransferTimeout.unwrap().toNumber()).to.be.eq(limits.sponsorTransferTimeout);126 expect(data.raw.limits.sponsorTransferTimeout).to.be.eq(limits.sponsorTransferTimeout);
126 expect(collectionSub.limits.sponsorApproveTimeout.unwrap().toNumber()).to.be.eq(limits.sponsorApproveTimeout);127 expect(data.raw.limits.sponsorApproveTimeout).to.be.eq(limits.sponsorApproveTimeout);
127 expect(collectionSub.limits.ownerCanTransfer.toHuman()).to.be.eq(limits.ownerCanTransfer);128 expect(data.raw.limits.ownerCanTransfer).to.be.eq(limits.ownerCanTransfer);
128 expect(collectionSub.limits.ownerCanDestroy.toHuman()).to.be.eq(limits.ownerCanDestroy);129 expect(data.raw.limits.ownerCanDestroy).to.be.eq(limits.ownerCanDestroy);
129 expect(collectionSub.limits.transfersEnabled.toHuman()).to.be.eq(limits.transfersEnabled);130 expect(data.raw.limits.transfersEnabled).to.be.eq(limits.transfersEnabled);
130 });131 });
131132
132 itWeb3('Collection address exist', async ({api, web3, privateKeyWrapper}) => {133 itEth('Collection address exist', async ({helper}) => {
133 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);134 const owner = await helper.eth.createAccountWithBalance(donor);
134 const collectionAddressForNonexistentCollection = '0x17C4E6453CC49AAAAEACA894E6D9683E00112233';135 const collectionAddressForNonexistentCollection = '0x17C4E6453CC49AAAAEACA894E6D9683E00112233';
135 const collectionHelpers = evmCollectionHelpers(web3, owner);136 expect(await helper.ethNativeContract.collectionHelpers(collectionAddressForNonexistentCollection)
136 expect(await collectionHelpers.methods
137 .isCollectionExist(collectionAddressForNonexistentCollection).call())137 .methods.isCollectionExist(collectionAddressForNonexistentCollection).call())
138 .to.be.false;138 .to.be.false;
139 139
140 const result = await collectionHelpers.methods.createNonfungibleCollection('Collection address exist', '7', '7').send();140 const {collectionAddress} = await helper.eth.createNonfungibleCollection(owner, 'Exister', 'absolutely anything', 'EVC');
141 const {collectionIdAddress} = await getCollectionAddressFromResult(api, result);141 expect(await helper.ethNativeContract.collectionHelpers(collectionAddress)
142 expect(await collectionHelpers.methods
143 .isCollectionExist(collectionIdAddress).call())142 .methods.isCollectionExist(collectionAddress).call())
144 .to.be.true;143 .to.be.true;
145 });144 });
146});145});
147146
148describe('(!negative tests!) Create NFT collection from EVM', () => {147describe('(!negative tests!) Create NFT collection from EVM', () => {
149 itWeb3('(!negative test!) Create collection (bad lengths)', async ({api, web3, privateKeyWrapper}) => {148 let donor: IKeyringPair;
149
150 before(async function() {
151 await usingEthPlaygrounds(async (_helper, privateKey) => {
152 donor = privateKey('//Alice');
153 });
154 });
155
156 itEth('(!negative test!) Create collection (bad lengths)', async ({helper}) => {
150 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);157 const owner = await helper.eth.createAccountWithBalance(donor);
151 const helper = evmCollectionHelpers(web3, owner);158 const collectionHelper = helper.ethNativeContract.collectionHelpers(owner);
152 {159 {
153 const MAX_NAME_LENGHT = 64;160 const MAX_NAME_LENGTH = 64;
154 const collectionName = 'A'.repeat(MAX_NAME_LENGHT + 1);161 const collectionName = 'A'.repeat(MAX_NAME_LENGTH + 1);
155 const description = 'A';162 const description = 'A';
156 const tokenPrefix = 'A';163 const tokenPrefix = 'A';
157 164
158 await expect(helper.methods165 await expect(collectionHelper.methods
159 .createNonfungibleCollection(collectionName, description, tokenPrefix)166 .createNonfungibleCollection(collectionName, description, tokenPrefix)
160 .call()).to.be.rejectedWith('name is too long. Max length is ' + MAX_NAME_LENGHT);167 .call()).to.be.rejectedWith('name is too long. Max length is ' + MAX_NAME_LENGTH);
161 168
162 }169 }
163 { 170 {
164 const MAX_DESCRIPTION_LENGHT = 256;171 const MAX_DESCRIPTION_LENGTH = 256;
165 const collectionName = 'A';172 const collectionName = 'A';
166 const description = 'A'.repeat(MAX_DESCRIPTION_LENGHT + 1);173 const description = 'A'.repeat(MAX_DESCRIPTION_LENGTH + 1);
167 const tokenPrefix = 'A';174 const tokenPrefix = 'A';
168 await expect(helper.methods175 await expect(collectionHelper.methods
169 .createNonfungibleCollection(collectionName, description, tokenPrefix)176 .createNonfungibleCollection(collectionName, description, tokenPrefix)
170 .call()).to.be.rejectedWith('description is too long. Max length is ' + MAX_DESCRIPTION_LENGHT);177 .call()).to.be.rejectedWith('description is too long. Max length is ' + MAX_DESCRIPTION_LENGTH);
171 }178 }
172 { 179 {
173 const MAX_TOKEN_PREFIX_LENGHT = 16;180 const MAX_TOKEN_PREFIX_LENGTH = 16;
174 const collectionName = 'A';181 const collectionName = 'A';
175 const description = 'A';182 const description = 'A';
176 const tokenPrefix = 'A'.repeat(MAX_TOKEN_PREFIX_LENGHT + 1);183 const tokenPrefix = 'A'.repeat(MAX_TOKEN_PREFIX_LENGTH + 1);
177 await expect(helper.methods184 await expect(collectionHelper.methods
178 .createNonfungibleCollection(collectionName, description, tokenPrefix)185 .createNonfungibleCollection(collectionName, description, tokenPrefix)
179 .call()).to.be.rejectedWith('token_prefix is too long. Max length is ' + MAX_TOKEN_PREFIX_LENGHT);186 .call()).to.be.rejectedWith('token_prefix is too long. Max length is ' + MAX_TOKEN_PREFIX_LENGTH);
180 }187 }
181 });188 });
182 189
183 itWeb3('(!negative test!) Create collection (no funds)', async ({web3}) => {190 itEth('(!negative test!) Create collection (no funds)', async ({helper}) => {
184 const owner = createEthAccount(web3);191 const owner = helper.eth.createAccount();
185 const helper = evmCollectionHelpers(web3, owner);192 const collectionHelper = helper.ethNativeContract.collectionHelpers(owner);
186 const collectionName = 'A';193 await expect(collectionHelper.methods
187 const description = 'A';
188 const tokenPrefix = 'A';
189
190 await expect(helper.methods
191 .createNonfungibleCollection(collectionName, description, tokenPrefix)194 .createNonfungibleCollection('Peasantry', 'absolutely anything', 'CVE')
192 .call()).to.be.rejectedWith('NotSufficientFounds');195 .call()).to.be.rejectedWith('NotSufficientFounds');
193 });196 });
194197
195 itWeb3('(!negative test!) Check owner', async ({api, web3, privateKeyWrapper}) => {198 itEth('(!negative test!) Check owner', async ({helper}) => {
196 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);199 const owner = await helper.eth.createAccountWithBalance(donor);
197 const notOwner = createEthAccount(web3);200 const malfeasant = helper.eth.createAccount();
198 const collectionHelpers = evmCollectionHelpers(web3, owner);201 const {collectionAddress} = await helper.eth.createNonfungibleCollection(owner, 'Transgressed', 'absolutely anything', 'COR');
199 const result = await collectionHelpers.methods.createNonfungibleCollection('A', 'A', 'A').send();
200 const {collectionIdAddress} = await getCollectionAddressFromResult(api, result);202 const malfeasantCollection = helper.ethNativeContract.collection(collectionAddress, 'nft', malfeasant);
201 const contractEvmFromNotOwner = evmCollection(web3, notOwner, collectionIdAddress);
202 const EXPECTED_ERROR = 'NoPermission';203 const EXPECTED_ERROR = 'NoPermission';
203 {204 {
204 const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper);205 const sponsor = await helper.eth.createAccountWithBalance(donor);
205 await expect(contractEvmFromNotOwner.methods206 await expect(malfeasantCollection.methods
206 .setCollectionSponsor(sponsor)207 .setCollectionSponsor(sponsor)
207 .call()).to.be.rejectedWith(EXPECTED_ERROR);208 .call()).to.be.rejectedWith(EXPECTED_ERROR);
208 209
209 const sponsorCollection = evmCollection(web3, sponsor, collectionIdAddress);210 const sponsorCollection = helper.ethNativeContract.collection(collectionAddress, 'nft', sponsor);
210 await expect(sponsorCollection.methods211 await expect(sponsorCollection.methods
211 .confirmCollectionSponsorship()212 .confirmCollectionSponsorship()
212 .call()).to.be.rejectedWith('caller is not set as sponsor');213 .call()).to.be.rejectedWith('caller is not set as sponsor');
213 }214 }
214 {215 {
215 await expect(contractEvmFromNotOwner.methods216 await expect(malfeasantCollection.methods
216 .setCollectionLimit('account_token_ownership_limit', '1000')217 .setCollectionLimit('account_token_ownership_limit', '1000')
217 .call()).to.be.rejectedWith(EXPECTED_ERROR);218 .call()).to.be.rejectedWith(EXPECTED_ERROR);
218 }219 }
219 });220 });
220221
221 itWeb3('(!negative test!) Set limits', async ({api, web3, privateKeyWrapper}) => {222 itEth('(!negative test!) Set limits', async ({helper}) => {
222 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);223 const owner = await helper.eth.createAccountWithBalance(donor);
223 const collectionHelpers = evmCollectionHelpers(web3, owner);224 const {collectionAddress} = await helper.eth.createNonfungibleCollection(owner, 'Limits', 'absolutely anything', 'OLF');
224 const result = await collectionHelpers.methods.createNonfungibleCollection('Schema collection', 'A', 'A').send();
225 const {collectionIdAddress} = await getCollectionAddressFromResult(api, result);225 const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
226 const collectionEvm = evmCollection(web3, owner, collectionIdAddress);
227 await expect(collectionEvm.methods226 await expect(collectionEvm.methods
228 .setCollectionLimit('badLimit', 'true')227 .setCollectionLimit('badLimit', 'true')
229 .call()).to.be.rejectedWith('unknown boolean limit "badLimit"');228 .call()).to.be.rejectedWith('unknown boolean limit "badLimit"');
modifiedtests/src/eth/createRFTCollection.test.tsdiffbeforeafterboth
--- a/tests/src/eth/createRFTCollection.test.ts
+++ b/tests/src/eth/createRFTCollection.test.ts
@@ -15,51 +15,50 @@
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
 import {evmToAddress} from '@polkadot/util-crypto';
-import {expect} from 'chai';
-import {getCreatedCollectionCount, getDetailedCollectionInfo, requirePallets, Pallets} from '../util/helpers';
-import {
-  evmCollectionHelpers,
-  collectionIdToAddress,
-  createEthAccount,
-  createEthAccountWithBalance,
-  evmCollection,
-  itWeb3,
-  getCollectionAddressFromResult,
-} from './util/helpers';
+import {IKeyringPair} from '@polkadot/types/types';
+import {Pallets, requirePalletsOrSkip} from '../util/playgrounds';
+import {expect, itEth, usingEthPlaygrounds} from './util/playgrounds';
 
 describe('Create RFT collection from EVM', () => {
+  let donor: IKeyringPair;
+
   before(async function() {
-    await requirePallets(this, [Pallets.ReFungible]);
+    await usingEthPlaygrounds(async (helper, privateKey) => {
+      requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);
+      donor = privateKey('//Alice');
+    });
   });
 
-  itWeb3('Create collection', async ({api, web3, privateKeyWrapper}) => {
-    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
-    const collectionHelper = evmCollectionHelpers(web3, owner);
-    const collectionName = 'CollectionEVM';
+  itEth('Create collection', async ({helper}) => {
+    const owner = await helper.eth.createAccountWithBalance(donor);
+    
+    const name = 'CollectionEVM';
     const description = 'Some description';
-    const tokenPrefix = 'token prefix';
+    const prefix = 'token prefix';
   
-    const collectionCountBefore = await getCreatedCollectionCount(api);
-    const result = await collectionHelper.methods
-      .createRFTCollection(collectionName, description, tokenPrefix)
-      .send();
-    const collectionCountAfter = await getCreatedCollectionCount(api);
+    // todo:playgrounds this might fail when in async environment.
+    const collectionCountBefore = +(await helper.callRpc('api.rpc.unique.collectionStats')).created;
+    const {collectionId} = await helper.eth.createRefungibleCollection(owner, name, description, prefix);
+    const collectionCountAfter = +(await helper.callRpc('api.rpc.unique.collectionStats')).created;
   
-    const {collectionId, collection} = await getCollectionAddressFromResult(api, result);
+    const data = (await helper.rft.getData(collectionId))!;
+
     expect(collectionCountAfter - collectionCountBefore).to.be.eq(1);
     expect(collectionId).to.be.eq(collectionCountAfter);
-    expect(collection.name.map(v => String.fromCharCode(v.toNumber())).join('')).to.be.eq(collectionName);
-    expect(collection.description.map(v => String.fromCharCode(v.toNumber())).join('')).to.be.eq(description);
-    expect(collection.tokenPrefix.toHuman()).to.be.eq(tokenPrefix);
-    expect(collection.mode.isReFungible).to.be.true;
+    expect(data.name).to.be.eq(name);
+    expect(data.description).to.be.eq(description);
+    expect(data.raw.tokenPrefix).to.be.eq(prefix);
+    expect(data.raw.mode).to.be.eq('ReFungible');
   });
 
-  itWeb3('Check collection address exist', async ({api, web3, privateKeyWrapper}) => {
-    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
-    const collectionHelpers = evmCollectionHelpers(web3, owner);
-  
-    const expectedCollectionId = await getCreatedCollectionCount(api) + 1;
-    const expectedCollectionAddress = collectionIdToAddress(expectedCollectionId);
+  // todo:playgrounds this test will fail when in async environment.
+  itEth('Check collection address exist', async ({helper}) => {
+    const owner = await helper.eth.createAccountWithBalance(donor);
+
+    const expectedCollectionId = +(await helper.callRpc('api.rpc.unique.collectionStats')).created + 1;
+    const expectedCollectionAddress = helper.ethAddress.fromCollectionId(expectedCollectionId);
+    const collectionHelpers = helper.ethNativeContract.collectionHelpers(owner);
+
     expect(await collectionHelpers.methods
       .isCollectionExist(expectedCollectionAddress)
       .call()).to.be.false;
@@ -73,31 +72,30 @@
       .call()).to.be.true;
   });
   
-  itWeb3('Set sponsorship', async ({api, web3, privateKeyWrapper}) => {
-    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
-    const collectionHelpers = evmCollectionHelpers(web3, owner);
-    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'});
-    result = await collectionEvm.methods.setCollectionSponsor(sponsor).send();
-    let collectionSub = (await getDetailedCollectionInfo(api, collectionId))!;
-    expect(collectionSub.sponsorship.isUnconfirmed).to.be.true;
-    const ss58Format = (api.registry.getChainProperties())!.toJSON().ss58Format;
-    expect(collectionSub.sponsorship.asUnconfirmed.toHuman()).to.be.eq(evmToAddress(sponsor, Number(ss58Format)));
-    await expect(collectionEvm.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('caller is not set as sponsor');
-    const sponsorCollection = evmCollection(web3, sponsor, collectionIdAddress);
+  itEth('Set sponsorship', async ({helper}) => {
+    const owner = await helper.eth.createAccountWithBalance(donor);
+    const sponsor = await helper.eth.createAccountWithBalance(donor);
+    const ss58Format = helper.chain.getChainProperties().ss58Format;
+    const {collectionId, collectionAddress} = await helper.eth.createRefungibleCollection(owner, 'Sponsor', 'absolutely anything', 'ENVY');
+
+    const collection = helper.ethNativeContract.collection(collectionAddress, 'rft', owner);
+    await collection.methods.setCollectionSponsor(sponsor).send();
+
+    let data = (await helper.rft.getData(collectionId))!;
+    expect(data.raw.sponsorship.Unconfirmed).to.be.equal(evmToAddress(sponsor, Number(ss58Format)));
+
+    await expect(collection.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('caller is not set as sponsor');
+
+    const sponsorCollection = helper.ethNativeContract.collection(collectionAddress, 'rft', sponsor);
     await sponsorCollection.methods.confirmCollectionSponsorship().send();
-    collectionSub = (await getDetailedCollectionInfo(api, collectionId))!;
-    expect(collectionSub.sponsorship.isConfirmed).to.be.true;
-    expect(collectionSub.sponsorship.asConfirmed.toHuman()).to.be.eq(evmToAddress(sponsor, Number(ss58Format)));
+
+    data = (await helper.rft.getData(collectionId))!;
+    expect(data.raw.sponsorship.Confirmed).to.be.equal(evmToAddress(sponsor, Number(ss58Format)));
   });
 
-  itWeb3('Set limits', async ({api, web3, privateKeyWrapper}) => {
-    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
-    const collectionHelpers = evmCollectionHelpers(web3, owner);
-    const result = await collectionHelpers.methods.createRFTCollection('Const collection', '5', '5').send();
-    const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);
+  itEth('Set limits', async ({helper}) => {
+    const owner = await helper.eth.createAccountWithBalance(donor);
+    const {collectionId, collectionAddress} = await helper.eth.createRefungibleCollection(owner, 'Limits', 'absolutely anything', 'INSI');
     const limits = {
       accountTokenOwnershipLimit: 1000,
       sponsoredDataSize: 1024,
@@ -110,128 +108,122 @@
       transfersEnabled: false,
     };
 
-    const collectionEvm = evmCollection(web3, owner, collectionIdAddress, {type: 'ReFungible'});
-    await collectionEvm.methods['setCollectionLimit(string,uint32)']('accountTokenOwnershipLimit', limits.accountTokenOwnershipLimit).send();
-    await collectionEvm.methods['setCollectionLimit(string,uint32)']('sponsoredDataSize', limits.sponsoredDataSize).send();
-    await collectionEvm.methods['setCollectionLimit(string,uint32)']('sponsoredDataRateLimit', limits.sponsoredDataRateLimit).send();
-    await collectionEvm.methods['setCollectionLimit(string,uint32)']('tokenLimit', limits.tokenLimit).send();
-    await collectionEvm.methods['setCollectionLimit(string,uint32)']('sponsorTransferTimeout', limits.sponsorTransferTimeout).send();
-    await collectionEvm.methods['setCollectionLimit(string,uint32)']('sponsorApproveTimeout', limits.sponsorApproveTimeout).send();
-    await collectionEvm.methods['setCollectionLimit(string,bool)']('ownerCanTransfer', limits.ownerCanTransfer).send();
-    await collectionEvm.methods['setCollectionLimit(string,bool)']('ownerCanDestroy', limits.ownerCanDestroy).send();
-    await collectionEvm.methods['setCollectionLimit(string,bool)']('transfersEnabled', limits.transfersEnabled).send();
+    const collection = helper.ethNativeContract.collection(collectionAddress, 'rft', owner);
+    await collection.methods['setCollectionLimit(string,uint32)']('accountTokenOwnershipLimit', limits.accountTokenOwnershipLimit).send();
+    await collection.methods['setCollectionLimit(string,uint32)']('sponsoredDataSize', limits.sponsoredDataSize).send();
+    await collection.methods['setCollectionLimit(string,uint32)']('sponsoredDataRateLimit', limits.sponsoredDataRateLimit).send();
+    await collection.methods['setCollectionLimit(string,uint32)']('tokenLimit', limits.tokenLimit).send();
+    await collection.methods['setCollectionLimit(string,uint32)']('sponsorTransferTimeout', limits.sponsorTransferTimeout).send();
+    await collection.methods['setCollectionLimit(string,uint32)']('sponsorApproveTimeout', limits.sponsorApproveTimeout).send();
+    await collection.methods['setCollectionLimit(string,bool)']('ownerCanTransfer', limits.ownerCanTransfer).send();
+    await collection.methods['setCollectionLimit(string,bool)']('ownerCanDestroy', limits.ownerCanDestroy).send();
+    await collection.methods['setCollectionLimit(string,bool)']('transfersEnabled', limits.transfersEnabled).send();
     
-    const collectionSub = (await getDetailedCollectionInfo(api, collectionId))!;
-    expect(collectionSub.limits.accountTokenOwnershipLimit.unwrap().toNumber()).to.be.eq(limits.accountTokenOwnershipLimit);
-    expect(collectionSub.limits.sponsoredDataSize.unwrap().toNumber()).to.be.eq(limits.sponsoredDataSize);
-    expect(collectionSub.limits.sponsoredDataRateLimit.unwrap().asBlocks.toNumber()).to.be.eq(limits.sponsoredDataRateLimit);
-    expect(collectionSub.limits.tokenLimit.unwrap().toNumber()).to.be.eq(limits.tokenLimit);
-    expect(collectionSub.limits.sponsorTransferTimeout.unwrap().toNumber()).to.be.eq(limits.sponsorTransferTimeout);
-    expect(collectionSub.limits.sponsorApproveTimeout.unwrap().toNumber()).to.be.eq(limits.sponsorApproveTimeout);
-    expect(collectionSub.limits.ownerCanTransfer.toHuman()).to.be.eq(limits.ownerCanTransfer);
-    expect(collectionSub.limits.ownerCanDestroy.toHuman()).to.be.eq(limits.ownerCanDestroy);
-    expect(collectionSub.limits.transfersEnabled.toHuman()).to.be.eq(limits.transfersEnabled);
+    const data = (await helper.rft.getData(collectionId))!;
+    expect(data.raw.limits.accountTokenOwnershipLimit).to.be.eq(limits.accountTokenOwnershipLimit);
+    expect(data.raw.limits.sponsoredDataSize).to.be.eq(limits.sponsoredDataSize);
+    expect(data.raw.limits.sponsoredDataRateLimit.blocks).to.be.eq(limits.sponsoredDataRateLimit);
+    expect(data.raw.limits.tokenLimit).to.be.eq(limits.tokenLimit);
+    expect(data.raw.limits.sponsorTransferTimeout).to.be.eq(limits.sponsorTransferTimeout);
+    expect(data.raw.limits.sponsorApproveTimeout).to.be.eq(limits.sponsorApproveTimeout);
+    expect(data.raw.limits.ownerCanTransfer).to.be.eq(limits.ownerCanTransfer);
+    expect(data.raw.limits.ownerCanDestroy).to.be.eq(limits.ownerCanDestroy);
+    expect(data.raw.limits.transfersEnabled).to.be.eq(limits.transfersEnabled);
   });
 
-  itWeb3('Collection address exist', async ({api, web3, privateKeyWrapper}) => {
-    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+  itEth('Collection address exist', async ({helper}) => {
+    const owner = await helper.eth.createAccountWithBalance(donor);
     const collectionAddressForNonexistentCollection = '0x17C4E6453CC49AAAAEACA894E6D9683E00112233';
-    const collectionHelpers = evmCollectionHelpers(web3, owner);
-    expect(await collectionHelpers.methods
-      .isCollectionExist(collectionAddressForNonexistentCollection).call())
+    expect(await helper.ethNativeContract.collectionHelpers(collectionAddressForNonexistentCollection)
+      .methods.isCollectionExist(collectionAddressForNonexistentCollection).call())
       .to.be.false;
     
-    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())
+    const {collectionAddress} = await helper.eth.createRefungibleCollection(owner, 'Exister', 'absolutely anything', 'WIWT');
+    expect(await helper.ethNativeContract.collectionHelpers(collectionAddress)
+      .methods.isCollectionExist(collectionAddress).call())
       .to.be.true;
   });
 });
 
 describe('(!negative tests!) Create RFT collection from EVM', () => {
+  let donor: IKeyringPair;
+
   before(async function() {
-    await requirePallets(this, [Pallets.ReFungible]);
+    await usingEthPlaygrounds(async (helper, privateKey) => {
+      requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);
+      donor = privateKey('//Alice');
+    });
   });
 
-  itWeb3('(!negative test!) Create collection (bad lengths)', async ({api, web3, privateKeyWrapper}) => {
-    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
-    const helper = evmCollectionHelpers(web3, owner);
+  itEth('(!negative test!) Create collection (bad lengths)', async ({helper}) => {
+    const owner = await helper.eth.createAccountWithBalance(donor);
+    const collectionHelper = helper.ethNativeContract.collectionHelpers(owner);
     {
-      const MAX_NAME_LENGHT = 64;
-      const collectionName = 'A'.repeat(MAX_NAME_LENGHT + 1);
+      const MAX_NAME_LENGTH = 64;
+      const collectionName = 'A'.repeat(MAX_NAME_LENGTH + 1);
       const description = 'A';
       const tokenPrefix = 'A';
-    
-      await expect(helper.methods
+
+      await expect(collectionHelper.methods
         .createRFTCollection(collectionName, description, tokenPrefix)
-        .call()).to.be.rejectedWith('name is too long. Max length is ' + MAX_NAME_LENGHT);
-      
+        .call()).to.be.rejectedWith('name is too long. Max length is ' + MAX_NAME_LENGTH);
     }
-    {  
-      const MAX_DESCRIPTION_LENGHT = 256;
+    {
+      const MAX_DESCRIPTION_LENGTH = 256;
       const collectionName = 'A';
-      const description = 'A'.repeat(MAX_DESCRIPTION_LENGHT + 1);
+      const description = 'A'.repeat(MAX_DESCRIPTION_LENGTH + 1);
       const tokenPrefix = 'A';
-      await expect(helper.methods
+      await expect(collectionHelper.methods
         .createRFTCollection(collectionName, description, tokenPrefix)
-        .call()).to.be.rejectedWith('description is too long. Max length is ' + MAX_DESCRIPTION_LENGHT);
+        .call()).to.be.rejectedWith('description is too long. Max length is ' + MAX_DESCRIPTION_LENGTH);
     }
-    {  
-      const MAX_TOKEN_PREFIX_LENGHT = 16;
+    {
+      const MAX_TOKEN_PREFIX_LENGTH = 16;
       const collectionName = 'A';
       const description = 'A';
-      const tokenPrefix = 'A'.repeat(MAX_TOKEN_PREFIX_LENGHT + 1);
-      await expect(helper.methods
+      const tokenPrefix = 'A'.repeat(MAX_TOKEN_PREFIX_LENGTH + 1);
+      await expect(collectionHelper.methods
         .createRFTCollection(collectionName, description, tokenPrefix)
-        .call()).to.be.rejectedWith('token_prefix is too long. Max length is ' + MAX_TOKEN_PREFIX_LENGHT);
+        .call()).to.be.rejectedWith('token_prefix is too long. Max length is ' + MAX_TOKEN_PREFIX_LENGTH);
     }
   });
   
-  itWeb3('(!negative test!) Create collection (no funds)', async ({web3}) => {
-    const owner = createEthAccount(web3);
-    const helper = evmCollectionHelpers(web3, owner);
-    const collectionName = 'A';
-    const description = 'A';
-    const tokenPrefix = 'A';
-    
-    await expect(helper.methods
-      .createRFTCollection(collectionName, description, tokenPrefix)
+  itEth('(!negative test!) Create collection (no funds)', async ({helper}) => {
+    const owner = helper.eth.createAccount();
+    const collectionHelper = helper.ethNativeContract.collectionHelpers(owner);
+    await expect(collectionHelper.methods
+      .createRFTCollection('Peasantry', 'absolutely anything', 'TWIW')
       .call()).to.be.rejectedWith('NotSufficientFounds');
   });
 
-  itWeb3('(!negative test!) Check owner', async ({api, web3, privateKeyWrapper}) => {
-    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
-    const notOwner = createEthAccount(web3);
-    const collectionHelpers = evmCollectionHelpers(web3, owner);
-    const result = await collectionHelpers.methods.createRFTCollection('A', 'A', 'A').send();
-    const {collectionIdAddress} = await getCollectionAddressFromResult(api, result);
-    const contractEvmFromNotOwner = evmCollection(web3, notOwner, collectionIdAddress, {type: 'ReFungible'});
+  itEth('(!negative test!) Check owner', async ({helper}) => {
+    const owner = await helper.eth.createAccountWithBalance(donor);
+    const peasant = helper.eth.createAccount();
+    const {collectionAddress} = await helper.eth.createRefungibleCollection(owner, 'Transgressed', 'absolutely anything', 'YVNE');
+    const peasantCollection = helper.ethNativeContract.collection(collectionAddress, 'rft', peasant);
     const EXPECTED_ERROR = 'NoPermission';
     {
-      const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
-      await expect(contractEvmFromNotOwner.methods
+      const sponsor = await helper.eth.createAccountWithBalance(donor);
+      await expect(peasantCollection.methods
         .setCollectionSponsor(sponsor)
         .call()).to.be.rejectedWith(EXPECTED_ERROR);
       
-      const sponsorCollection = evmCollection(web3, sponsor, collectionIdAddress);
+      const sponsorCollection = helper.ethNativeContract.collection(collectionAddress, 'rft', sponsor);
       await expect(sponsorCollection.methods
         .confirmCollectionSponsorship()
         .call()).to.be.rejectedWith('caller is not set as sponsor');
     }
     {
-      await expect(contractEvmFromNotOwner.methods
+      await expect(peasantCollection.methods
         .setCollectionLimit('account_token_ownership_limit', '1000')
         .call()).to.be.rejectedWith(EXPECTED_ERROR);
     }
   });
 
-  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.createRFTCollection('Schema collection', 'A', 'A').send();
-    const {collectionIdAddress} = await getCollectionAddressFromResult(api, result);
-    const collectionEvm = evmCollection(web3, owner, collectionIdAddress, {type: 'ReFungible'});
+  itEth('(!negative test!) Set limits', async ({helper}) => {
+    const owner = await helper.eth.createAccountWithBalance(donor);
+    const {collectionAddress} = await helper.eth.createRefungibleCollection(owner, 'Limits', 'absolutely anything', 'ISNI');
+    const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'rft', owner);
     await expect(collectionEvm.methods
       .setCollectionLimit('badLimit', 'true')
       .call()).to.be.rejectedWith('unknown boolean limit "badLimit"');
modifiedtests/src/eth/crossTransfer.test.tsdiffbeforeafterboth
--- a/tests/src/eth/crossTransfer.test.ts
+++ b/tests/src/eth/crossTransfer.test.ts
@@ -14,7 +14,7 @@
 // You should have received a copy of the GNU General Public License
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
-import {createCollectionExpectSuccess,
+/*import {createCollectionExpectSuccess,
   createFungibleItemExpectSuccess,
   transferExpectSuccess,
   transferFromExpectSuccess,
@@ -23,81 +23,94 @@
 import {collectionIdToAddress,
   createEthAccountWithBalance,
   subToEth,
-  GAS_ARGS, itWeb3} from './util/helpers';
+  GAS_ARGS, itEth} from './util/helpers';
 import fungibleAbi from './fungibleAbi.json';
-import nonFungibleAbi from './nonFungibleAbi.json';
+import nonFungibleAbi from './nonFungibleAbi.json';*/
+import {itEth, usingEthPlaygrounds} from './util/playgrounds';
+import {CrossAccountId} from '../util/playgrounds/unique';
+import {IKeyringPair} from '@polkadot/types/types';
 
 describe('Token transfer between substrate address and EVM address. Fungible', () => {
-  itWeb3('The private key X create a substrate address. Alice sends a token to the corresponding EVM address, and X can send it to Bob in the substrate', async ({privateKeyWrapper}) => {
-    const collection = await createCollectionExpectSuccess({
-      name: 'token name',
-      mode: {type: 'Fungible', decimalPoints: 0},
+  let donor: IKeyringPair;
+  let alice: IKeyringPair;
+  let bob: IKeyringPair;
+  let charlie: IKeyringPair;
+
+  before(async function() {
+    await usingEthPlaygrounds(async (helper, privateKey) => {
+      donor = privateKey('//Alice');
+      [alice, bob, charlie] = await helper.arrange.createAccounts([10n, 10n, 10n], donor);
     });
-    const alice = privateKeyWrapper('//Alice');
-    const bob = privateKeyWrapper('//Bob');
-    const charlie = privateKeyWrapper('//Charlie');
-    await setCollectionLimitsExpectSuccess(alice, collection, {ownerCanTransfer: true});
-    await createFungibleItemExpectSuccess(alice, collection, {Value: 200n}, {Substrate: alice.address});
-    await transferExpectSuccess(collection, 0, alice, {Ethereum: subToEth(charlie.address)} , 200, 'Fungible');
-    await transferFromExpectSuccess(collection, 0, alice, {Ethereum: subToEth(charlie.address)}, charlie, 50, 'Fungible');
-    await transferExpectSuccess(collection, 0, charlie, bob, 50, 'Fungible');
   });
+  
+  itEth('The private key X create a substrate address. Alice sends a token to the corresponding EVM address, and X can send it to Bob in the substrate', async ({helper}) => {  
+    const bobCA = CrossAccountId.fromKeyring(bob);
+    const charlieCA = CrossAccountId.fromKeyring(charlie);
 
-  itWeb3('The private key X create a EVM address. Alice sends a token to the substrate address corresponding to this EVM address, and X can send it to Bob in the EVM', async ({api, web3, privateKeyWrapper}) => {
-    const collection = await createCollectionExpectSuccess({
-      name: 'token name',
-      mode: {type: 'Fungible', decimalPoints: 0},
-    });
-    const alice = privateKeyWrapper('//Alice');
-    const bob = privateKeyWrapper('//Bob');
-    await setCollectionLimitsExpectSuccess(alice, collection, {ownerCanTransfer: true});
-    const bobProxy = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
-    const aliceProxy = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    const collection = await helper.ft.mintCollection(alice);
+    await collection.setLimits(alice, {ownerCanTransfer: true});
 
-    await createFungibleItemExpectSuccess(alice, collection, {Value: 200n}, alice.address);
-    await transferExpectSuccess(collection, 0, alice, {Ethereum: aliceProxy} , 200, 'Fungible');
-    const address = collectionIdToAddress(collection);
-    const contract = new web3.eth.Contract(fungibleAbi as any, address, {from: aliceProxy, ...GAS_ARGS});
+    await collection.mint(alice, 200n);
+    await collection.transfer(alice, charlieCA.toEthereum(), 200n);
+    await collection.transferFrom(alice, charlieCA.toEthereum(), charlieCA, 50n);
+    await collection.transfer(charlie, bobCA, 50n);
+  });
+
+  itEth('The private key X create a EVM address. Alice sends a token to the substrate address corresponding to this EVM address, and X can send it to Bob in the EVM', async ({helper}) => {
+    const aliceProxy = await helper.eth.createAccountWithBalance(donor);
+    const bobProxy = await helper.eth.createAccountWithBalance(donor);
 
+    const collection = await helper.ft.mintCollection(alice);
+    await collection.setLimits(alice, {ownerCanTransfer: true});
+
+    const address = helper.ethAddress.fromCollectionId(collection.collectionId);
+    const contract = helper.ethNativeContract.collection(address, 'ft', aliceProxy);
+
+    await collection.mint(alice, 200n, {Ethereum: aliceProxy});
     await contract.methods.transfer(bobProxy, 50).send({from: aliceProxy});
-    await transferFromExpectSuccess(collection, 0, alice, {Ethereum: bobProxy}, bob, 50, 'Fungible');
-    await transferExpectSuccess(collection, 0, bob, alice, 50, 'Fungible');
+    await collection.transferFrom(alice, {Ethereum: bobProxy}, CrossAccountId.fromKeyring(bob), 50n);
+    await collection.transfer(bob, CrossAccountId.fromKeyring(alice), 50n);
   });
 });
 
 describe('Token transfer between substrate address and EVM address. NFT', () => {
-  itWeb3('The private key X create a substrate address. Alice sends a token to the corresponding EVM address, and X can send it to Bob in the substrate', async ({privateKeyWrapper}) => {
-    const collection = await createCollectionExpectSuccess({
-      name: 'token name',
-      mode: {type: 'NFT'},
+  let donor: IKeyringPair;
+  let alice: IKeyringPair;
+  let bob: IKeyringPair;
+  let charlie: IKeyringPair;
+
+  before(async function() {
+    await usingEthPlaygrounds(async (helper, privateKey) => {
+      donor = privateKey('//Alice');
+      [alice, bob, charlie] = await helper.arrange.createAccounts([10n, 10n, 10n], donor);
     });
-    const alice = privateKeyWrapper('//Alice');
-    const bob = privateKeyWrapper('//Bob');
-    const charlie = privateKeyWrapper('//Charlie');
-    await setCollectionLimitsExpectSuccess(alice, collection, {ownerCanTransfer: true});
-    const tokenId = await createItemExpectSuccess(alice, collection, 'NFT', {Substrate: alice.address});
-    await transferExpectSuccess(collection, tokenId, alice, {Ethereum: subToEth(charlie.address)}, 1, 'NFT');
-    await transferFromExpectSuccess(collection, tokenId, alice, {Ethereum: subToEth(charlie.address)}, charlie, 1, 'NFT');
-    await transferExpectSuccess(collection, tokenId, charlie, bob, 1, 'NFT');
   });
+  
+  itEth('The private key X create a substrate address. Alice sends a token to the corresponding EVM address, and X can send it to Bob in the substrate', async ({helper}) => {
+    const charlieEth = CrossAccountId.fromKeyring(charlie, 'Ethereum');
+    
+    const collection = await helper.nft.mintCollection(alice);
+    await collection.setLimits(alice, {ownerCanTransfer: true});
+    const token = await collection.mintToken(alice);
+    await token.transfer(alice, charlieEth);
+    await token.transferFrom(alice, charlieEth, CrossAccountId.fromKeyring(charlie));
+    await token.transfer(charlie, CrossAccountId.fromKeyring(bob));
+  });
 
-  itWeb3('The private key X create a EVM address. Alice sends a token to the substrate address corresponding to this EVM address, and X can send it to Bob in the EVM', async ({api, web3, privateKeyWrapper}) => {
-    const collection = await createCollectionExpectSuccess({
-      name: 'token name',
-      mode: {type: 'NFT'},
-    });
-    const alice = privateKeyWrapper('//Alice');
-    const bob = privateKeyWrapper('//Bob');
-    const charlie = privateKeyWrapper('//Charlie');
-    await setCollectionLimitsExpectSuccess(alice, collection, {ownerCanTransfer: true});
-    const bobProxy = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
-    const aliceProxy = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
-    const tokenId = await createItemExpectSuccess(alice, collection, 'NFT', {Substrate: alice.address});
-    await transferExpectSuccess(collection, tokenId, alice, {Ethereum: aliceProxy} , 1, 'NFT');
-    const address = collectionIdToAddress(collection);
-    const contract = new web3.eth.Contract(nonFungibleAbi as any, address, {from: aliceProxy, ...GAS_ARGS});
+  itEth('The private key X create a EVM address. Alice sends a token to the substrate address corresponding to this EVM address, and X can send it to Bob in the EVM', async ({helper}) => {
+    const aliceProxy = await helper.eth.createAccountWithBalance(donor);
+    const bobProxy = await helper.eth.createAccountWithBalance(donor);
+
+    const collection = await helper.nft.mintCollection(alice);
+    await collection.setLimits(alice, {ownerCanTransfer: true});
+
+    const address = helper.ethAddress.fromCollectionId(collection.collectionId);
+    const contract = helper.ethNativeContract.collection(address, 'nft', aliceProxy);
+
+    const token = await collection.mintToken(alice);
+    await token.transfer(alice, {Ethereum: aliceProxy});
     await contract.methods.transfer(bobProxy, 1).send({from: aliceProxy});
-    await transferFromExpectSuccess(collection, tokenId, alice, {Ethereum: bobProxy}, bob, 1, 'NFT');
-    await transferExpectSuccess(collection, tokenId, bob, charlie, 1, 'NFT');
+    await token.transferFrom(alice, {Ethereum: bobProxy}, {Substrate: bob.address});
+    await token.transfer(bob, {Substrate: charlie.address});
   });
 });
modifiedtests/src/eth/helpersSmoke.test.tsdiffbeforeafterboth
--- a/tests/src/eth/helpersSmoke.test.ts
+++ b/tests/src/eth/helpersSmoke.test.ts
@@ -14,21 +14,30 @@
 // You should have received a copy of the GNU General Public License
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
-import {expect} from 'chai';
-import {createEthAccountWithBalance, deployFlipper, itWeb3, contractHelpers} from './util/helpers';
+import {expect, itEth, usingEthPlaygrounds} from './util/playgrounds';
+import {IKeyringPair} from '@polkadot/types/types';
 
 describe('Helpers sanity check', () => {
-  itWeb3('Contract owner is recorded', async ({api, web3, privateKeyWrapper}) => {
-    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+  let donor: IKeyringPair;
 
-    const flipper = await deployFlipper(web3, owner);
+  before(async function() {
+    await usingEthPlaygrounds(async (_helper, privateKey) => {
+      donor = privateKey('//Alice');
+    });
+  });
+  
+  itEth('Contract owner is recorded', async ({helper}) => {
+    const owner = await helper.eth.createAccountWithBalance(donor);
 
-    expect(await contractHelpers(web3, owner).methods.contractOwner(flipper.options.address).call()).to.be.equal(owner);
+    const flipper = await helper.eth.deployFlipper(owner);
+
+    expect(await helper.ethNativeContract.contractHelpers(owner).methods.contractOwner(flipper.options.address).call()).to.be.equal(owner);
   });
 
-  itWeb3('Flipper is working', async ({api, web3, privateKeyWrapper}) => {
-    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
-    const flipper = await deployFlipper(web3, owner);
+  itEth('Flipper is working', async ({helper}) => {
+    const owner = await helper.eth.createAccountWithBalance(donor);
+
+    const flipper = await helper.eth.deployFlipper(owner);
 
     expect(await flipper.methods.getValue().call()).to.be.false;
     await flipper.methods.flip().send({from: owner});
modifiedtests/src/eth/migration.test.tsdiffbeforeafterboth
--- a/tests/src/eth/migration.test.ts
+++ b/tests/src/eth/migration.test.ts
@@ -14,12 +14,20 @@
 // You should have received a copy of the GNU General Public License
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
-import {expect} from 'chai';
-import {submitTransactionAsync} from '../substrate/substrate-api';
-import {createEthAccountWithBalance, GAS_ARGS, itWeb3} from './util/helpers';
+import {expect, itEth, usingEthPlaygrounds} from './util/playgrounds';
+import {IKeyringPair} from '@polkadot/types/types';
 
 describe('EVM Migrations', () => {
-  itWeb3('Deploy contract saved state', async ({web3, api, privateKeyWrapper}) => {
+  let superuser: IKeyringPair;
+
+  before(async function() {
+    await usingEthPlaygrounds(async (_helper, privateKey) => {
+      superuser = privateKey('//Alice');
+    });
+  });
+  
+  // todo:playgrounds requires sudo, look into later
+  itEth('Deploy contract saved state', async ({helper}) => {
     /*
       contract StatefulContract {
         uint counter;
@@ -53,13 +61,16 @@
       ['0xedc95719e9a3b28dd8e80877cb5880a9be7de1a13fc8b05e7999683b6b567643', '0x0000000000000000000000000000000000000000000000000000000000000004'],
     ];
 
-    const alice = privateKeyWrapper('//Alice');
-    const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    const caller = await helper.eth.createAccountWithBalance(superuser);
 
-    await submitTransactionAsync(alice, api.tx.sudo.sudo(api.tx.evmMigration.begin(ADDRESS) as any));
-    await submitTransactionAsync(alice, api.tx.sudo.sudo(api.tx.evmMigration.setData(ADDRESS, DATA as any) as any));
-    await submitTransactionAsync(alice, api.tx.sudo.sudo(api.tx.evmMigration.finish(ADDRESS, CODE) as any));
+    const txBegin = helper.constructApiCall('api.tx.evmMigration.begin', [ADDRESS]);
+    const txSetData = helper.constructApiCall('api.tx.evmMigration.setData', [ADDRESS, DATA]);
+    const txFinish = helper.constructApiCall('api.tx.evmMigration.finish', [ADDRESS, CODE]);
+    await expect(helper.executeExtrinsic(superuser, 'api.tx.sudo.sudo', [txBegin])).to.be.fulfilled;
+    await expect(helper.executeExtrinsic(superuser, 'api.tx.sudo.sudo', [txSetData])).to.be.fulfilled;
+    await expect(helper.executeExtrinsic(superuser, 'api.tx.sudo.sudo', [txFinish])).to.be.fulfilled;
 
+    const web3 = helper.getWeb3();
     const contract = new web3.eth.Contract([
       {
         inputs: [],
@@ -87,7 +98,7 @@
         stateMutability: 'view',
         type: 'function',
       },
-    ], ADDRESS, {from: caller, ...GAS_ARGS});
+    ], ADDRESS, {from: caller, gas: helper.eth.DEFAULT_GAS});
 
     expect(await contract.methods.counterValue().call()).to.be.equal('10');
     for (let i = 1; i <= 4; i++) {
modifiedtests/src/eth/nonFungible.test.tsdiffbeforeafterboth
--- a/tests/src/eth/nonFungible.test.ts
+++ b/tests/src/eth/nonFungible.test.ts
@@ -171,70 +171,38 @@
   });
 
   //TODO: CORE-302 add eth methods
-  /* todo:playgrounds skipped test!
-  itWeb3.skip('Can perform mintBulk()', async ({helper}) => {
-    const collection = await createCollectionExpectSuccess({
-      mode: {type: 'NFT'},
-    });
-    const alice = privateKeyWrapper('//Alice');
+  itEth.skip('Can perform mintBulk()', async ({helper}) => {
+    const caller = await helper.eth.createAccountWithBalance(donor);
+    const receiver = helper.eth.createAccount();
 
-    const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
-    const changeAdminTx = api.tx.unique.addCollectionAdmin(collection, {Ethereum: caller});
-    await submitTransactionAsync(alice, changeAdminTx);
-    const receiver = createEthAccount(web3);
+    const collection = await helper.nft.mintCollection(alice);
+    await collection.addAdmin(alice, {Ethereum: caller});
 
-    const address = collectionIdToAddress(collection);
-    const contract = new web3.eth.Contract(nonFungibleAbi as any, address, {from: caller, ...GAS_ARGS});
-
+    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);
+    const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', caller);
     {
+      const bulkSize = 3;
       const nextTokenId = await contract.methods.nextTokenId().call();
       expect(nextTokenId).to.be.equal('1');
       const result = await contract.methods.mintBulkWithTokenURI(
         receiver,
-        [
-          [nextTokenId, 'Test URI 0'],
-          [+nextTokenId + 1, 'Test URI 1'],
-          [+nextTokenId + 2, 'Test URI 2'],
-        ],
+        Array.from({length: bulkSize}, (_, i) => (
+          [+nextTokenId + i, `Test URI ${i}`]
+        )),
       ).send({from: caller});
-      const events = normalizeEvents(result.events);
 
-      expect(events).to.be.deep.equal([
-        {
-          address,
-          event: 'Transfer',
-          args: {
-            from: '0x0000000000000000000000000000000000000000',
-            to: receiver,
-            tokenId: nextTokenId,
-          },
-        },
-        {
-          address,
-          event: 'Transfer',
-          args: {
-            from: '0x0000000000000000000000000000000000000000',
-            to: receiver,
-            tokenId: String(+nextTokenId + 1),
-          },
-        },
-        {
-          address,
-          event: 'Transfer',
-          args: {
-            from: '0x0000000000000000000000000000000000000000',
-            to: receiver,
-            tokenId: String(+nextTokenId + 2),
-          },
-        },
-      ]);
+      const events = result.events.Transfer.sort((a: any, b: any) => +a.returnValues.tokenId - b.returnValues.tokenId);
+      for (let i = 0; i < bulkSize; i++) {
+        const event = events[i];
+        expect(event.address).to.equal(collectionAddress);
+        expect(event.returnValues.from).to.equal('0x0000000000000000000000000000000000000000');
+        expect(event.returnValues.to).to.equal(receiver);
+        expect(event.returnValues.tokenId).to.equal(`${+nextTokenId + i}`);
 
-      expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('Test URI 0');
-      expect(await contract.methods.tokenURI(+nextTokenId + 1).call()).to.be.equal('Test URI 1');
-      expect(await contract.methods.tokenURI(+nextTokenId + 2).call()).to.be.equal('Test URI 2');
+        expect(await contract.methods.tokenURI(+nextTokenId + i).call()).to.be.equal(`Test URI ${i}`);
+      }
     }
   });
-  */
 
   itEth('Can perform burn()', async ({helper}) => {
     const caller = await helper.eth.createAccountWithBalance(donor);
modifiedtests/src/eth/util/playgrounds/unique.dev.tsdiffbeforeafterboth
--- a/tests/src/eth/util/playgrounds/unique.dev.ts
+++ b/tests/src/eth/util/playgrounds/unique.dev.ts
@@ -131,7 +131,7 @@
   }
 }
 
-  
+
 class EthGroup extends EthGroupBase {
   DEFAULT_GAS = 2_500_000;
 
@@ -164,14 +164,14 @@
       true,
     );
   }
-  
+
   async callEVM(signer: TEthereumAccount, contractAddress: string, abi: string) {
     return await this.helper.callRpc('api.rpc.eth.call', [{from: signer, to: contractAddress, data: abi}]);
   }
 
   async createNonfungibleCollection(signer: string, name: string, description: string, tokenPrefix: string): Promise<{collectionId: number, collectionAddress: string}> {
     const collectionHelper = this.helper.ethNativeContract.collectionHelpers(signer);
-        
+
     const result = await collectionHelper.methods.createNonfungibleCollection(name, description, tokenPrefix).send();
 
     const collectionAddress = this.helper.ethAddress.normalizeAddress(result.events.CollectionCreated.returnValues.collectionId);
@@ -182,7 +182,7 @@
 
   async createRefungibleCollection(signer: string, name: string, description: string, tokenPrefix: string): Promise<{collectionId: number, collectionAddress: string}> {
     const collectionHelper = this.helper.ethNativeContract.collectionHelpers(signer);
-        
+
     const result = await collectionHelper.methods.createRFTCollection(name, description, tokenPrefix).send();
 
     const collectionAddress = this.helper.ethAddress.normalizeAddress(result.events.CollectionCreated.returnValues.collectionId);
@@ -246,7 +246,7 @@
     return before - after;
   }
 }  
-  
+
 class EthAddressGroup extends EthGroupBase {
   extractCollectionId(address: string): number {
     if (!(address.length === 42 || address.length === 40)) throw new Error('address wrong format');
modifiedtests/src/util/playgrounds/unique.tsdiffbeforeafterboth
--- a/tests/src/util/playgrounds/unique.ts
+++ b/tests/src/util/playgrounds/unique.ts
@@ -20,8 +20,11 @@
     if (account.Ethereum) this.Ethereum = account.Ethereum;
   }
 
-  static fromKeyring(account: IKeyringPair) {
-    return new CrossAccountId({Substrate: account.address});
+  static fromKeyring(account: IKeyringPair, domain: 'Substrate' | 'Ethereum' = 'Substrate') {
+    switch (domain) {
+      case 'Substrate': return new CrossAccountId({Substrate: account.address});
+      case 'Ethereum': return new CrossAccountId({Substrate: account.address}).toEthereum();
+    }
   }
 
   static fromLowerCaseKeys(address: ICrossAccountIdLower): CrossAccountId {
@@ -40,6 +43,24 @@
     if (this.Substrate) return CrossAccountId.withNormalizedSubstrate(this.Substrate, ss58Format);
     return this;
   }
+
+  static translateSubToEth(address: TSubstrateAccount): TEthereumAccount {
+    return nesting.toChecksumAddress('0x' + Array.from(addressToEvm(address), i => i.toString(16).padStart(2, '0')).join(''));
+  }
+
+  toEthereum(): CrossAccountId {
+    if (this.Substrate) return new CrossAccountId({Ethereum: CrossAccountId.translateSubToEth(this.Substrate)});
+    return this;
+  }
+
+  static translateEthToSub(address: TEthereumAccount, ss58Format?: number): TSubstrateAccount {
+    return evmToAddress(address, ss58Format);
+  }
+
+  toSubstrate(ss58Format?: number): CrossAccountId {
+    if (this.Ethereum) return new CrossAccountId({Substrate: CrossAccountId.translateEthToSub(this.Ethereum, ss58Format)});
+    return this;
+  }
   
   toLowerCase(): CrossAccountId {
     if (this.Substrate) this.Substrate = this.Substrate.toLowerCase();
@@ -2093,9 +2114,8 @@
    * @example normalizeSubstrateToChainFormat("5GrwvaEF5zXb26Fz...") // returns unjKJQJrRd238pkUZZ... for Unique Network
    * @returns address in chain format
    */
-  async normalizeSubstrateToChainFormat(address: TSubstrateAccount): Promise<TSubstrateAccount> {
-    const info = this.helper.chain.getChainProperties();
-    return encodeAddress(decodeAddress(address), info.ss58Format);
+  normalizeSubstrateToChainFormat(address: TSubstrateAccount): TSubstrateAccount {
+    return this.normalizeSubstrate(address, this.helper.chain.getChainProperties().ss58Format);
   }
 
   /**
@@ -2105,10 +2125,8 @@
    * @example ethToSubstrate('0x9F0583DbB855d...')
    * @returns substrate mirror of a provided ethereum address
    */
-  async ethToSubstrate(ethAddress: TEthereumAccount, toChainFormat=false): Promise<TSubstrateAccount> {
-    if(!toChainFormat) return evmToAddress(ethAddress);
-    const info = this.helper.chain.getChainProperties();
-    return evmToAddress(ethAddress, info.ss58Format);
+  ethToSubstrate(ethAddress: TEthereumAccount, toChainFormat=false): TSubstrateAccount {
+    return CrossAccountId.translateEthToSub(ethAddress, toChainFormat ? this.helper.chain.getChainProperties().ss58Format : undefined);
   }
 
   /**
@@ -2118,7 +2136,7 @@
    * @returns ethereum mirror of a provided substrate address
    */
   substrateToEth(subAddress: TSubstrateAccount): TEthereumAccount {
-    return nesting.toChecksumAddress('0x' + Array.from(addressToEvm(subAddress), i => i.toString(16).padStart(2, '0')).join(''));
+    return CrossAccountId.translateSubToEth(subAddress);
   }
 }