From 3592c4714f4dcf8a852d893565336f71fa89b095 Mon Sep 17 00:00:00 2001 From: Ivo Yankov Date: Wed, 6 Jul 2022 12:41:56 +0300 Subject: [PATCH 1/6] refactor: allow running erc20 tests against several contracts Signed-off-by: Ivo Yankov --- .../server/tests/acceptance/erc20.spec.ts | 328 +++++++++--------- .../server/tests/clients/servicesClient.ts | 19 + 2 files changed, 190 insertions(+), 157 deletions(-) diff --git a/packages/server/tests/acceptance/erc20.spec.ts b/packages/server/tests/acceptance/erc20.spec.ts index 2756ee7d69..f761f8e7c1 100644 --- a/packages/server/tests/acceptance/erc20.spec.ts +++ b/packages/server/tests/acceptance/erc20.spec.ts @@ -25,29 +25,31 @@ chai.use(solidity); import { AliasAccount } from '../clients/servicesClient'; import { ethers, BigNumber } from 'ethers'; - import ERC20MockJson from '../contracts/ERC20Mock.json'; -import ERC20DecimalsMockJson from '../contracts/ERC20DecimalsMock.json'; - import Assertions from '../helpers/assertions'; import {Utils} from '../helpers/utils'; -describe('ERC20 Acceptance Tests', function () { +describe('ERC20 Acceptance Tests', async function () { this.timeout(240 * 1000); // 240 seconds const {servicesNode, relay} = global; // cached entities const accounts: AliasAccount[] = []; - let contract; let initialHolder; let recipient; let anotherAccount; + const contracts:[any] = []; const name = Utils.randomString(10); const symbol = Utils.randomString(5); const initialSupply = BigNumber.from(10000); + const testTitles = [ + 'ERC20 Contract', + 'HTS token' + ]; + before(async () => { accounts[0] = await servicesNode.createAliasAccount(30, relay.provider); accounts[1] = await servicesNode.createAliasAccount(10, relay.provider); @@ -57,219 +59,219 @@ describe('ERC20 Acceptance Tests', function () { recipient = accounts[1].address; anotherAccount = accounts[2].address; - contract = await deployErc20([name, symbol, initialHolder, initialSupply], ERC20MockJson); + contracts.push(await deployErc20([name, symbol, initialHolder, initialSupply], ERC20MockJson)); + contracts.push(await deployErc20([name, symbol, initialHolder, initialSupply], ERC20MockJson)); + // contracts.push(await createHTS(name, symbol, initialHolder, initialSupply, initialHolder.publicKey)); }); - describe('Mocked ERC20 Contract', function () { - it('has a name', async function () { - expect(await contract.name()).to.equal(name); - }); - - it('has a symbol', async function () { - expect(await contract.symbol()).to.equal(symbol); - }); - - it('has 18 decimals', async function () { - expect(await contract.decimals()).to.be.equal(18); - }); - - it('Relay can execute "eth_getCode" for ERC20 contract with evmAddress', async function () { - const res = await relay.call('eth_getCode', [contract.address]); - expect(res).to.eq(ERC20MockJson.deployedBytecode); - }); + for (const i in testTitles) { + describe(testTitles[i], async function () { + let contract; - describe('set decimals', function () { - const decimals = 6; + before(async function () { + contract = contracts[i]; + }); - it('can set decimals during construction', async function () { + it('has a name', async function () { + expect(await contract.name()).to.equal(name); + }); - const decimalsContract = await deployErc20([name, symbol, decimals], ERC20DecimalsMockJson); - expect(await decimalsContract.decimals()).to.be.equal(decimals); + it('has a symbol', async function () { + expect(await contract.symbol()).to.equal(symbol); }); - }); - describe('should behave like erc20', function() { - describe('total supply', function () { - it('returns the total amount of tokens', async function () { - const supply = await contract.totalSupply(); - expect(supply.toString()).to.be.equal(initialSupply.toString()); - }); + it('has 18 decimals', async function () { + expect(await contract.decimals()).to.be.equal(18); }); - describe('balanceOf', function () { - describe('when the requested account has no tokens', function () { - it('returns zero', async function () { - const otherBalance = await contract.balanceOf(anotherAccount); - expect(otherBalance.toString()).to.be.equal('0'); - }); - }); + it('Relay can execute "eth_getCode" for ERC20 contract with evmAddress', async function () { + const res = await relay.call('eth_getCode', [contract.address]); + expect(res).to.eq(ERC20MockJson.deployedBytecode); + }); - describe('when the requested account has some tokens', function () { + describe('should behave like erc20', function() { + describe('total supply', function () { it('returns the total amount of tokens', async function () { - const balance = await contract.balanceOf(initialHolder); - expect(balance.toString()).to.be.equal(initialSupply.toString()); + const supply = await contract.totalSupply(); + expect(supply.toString()).to.be.equal(initialSupply.toString()); }); }); - }); - describe('transfer from', function () { - let spender; - let spenderWallet; + describe('balanceOf', function () { + describe('when the requested account has no tokens', function () { + it('returns zero', async function () { + const otherBalance = await contract.balanceOf(anotherAccount); + expect(otherBalance.toString()).to.be.equal('0'); + }); + }); - before(async function () { - spender = accounts[1].address; - spenderWallet = accounts[1].wallet; + describe('when the requested account has some tokens', function () { + it('returns the total amount of tokens', async function () { + const balance = await contract.balanceOf(initialHolder); + expect(balance.toString()).to.be.equal(initialSupply.toString()); + }); + }); }); - describe('when the token owner is not the zero address', function () { - let tokenOwner, tokenOwnerWallet; + describe('transfer from', function () { + let spender; + let spenderWallet; + before(async function () { - tokenOwner = initialHolder; - tokenOwnerWallet = accounts[0].wallet; + spender = accounts[1].address; + spenderWallet = accounts[1].wallet; }); - describe('when the recipient is not the zero address', function () { - let to, toWallet; + describe('when the token owner is not the zero address', function () { + let tokenOwner, tokenOwnerWallet; before(async function () { - to = anotherAccount; - toWallet = accounts[2].wallet; + tokenOwner = initialHolder; + tokenOwnerWallet = accounts[0].wallet; }); - describe('when the spender has enough allowance', function () { + describe('when the recipient is not the zero address', function () { + let to, toWallet; before(async function () { - await contract.approve(spender, initialSupply, { from: initialHolder }); + to = anotherAccount; + toWallet = accounts[2].wallet; }); - describe('when the token owner has enough balance', function () { - let amount, tx, receipt; + describe('when the spender has enough allowance', function () { before(async function () { - amount = initialSupply; - tx = await contract.connect(spenderWallet).transferFrom(tokenOwner, to, amount); + await contract.approve(spender, initialSupply, { from: initialHolder }); }); - it('transfers the requested amount', async function () { - const ownerBalance = await contract.balanceOf(tokenOwner); - const toBalance = await contract.balanceOf(to); - expect(ownerBalance.toString()).to.be.equal('0'); - expect(toBalance.toString()).to.be.equal(amount.toString()); + describe('when the token owner has enough balance', function () { + let amount, tx, receipt; + before(async function () { + amount = initialSupply; + tx = await contract.connect(spenderWallet).transferFrom(tokenOwner, to, amount); + }); + + it('transfers the requested amount', async function () { + const ownerBalance = await contract.balanceOf(tokenOwner); + const toBalance = await contract.balanceOf(to); + expect(ownerBalance.toString()).to.be.equal('0'); + expect(toBalance.toString()).to.be.equal(amount.toString()); + }); + + it('decreases the spender allowance', async function () { + const allowance = await contract.allowance(tokenOwner, spender); + expect(allowance.toString()).to.be.equal('0'); + }); + + it('emits a transfer event', async function () { + await expect(tx) + .to.emit(contract, 'Transfer') + .withArgs(tokenOwnerWallet.address, toWallet.address, amount); + }); + + it('emits an approval event', async function () { + await expect(tx) + .to.emit(contract, 'Approval') + .withArgs(tokenOwnerWallet.address, spenderWallet.address, await contract.allowance(tokenOwner, spender)); + }); }); - it('decreases the spender allowance', async function () { - const allowance = await contract.allowance(tokenOwner, spender); - expect(allowance.toString()).to.be.equal('0'); - }); + describe('when the token owner does not have enough balance', function () { + let amount; - it('emits a transfer event', async function () { - await expect(tx) - .to.emit(contract, 'Transfer') - .withArgs(tokenOwnerWallet.address, toWallet.address, amount); - }); + beforeEach('reducing balance', async function () { + amount = initialSupply; + await contract.transfer(to, 1, { from: tokenOwner }); + }); - it('emits an approval event', async function () { - await expect(tx) - .to.emit(contract, 'Approval') - .withArgs(tokenOwnerWallet.address, spenderWallet.address, await contract.allowance(tokenOwner, spender)); + it('reverts', async function () { + await expectRevert( + contract.connect(spenderWallet).transferFrom(tokenOwner, to, amount), + 'CALL_EXCEPTION' + ); + }); }); }); - describe('when the token owner does not have enough balance', function () { - let amount; + describe('when the spender does not have enough allowance', function () { + let allowance; - beforeEach('reducing balance', async function () { - amount = initialSupply; - await contract.transfer(to, 1, { from: tokenOwner }); + before(async function () { + allowance = initialSupply.sub(1); }); - it('reverts', async function () { - await expectRevert( - contract.connect(spenderWallet).transferFrom(tokenOwner, to, amount), - 'CALL_EXCEPTION' - ); + beforeEach(async function () { + await contract.approve(spender, allowance, { from: tokenOwner }); }); - }); - }); - - describe('when the spender does not have enough allowance', function () { - let allowance; - - before(async function () { - allowance = initialSupply.sub(1); - }); - - beforeEach(async function () { - await contract.approve(spender, allowance, { from: tokenOwner }); - }); - describe('when the token owner has enough balance', function () { - let amount; - before(async function () { - amount = initialSupply; + describe('when the token owner has enough balance', function () { + let amount; + before(async function () { + amount = initialSupply; + }); + + it('reverts', async function () { + await expectRevert( + contract.connect(spenderWallet).transferFrom(tokenOwner, to, amount), + `CALL_EXCEPTION`, + ); + }); }); - it('reverts', async function () { - await expectRevert( - contract.connect(spenderWallet).transferFrom(tokenOwner, to, amount), - `CALL_EXCEPTION`, - ); + describe('when the token owner does not have enough balance', function () { + let amount; + before(async function () { + amount = allowance; + }); + + beforeEach('reducing balance', async function () { + await contract.transfer(to, 2, { from: tokenOwner }); + }); + + it('reverts', async function () { + await expectRevert( + contract.connect(spenderWallet).transferFrom(tokenOwner, to, amount), + `CALL_EXCEPTION`, + ); + }); }); }); - describe('when the token owner does not have enough balance', function () { - let amount; - before(async function () { - amount = allowance; + describe('when the spender has unlimited allowance', function () { + beforeEach(async function () { + await contract.connect(tokenOwnerWallet).approve(spender, ethers.constants.MaxUint256); }); - beforeEach('reducing balance', async function () { - await contract.transfer(to, 2, { from: tokenOwner }); + it('does not decrease the spender allowance', async function () { + await contract.connect(spenderWallet).transferFrom(tokenOwner, to, 1); + const allowance = await contract.allowance(tokenOwner, spender); + expect(allowance.toString()).to.be.equal(ethers.constants.MaxUint256.toString()); }); - it('reverts', async function () { - await expectRevert( - contract.connect(spenderWallet).transferFrom(tokenOwner, to, amount), - `CALL_EXCEPTION`, - ); + it('does not emit an approval event', async function () { + await expect(await contract.connect(spenderWallet).transferFrom(tokenOwner, to, 1)) + .to.not.emit(contract, 'Approval'); }); }); }); - describe('when the spender has unlimited allowance', function () { - beforeEach(async function () { - await contract.connect(tokenOwnerWallet).approve(spender, ethers.constants.MaxUint256); - }); + describe('when the recipient is the zero address', function () { + let amount, to, tokenOwnerWallet; - it('does not decrease the spender allowance', async function () { - await contract.connect(spenderWallet).transferFrom(tokenOwner, to, 1); - const allowance = await contract.allowance(tokenOwner, spender); - expect(allowance.toString()).to.be.equal(ethers.constants.MaxUint256.toString()); + beforeEach(async function () { + amount = initialSupply; + to = ethers.constants.AddressZero; + tokenOwnerWallet = accounts[0].wallet; + await contract.connect(tokenOwnerWallet).approve(spender, amount); }); - it('does not emit an approval event', async function () { - await expect(await contract.connect(spenderWallet).transferFrom(tokenOwner, to, 1)) - .to.not.emit(contract, 'Approval'); + it('reverts', async function () { + await expectRevert(contract.connect(spenderWallet).transferFrom(tokenOwner, to, amount), + `CALL_EXCEPTION`); }); }); }); - - describe('when the recipient is the zero address', function () { - let amount, to, tokenOwnerWallet; - - beforeEach(async function () { - amount = initialSupply; - to = ethers.constants.AddressZero; - tokenOwnerWallet = accounts[0].wallet; - await contract.connect(tokenOwnerWallet).approve(spender, amount); - }); - - it('reverts', async function () { - await expectRevert(contract.connect(spenderWallet).transferFrom(tokenOwner, to, amount), - `CALL_EXCEPTION`); - }); - }); }); }); }); - }); + } const expectRevert = async (promise, code) => { const tx = await promise; @@ -283,7 +285,7 @@ describe('ERC20 Acceptance Tests', function () { } }; - const deployErc20 = async (constructorArgs:any[] = [], contractJson) => { + async function deployErc20(constructorArgs:any[] = [], contractJson) { const factory = new ethers.ContractFactory(contractJson.abi, contractJson.bytecode, accounts[0].wallet); let contract = await factory.deploy(...constructorArgs); await contract.deployed(); @@ -293,4 +295,16 @@ describe('ERC20 Acceptance Tests', function () { contract = new ethers.Contract(receipt.to, contractJson.abi, accounts[0].wallet); return contract; }; -}); + + const createHTS = async(tokenName, symbol, treasuryAccountId, initialSupply, adminPublicKey) => { + const tx = await servicesNode.createHTS({ + tokenName, + symbol, + treasuryAccountId, + initialSupply, + adminPublicKey + }); + + return tx; + }; +}); \ No newline at end of file diff --git a/packages/server/tests/clients/servicesClient.ts b/packages/server/tests/clients/servicesClient.ts index d7a5e9e422..64b9c9fcd3 100644 --- a/packages/server/tests/clients/servicesClient.ts +++ b/packages/server/tests/clients/servicesClient.ts @@ -275,6 +275,25 @@ export default class ServicesClient { const receipt = await response.getReceipt(this.client); this.logger.info(`File ${fileId} updated with status: ${receipt.status.toString()}`); } + + async createHTS( args = { + tokenName: 'Default Name', + symbol: 'HTS', + treasuryAccountId: '0.0.2', + initialSupply: 5000, + adminPublicKey: this.DEFAULT_KEY + }) { + const {} = args; + const transaction = await new TokenCreateTransaction() + .setTokenName(args.tokenName) + .setTokenSymbol(args.symbol) + .setTreasuryAccountId(args.treasuryAccountId) + .setInitialSupply(args.initialSupply) + .setAdminKey(args.adminPublicKey) + .freezeWith(this.client); + + return transaction; + } } export class AliasAccount { From 999d2cec2fa594e566c853feeb99364b983ac0e3 Mon Sep 17 00:00:00 2001 From: Ivo Yankov Date: Wed, 6 Jul 2022 16:56:55 +0300 Subject: [PATCH 2/6] test: run erc20 tests against hts Signed-off-by: Ivo Yankov --- .../server/tests/acceptance/erc20.spec.ts | 14 ++++--- .../server/tests/clients/servicesClient.ts | 38 +++++++++++++++---- 2 files changed, 40 insertions(+), 12 deletions(-) diff --git a/packages/server/tests/acceptance/erc20.spec.ts b/packages/server/tests/acceptance/erc20.spec.ts index f761f8e7c1..6c057ffed0 100644 --- a/packages/server/tests/acceptance/erc20.spec.ts +++ b/packages/server/tests/acceptance/erc20.spec.ts @@ -60,8 +60,7 @@ describe('ERC20 Acceptance Tests', async function () { anotherAccount = accounts[2].address; contracts.push(await deployErc20([name, symbol, initialHolder, initialSupply], ERC20MockJson)); - contracts.push(await deployErc20([name, symbol, initialHolder, initialSupply], ERC20MockJson)); - // contracts.push(await createHTS(name, symbol, initialHolder, initialSupply, initialHolder.publicKey)); + contracts.push(await createHTS(name, symbol, accounts[0].accountId.toString(), initialSupply, accounts[0].privateKey.publicKey, ERC20MockJson.abi)); }); for (const i in testTitles) { @@ -296,8 +295,8 @@ describe('ERC20 Acceptance Tests', async function () { return contract; }; - const createHTS = async(tokenName, symbol, treasuryAccountId, initialSupply, adminPublicKey) => { - const tx = await servicesNode.createHTS({ + const createHTS = async(tokenName, symbol, treasuryAccountId, initialSupply, adminPublicKey, abi) => { + const hts = await servicesNode.createHTS({ tokenName, symbol, treasuryAccountId, @@ -305,6 +304,11 @@ describe('ERC20 Acceptance Tests', async function () { adminPublicKey }); - return tx; + await servicesNode.associateHTSToken(accounts[0].accountId, hts.tokenId, accounts[0].privateKey); + await servicesNode.associateHTSToken(accounts[1].accountId, hts.tokenId, accounts[1].privateKey); + await servicesNode.associateHTSToken(accounts[2].accountId, hts.tokenId, accounts[2].privateKey); + + const evmAddress = Utils.idToEvmAddress(hts.tokenId.toString()); + return new ethers.Contract(evmAddress, abi, accounts[0].wallet); }; }); \ No newline at end of file diff --git a/packages/server/tests/clients/servicesClient.ts b/packages/server/tests/clients/servicesClient.ts index 64b9c9fcd3..8aaa2415ab 100644 --- a/packages/server/tests/clients/servicesClient.ts +++ b/packages/server/tests/clients/servicesClient.ts @@ -38,6 +38,7 @@ import { TransferTransaction, ContractCreateFlow, FileUpdateTransaction, + TransactionId } from '@hashgraph/sdk'; import { Logger } from 'pino'; import { ethers } from 'ethers'; @@ -241,6 +242,7 @@ export default class ServicesClient { accountInfo.accountId, accountInfo.contractAccountId, servicesClient, + privateKey, wallet ); }; @@ -281,19 +283,39 @@ export default class ServicesClient { symbol: 'HTS', treasuryAccountId: '0.0.2', initialSupply: 5000, - adminPublicKey: this.DEFAULT_KEY + adminPublicKey: this.DEFAULT_KEY, + accounts: [] }) { const {} = args; - const transaction = await new TokenCreateTransaction() + + const expiration = new Date(); + expiration.setDate(expiration.getDate() + 30); + const tokenCreate = await (await new TokenCreateTransaction() .setTokenName(args.tokenName) .setTokenSymbol(args.symbol) - .setTreasuryAccountId(args.treasuryAccountId) + .setExpirationTime(expiration) + .setDecimals(18) + .setTreasuryAccountId(this.client.operatorAccountId) .setInitialSupply(args.initialSupply) - .setAdminKey(args.adminPublicKey) - .freezeWith(this.client); + .setTransactionId(TransactionId.generate(this.client.operatorAccountId)) + .setNodeAccountIds([this.client._network.getNodeAccountIdsForExecute()[0]])) + .execute(this.client); - return transaction; + const receipt = await tokenCreate.getReceipt(this.client); + return receipt; } + + async associateHTSToken(accountId, tokenId, privateKey) { + const tokenAssociate = await (await new TokenAssociateTransaction() + .setAccountId(accountId) + .setTokenIds([tokenId]) + .freezeWith(this.client) + .sign(privateKey)) + .execute(this.client); + + await tokenAssociate.getReceipt(this.client); + this.logger.info(`HTS Token ${tokenId} associated to : ${accountId}`); + }; } export class AliasAccount { @@ -302,13 +324,15 @@ export class AliasAccount { public readonly accountId: AccountId; public readonly address: string; public readonly client: ServicesClient; + public readonly privateKey: PrivateKey; public readonly wallet: ethers.Wallet; - constructor(_alias, _accountId, _address, _client, _wallet) { + constructor(_alias, _accountId, _address, _client, _privateKey, _wallet) { this.alias = _alias; this.accountId = _accountId; this.address = _address; this.client = _client; + this.privateKey = _privateKey; this.wallet = _wallet; } From f40c456847b638f9d74cb043863ed2f003384a21 Mon Sep 17 00:00:00 2001 From: Ivo Yankov Date: Fri, 8 Jul 2022 10:33:35 +0300 Subject: [PATCH 3/6] wip: hts tests Signed-off-by: Ivo Yankov --- .../server/tests/acceptance/erc20.spec.ts | 56 +++++++++++++++---- .../server/tests/acceptance/index.spec.ts | 4 +- .../server/tests/clients/servicesClient.ts | 30 +++++++++- 3 files changed, 75 insertions(+), 15 deletions(-) diff --git a/packages/server/tests/acceptance/erc20.spec.ts b/packages/server/tests/acceptance/erc20.spec.ts index 6c057ffed0..c591f5bfbd 100644 --- a/packages/server/tests/acceptance/erc20.spec.ts +++ b/packages/server/tests/acceptance/erc20.spec.ts @@ -32,7 +32,7 @@ import {Utils} from '../helpers/utils'; describe('ERC20 Acceptance Tests', async function () { this.timeout(240 * 1000); // 240 seconds - const {servicesNode, relay} = global; + const {servicesNode, relay, logger} = global; // cached entities const accounts: AliasAccount[] = []; @@ -46,8 +46,8 @@ describe('ERC20 Acceptance Tests', async function () { const initialSupply = BigNumber.from(10000); const testTitles = [ - 'ERC20 Contract', - 'HTS token' + // {testName: 'ERC20 Contract', expectedBytecode: ERC20MockJson.deployedBytecode}, + {testName: 'HTS token', expectedBytecode: '0x0'} ]; before(async () => { @@ -59,12 +59,12 @@ describe('ERC20 Acceptance Tests', async function () { recipient = accounts[1].address; anotherAccount = accounts[2].address; - contracts.push(await deployErc20([name, symbol, initialHolder, initialSupply], ERC20MockJson)); - contracts.push(await createHTS(name, symbol, accounts[0].accountId.toString(), initialSupply, accounts[0].privateKey.publicKey, ERC20MockJson.abi)); + // contracts.push(await deployErc20([name, symbol, initialHolder, initialSupply], ERC20MockJson)); + contracts.push(await createHTS(name, symbol, accounts[0].accountId.toString(), 10000, accounts[0].privateKey.publicKey, ERC20MockJson.abi)); }); for (const i in testTitles) { - describe(testTitles[i], async function () { + describe(testTitles[i].testName, async function () { let contract; before(async function () { @@ -85,7 +85,7 @@ describe('ERC20 Acceptance Tests', async function () { it('Relay can execute "eth_getCode" for ERC20 contract with evmAddress', async function () { const res = await relay.call('eth_getCode', [contract.address]); - expect(res).to.eq(ERC20MockJson.deployedBytecode); + expect(res).to.eq(testTitles[i].expectedBytecode); }); describe('should behave like erc20', function() { @@ -124,27 +124,32 @@ describe('ERC20 Acceptance Tests', async function () { describe('when the token owner is not the zero address', function () { let tokenOwner, tokenOwnerWallet; before(async function () { - tokenOwner = initialHolder; + tokenOwner = accounts[0].address; tokenOwnerWallet = accounts[0].wallet; }); describe('when the recipient is not the zero address', function () { let to, toWallet; before(async function () { - to = anotherAccount; + to = accounts[2].address; toWallet = accounts[2].wallet; }); describe('when the spender has enough allowance', function () { before(async function () { - await contract.approve(spender, initialSupply, { from: initialHolder }); + await contract.approve(spender, initialSupply, { from: tokenOwner }); }); describe('when the token owner has enough balance', function () { - let amount, tx, receipt; + let amount, tx; before(async function () { amount = initialSupply; + const ownerBalance = await contract.balanceOf(tokenOwner); + const toBalance = await contract.balanceOf(to); + expect(ownerBalance.toString()).to.be.equal(amount.toString()); + expect(toBalance.toString()).to.be.equal('0'); tx = await contract.connect(spenderWallet).transferFrom(tokenOwner, to, amount); + logger.debug(tx); }); it('transfers the requested amount', async function () { @@ -308,7 +313,34 @@ describe('ERC20 Acceptance Tests', async function () { await servicesNode.associateHTSToken(accounts[1].accountId, hts.tokenId, accounts[1].privateKey); await servicesNode.associateHTSToken(accounts[2].accountId, hts.tokenId, accounts[2].privateKey); + await servicesNode.approveHTSToken(accounts[0].accountId, hts.tokenId); + await servicesNode.approveHTSToken(accounts[1].accountId, hts.tokenId); + await servicesNode.approveHTSToken(accounts[2].accountId, hts.tokenId); + + await servicesNode.transferHTSToken(accounts[0].accountId, hts.tokenId, 10000); + // await servicesNode.transferHTSToken(accounts[1].accountId, hts.tokenId, 10000, accounts[0].accountId); + // await servicesNode.transferHTSToken(accounts[2].accountId, hts.tokenId, 10000, accounts[1].accountId); + // await servicesNode.transferHTSToken(accounts[0].accountId, hts.tokenId, 10000, accounts[2].accountId); + // + // + // await servicesNode.transferHTSToken(accounts[1].accountId, hts.tokenId, 10000); + // await servicesNode.transferHTSToken(servicesNode.client.operatorAccountId, hts.tokenId, 10000, accounts[1].accountId); + // + // await servicesNode.transferHTSToken(accounts[2].accountId, hts.tokenId, 10000); + // await servicesNode.transferHTSToken(servicesNode.client.operatorAccountId, hts.tokenId, 10000, accounts[2].accountId); + const evmAddress = Utils.idToEvmAddress(hts.tokenId.toString()); - return new ethers.Contract(evmAddress, abi, accounts[0].wallet); + const contract = new ethers.Contract(evmAddress, abi, accounts[0].wallet); + + const balance0 = await contract.balanceOf(accounts[0].address); + const balance1 = await contract.balanceOf(accounts[1].address); + const balance2 = await contract.balanceOf(accounts[2].address); + + logger.info(`balance0: ${balance0}`); + logger.info(`balance1: ${balance1}`); + logger.info(`balance2: ${balance2}`); + + + return contract; }; }); \ No newline at end of file diff --git a/packages/server/tests/acceptance/index.spec.ts b/packages/server/tests/acceptance/index.spec.ts index 1e0c47fedb..f25dd150f6 100644 --- a/packages/server/tests/acceptance/index.spec.ts +++ b/packages/server/tests/acceptance/index.spec.ts @@ -95,7 +95,7 @@ describe('RPC Server Acceptance Tests', function () { if (USE_LOCAL_NODE === 'true') { // stop local-node logger.info('Shutdown local node'); - shell.exec('npx hedera-local stop'); + // shell.exec('npx hedera-local stop'); } // stop relay @@ -108,7 +108,7 @@ describe('RPC Server Acceptance Tests', function () { describe("Acceptance tests", async () => { fs.readdirSync(path.resolve(__dirname, './')) .forEach(test => { - if (test !== 'index.spec.ts' && test.endsWith('.spec.ts')) { + if (test !== 'index.spec.ts' && test.endsWith('erc20.spec.ts')) { require(`./${test}`); } }); diff --git a/packages/server/tests/clients/servicesClient.ts b/packages/server/tests/clients/servicesClient.ts index 8aaa2415ab..751a0be790 100644 --- a/packages/server/tests/clients/servicesClient.ts +++ b/packages/server/tests/clients/servicesClient.ts @@ -38,7 +38,8 @@ import { TransferTransaction, ContractCreateFlow, FileUpdateTransaction, - TransactionId + TransactionId, + AccountAllowanceApproveTransaction } from '@hashgraph/sdk'; import { Logger } from 'pino'; import { ethers } from 'ethers'; @@ -316,6 +317,33 @@ export default class ServicesClient { await tokenAssociate.getReceipt(this.client); this.logger.info(`HTS Token ${tokenId} associated to : ${accountId}`); }; + + async approveHTSToken(spenderId, tokenId) { + const amount = 10000; + const tokenApprove = await (new AccountAllowanceApproveTransaction() + .addTokenAllowance(tokenId, spenderId, amount)) + .execute(this.client); + + await tokenApprove.getReceipt(this.client); + this.logger.info(`${amount} of HTS Token ${tokenId} can be spent by ${spenderId}`); + }; + + async transferHTSToken(accountId, tokenId, amount, fromId = this.client.operatorAccountId) { + try { + const tokenTransfer = await (await new TransferTransaction() + .addTokenTransfer(tokenId, fromId, -amount) + .addTokenTransfer(tokenId, accountId, amount)) + .execute(this.client); + + const rec = await tokenTransfer.getReceipt(this.client); + this.logger.info(`${amount} of HTS Token ${tokenId} can be spent by ${accountId}`); + this.logger.debug(rec); + } + catch(e) { + this.logger.debug(e); + } + }; + } export class AliasAccount { From 2ca2ab37b52d8f250e0e23fb87c01a614a09927b Mon Sep 17 00:00:00 2001 From: Ivo Yankov Date: Mon, 11 Jul 2022 11:52:42 +0300 Subject: [PATCH 4/6] test: disable allowance tests for hts Signed-off-by: Ivo Yankov --- .../server/tests/acceptance/erc20.spec.ts | 249 ++++++++++-------- .../server/tests/acceptance/index.spec.ts | 4 +- 2 files changed, 135 insertions(+), 118 deletions(-) diff --git a/packages/server/tests/acceptance/erc20.spec.ts b/packages/server/tests/acceptance/erc20.spec.ts index c591f5bfbd..b0f719963d 100644 --- a/packages/server/tests/acceptance/erc20.spec.ts +++ b/packages/server/tests/acceptance/erc20.spec.ts @@ -45,9 +45,12 @@ describe('ERC20 Acceptance Tests', async function () { const symbol = Utils.randomString(5); const initialSupply = BigNumber.from(10000); + const ERC20 = 'ERC20 Contract'; + const HTS = 'HTS token'; + const testTitles = [ - // {testName: 'ERC20 Contract', expectedBytecode: ERC20MockJson.deployedBytecode}, - {testName: 'HTS token', expectedBytecode: '0x0'} + {testName: ERC20, expectedBytecode: ERC20MockJson.deployedBytecode}, + {testName: HTS, expectedBytecode: '0x0'} ]; before(async () => { @@ -59,7 +62,7 @@ describe('ERC20 Acceptance Tests', async function () { recipient = accounts[1].address; anotherAccount = accounts[2].address; - // contracts.push(await deployErc20([name, symbol, initialHolder, initialSupply], ERC20MockJson)); + contracts.push(await deployErc20([name, symbol, initialHolder, initialSupply], ERC20MockJson)); contracts.push(await createHTS(name, symbol, accounts[0].accountId.toString(), 10000, accounts[0].privateKey.publicKey, ERC20MockJson.abi)); }); @@ -135,125 +138,160 @@ describe('ERC20 Acceptance Tests', async function () { toWallet = accounts[2].wallet; }); - describe('when the spender has enough allowance', function () { + describe('when the spender has enough tokens', function () { + let amount, tx; before(async function () { - await contract.approve(spender, initialSupply, { from: tokenOwner }); + amount = initialSupply; }); - describe('when the token owner has enough balance', function () { - let amount, tx; - before(async function () { - amount = initialSupply; - const ownerBalance = await contract.balanceOf(tokenOwner); - const toBalance = await contract.balanceOf(to); - expect(ownerBalance.toString()).to.be.equal(amount.toString()); - expect(toBalance.toString()).to.be.equal('0'); - tx = await contract.connect(spenderWallet).transferFrom(tokenOwner, to, amount); - logger.debug(tx); - }); - - it('transfers the requested amount', async function () { - const ownerBalance = await contract.balanceOf(tokenOwner); - const toBalance = await contract.balanceOf(to); - expect(ownerBalance.toString()).to.be.equal('0'); - expect(toBalance.toString()).to.be.equal(amount.toString()); - }); + it ('contract owner transfers tokens', async function () { + tx = await contract.connect(tokenOwnerWallet).transfer(to, amount); + const ownerBalance = await contract.balanceOf(tokenOwner); + const toBalance = await contract.balanceOf(to); + expect(ownerBalance.toString()).to.be.equal('0'); + expect(toBalance.toString()).to.be.equal(amount.toString()); - it('decreases the spender allowance', async function () { - const allowance = await contract.allowance(tokenOwner, spender); - expect(allowance.toString()).to.be.equal('0'); - }); + }); + // FIXME + if (testTitles[i].testName !== HTS) { it('emits a transfer event', async function () { await expect(tx) .to.emit(contract, 'Transfer') .withArgs(tokenOwnerWallet.address, toWallet.address, amount); }); - - it('emits an approval event', async function () { - await expect(tx) - .to.emit(contract, 'Approval') - .withArgs(tokenOwnerWallet.address, spenderWallet.address, await contract.allowance(tokenOwner, spender)); - }); + } + + it ('other account transfers tokens back to owner', async function () { + tx = await contract.connect(toWallet).transfer(tokenOwner, amount); + const ownerBalance = await contract.balanceOf(tokenOwner); + const toBalance = await contract.balanceOf(to); + expect(ownerBalance.toString()).to.be.equal(amount.toString()); + expect(toBalance.toString()).to.be.equal('0'); }); + }); - describe('when the token owner does not have enough balance', function () { - let amount; - - beforeEach('reducing balance', async function () { - amount = initialSupply; - await contract.transfer(to, 1, { from: tokenOwner }); + // FIXME + if (testTitles[i].testName !== HTS) { + describe('when the spender has enough allowance', function () { + before(async function () { + await contract.connect(tokenOwnerWallet).approve(spender, initialSupply); }); - it('reverts', async function () { - await expectRevert( - contract.connect(spenderWallet).transferFrom(tokenOwner, to, amount), - 'CALL_EXCEPTION' - ); + describe('when the token owner has enough balance', function () { + let amount, tx; + before(async function () { + amount = initialSupply; + const ownerBalance = await contract.balanceOf(tokenOwner); + const toBalance = await contract.balanceOf(to); + expect(ownerBalance.toString()).to.be.equal(amount.toString()); + expect(toBalance.toString()).to.be.equal('0'); + }); + + it('transfers the requested amount', async function () { + tx = await contract.connect(spenderWallet).transferFrom(tokenOwner, to, initialSupply); + const ownerBalance = await contract.balanceOf(tokenOwner); + const toBalance = await contract.balanceOf(to); + expect(ownerBalance.toString()).to.be.equal('0'); + expect(toBalance.toString()).to.be.equal(amount.toString()); + }); + + it('decreases the spender allowance', async function () { + const allowance = await contract.allowance(tokenOwner, spender); + expect(allowance.toString()).to.be.equal('0'); + }); + + it('emits a transfer event', async function () { + await expect(tx) + .to.emit(contract, 'Transfer') + .withArgs(tokenOwnerWallet.address, toWallet.address, amount); + }); + + it('emits an approval event', async function () { + await expect(tx) + .to.emit(contract, 'Approval') + .withArgs(tokenOwnerWallet.address, spenderWallet.address, await contract.allowance(tokenOwner, spender)); + }); }); - }); - }); - describe('when the spender does not have enough allowance', function () { - let allowance; + describe('when the token owner does not have enough balance', function () { + let amount; - before(async function () { - allowance = initialSupply.sub(1); - }); + beforeEach('reducing balance', async function () { + amount = initialSupply; + await contract.transfer(to, 1); + }); - beforeEach(async function () { - await contract.approve(spender, allowance, { from: tokenOwner }); + it('reverts', async function () { + await expectRevert( + contract.connect(spenderWallet).transferFrom(tokenOwner, to, amount), + 'CALL_EXCEPTION' + ); + }); + }); }); - describe('when the token owner has enough balance', function () { - let amount; + describe('when the spender does not have enough allowance', function () { + let allowance; + before(async function () { - amount = initialSupply; + allowance = initialSupply.sub(1); }); - it('reverts', async function () { - await expectRevert( - contract.connect(spenderWallet).transferFrom(tokenOwner, to, amount), - `CALL_EXCEPTION`, - ); - }); - }); - - describe('when the token owner does not have enough balance', function () { - let amount; - before(async function () { - amount = allowance; + beforeEach(async function () { + await contract.approve(spender, allowance); }); - beforeEach('reducing balance', async function () { - await contract.transfer(to, 2, { from: tokenOwner }); + describe('when the token owner has enough balance', function () { + let amount; + before(async function () { + amount = initialSupply; + }); + + it('reverts', async function () { + await expectRevert( + contract.connect(spenderWallet).transferFrom(tokenOwner, to, amount), + `CALL_EXCEPTION`, + ); + }); }); - it('reverts', async function () { - await expectRevert( - contract.connect(spenderWallet).transferFrom(tokenOwner, to, amount), - `CALL_EXCEPTION`, - ); + describe('when the token owner does not have enough balance', function () { + let amount; + before(async function () { + amount = allowance; + }); + + beforeEach('reducing balance', async function () { + await contract.transfer(to, 2); + }); + + it('reverts', async function () { + await expectRevert( + contract.connect(spenderWallet).transferFrom(tokenOwner, to, amount), + `CALL_EXCEPTION`, + ); + }); }); }); - }); - describe('when the spender has unlimited allowance', function () { - beforeEach(async function () { - await contract.connect(tokenOwnerWallet).approve(spender, ethers.constants.MaxUint256); - }); + describe('when the spender has unlimited allowance', function () { + beforeEach(async function () { + await contract.connect(tokenOwnerWallet).approve(spender, ethers.constants.MaxUint256); + }); - it('does not decrease the spender allowance', async function () { - await contract.connect(spenderWallet).transferFrom(tokenOwner, to, 1); - const allowance = await contract.allowance(tokenOwner, spender); - expect(allowance.toString()).to.be.equal(ethers.constants.MaxUint256.toString()); - }); + it('does not decrease the spender allowance', async function () { + await contract.connect(spenderWallet).transferFrom(tokenOwner, to, 1); + const allowance = await contract.allowance(tokenOwner, spender); + expect(allowance.toString()).to.be.equal(ethers.constants.MaxUint256.toString()); + }); - it('does not emit an approval event', async function () { - await expect(await contract.connect(spenderWallet).transferFrom(tokenOwner, to, 1)) - .to.not.emit(contract, 'Approval'); + it('does not emit an approval event', async function () { + await expect(await contract.connect(spenderWallet).transferFrom(tokenOwner, to, 1)) + .to.not.emit(contract, 'Approval'); + }); }); - }); + } }); describe('when the recipient is the zero address', function () { @@ -309,38 +347,17 @@ describe('ERC20 Acceptance Tests', async function () { adminPublicKey }); - await servicesNode.associateHTSToken(accounts[0].accountId, hts.tokenId, accounts[0].privateKey); - await servicesNode.associateHTSToken(accounts[1].accountId, hts.tokenId, accounts[1].privateKey); - await servicesNode.associateHTSToken(accounts[2].accountId, hts.tokenId, accounts[2].privateKey); - - await servicesNode.approveHTSToken(accounts[0].accountId, hts.tokenId); - await servicesNode.approveHTSToken(accounts[1].accountId, hts.tokenId); - await servicesNode.approveHTSToken(accounts[2].accountId, hts.tokenId); + // Associate and approve token for all accounts + for (const account of accounts) { + await servicesNode.associateHTSToken(account.accountId, hts.tokenId, account.privateKey); + await servicesNode.approveHTSToken(account.accountId, hts.tokenId); + } + // Setup initial balance of token owner account await servicesNode.transferHTSToken(accounts[0].accountId, hts.tokenId, 10000); - // await servicesNode.transferHTSToken(accounts[1].accountId, hts.tokenId, 10000, accounts[0].accountId); - // await servicesNode.transferHTSToken(accounts[2].accountId, hts.tokenId, 10000, accounts[1].accountId); - // await servicesNode.transferHTSToken(accounts[0].accountId, hts.tokenId, 10000, accounts[2].accountId); - // - // - // await servicesNode.transferHTSToken(accounts[1].accountId, hts.tokenId, 10000); - // await servicesNode.transferHTSToken(servicesNode.client.operatorAccountId, hts.tokenId, 10000, accounts[1].accountId); - // - // await servicesNode.transferHTSToken(accounts[2].accountId, hts.tokenId, 10000); - // await servicesNode.transferHTSToken(servicesNode.client.operatorAccountId, hts.tokenId, 10000, accounts[2].accountId); - const evmAddress = Utils.idToEvmAddress(hts.tokenId.toString()); const contract = new ethers.Contract(evmAddress, abi, accounts[0].wallet); - const balance0 = await contract.balanceOf(accounts[0].address); - const balance1 = await contract.balanceOf(accounts[1].address); - const balance2 = await contract.balanceOf(accounts[2].address); - - logger.info(`balance0: ${balance0}`); - logger.info(`balance1: ${balance1}`); - logger.info(`balance2: ${balance2}`); - - return contract; }; }); \ No newline at end of file diff --git a/packages/server/tests/acceptance/index.spec.ts b/packages/server/tests/acceptance/index.spec.ts index f25dd150f6..1e0c47fedb 100644 --- a/packages/server/tests/acceptance/index.spec.ts +++ b/packages/server/tests/acceptance/index.spec.ts @@ -95,7 +95,7 @@ describe('RPC Server Acceptance Tests', function () { if (USE_LOCAL_NODE === 'true') { // stop local-node logger.info('Shutdown local node'); - // shell.exec('npx hedera-local stop'); + shell.exec('npx hedera-local stop'); } // stop relay @@ -108,7 +108,7 @@ describe('RPC Server Acceptance Tests', function () { describe("Acceptance tests", async () => { fs.readdirSync(path.resolve(__dirname, './')) .forEach(test => { - if (test !== 'index.spec.ts' && test.endsWith('erc20.spec.ts')) { + if (test !== 'index.spec.ts' && test.endsWith('.spec.ts')) { require(`./${test}`); } }); From 0c06cdc451e54bf3868510070b2509b76eb9a98a Mon Sep 17 00:00:00 2001 From: Ivo Yankov Date: Mon, 11 Jul 2022 12:03:52 +0300 Subject: [PATCH 5/6] chore: resolve conflicts Signed-off-by: Ivo Yankov --- packages/server/tests/clients/servicesClient.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/tests/clients/servicesClient.ts b/packages/server/tests/clients/servicesClient.ts index f7fecf6644..175f8dbe55 100644 --- a/packages/server/tests/clients/servicesClient.ts +++ b/packages/server/tests/clients/servicesClient.ts @@ -39,7 +39,7 @@ import { ContractCreateFlow, FileUpdateTransaction, TransactionId, - AccountAllowanceApproveTransaction + AccountAllowanceApproveTransaction, AccountBalance, } from '@hashgraph/sdk'; import { Logger } from 'pino'; From 28644ff944ef84017ffbf7425b195fddc43e8154 Mon Sep 17 00:00:00 2001 From: Ivo Yankov Date: Tue, 12 Jul 2022 17:57:58 +0300 Subject: [PATCH 6/6] feat: specify admin account when creating hts Signed-off-by: Ivo Yankov --- .../server/tests/acceptance/erc20.spec.ts | 20 +++++------ .../server/tests/clients/servicesClient.ts | 34 +++++++++++-------- 2 files changed, 30 insertions(+), 24 deletions(-) diff --git a/packages/server/tests/acceptance/erc20.spec.ts b/packages/server/tests/acceptance/erc20.spec.ts index b0f719963d..aec811d6d7 100644 --- a/packages/server/tests/acceptance/erc20.spec.ts +++ b/packages/server/tests/acceptance/erc20.spec.ts @@ -63,7 +63,7 @@ describe('ERC20 Acceptance Tests', async function () { anotherAccount = accounts[2].address; contracts.push(await deployErc20([name, symbol, initialHolder, initialSupply], ERC20MockJson)); - contracts.push(await createHTS(name, symbol, accounts[0].accountId.toString(), 10000, accounts[0].privateKey.publicKey, ERC20MockJson.abi)); + contracts.push(await createHTS(name, symbol, accounts[0], 10000, ERC20MockJson.abi, [accounts[1], accounts[2]])); }); for (const i in testTitles) { @@ -338,24 +338,24 @@ describe('ERC20 Acceptance Tests', async function () { return contract; }; - const createHTS = async(tokenName, symbol, treasuryAccountId, initialSupply, adminPublicKey, abi) => { - const hts = await servicesNode.createHTS({ + const createHTS = async(tokenName, symbol, adminAccount, initialSupply, abi, associatedAccounts) => { + const htsResult = await servicesNode.createHTS({ tokenName, symbol, - treasuryAccountId, + treasuryAccountId: adminAccount.accountId.toString(), initialSupply, - adminPublicKey + adminPrivateKey: adminAccount.privateKey, }); // Associate and approve token for all accounts - for (const account of accounts) { - await servicesNode.associateHTSToken(account.accountId, hts.tokenId, account.privateKey); - await servicesNode.approveHTSToken(account.accountId, hts.tokenId); + for (const account of associatedAccounts) { + await servicesNode.associateHTSToken(account.accountId, htsResult.receipt.tokenId, account.privateKey, htsResult.client); + await servicesNode.approveHTSToken(account.accountId, htsResult.receipt.tokenId, htsResult.client); } // Setup initial balance of token owner account - await servicesNode.transferHTSToken(accounts[0].accountId, hts.tokenId, 10000); - const evmAddress = Utils.idToEvmAddress(hts.tokenId.toString()); + await servicesNode.transferHTSToken(accounts[0].accountId, htsResult.receipt.tokenId, initialSupply, htsResult.client); + const evmAddress = Utils.idToEvmAddress(htsResult.receipt.tokenId.toString()); const contract = new ethers.Contract(evmAddress, abi, accounts[0].wallet); return contract; diff --git a/packages/server/tests/clients/servicesClient.ts b/packages/server/tests/clients/servicesClient.ts index 175f8dbe55..419676a36a 100644 --- a/packages/server/tests/clients/servicesClient.ts +++ b/packages/server/tests/clients/servicesClient.ts @@ -298,47 +298,53 @@ export default class ServicesClient { symbol: 'HTS', treasuryAccountId: '0.0.2', initialSupply: 5000, - adminPublicKey: this.DEFAULT_KEY, - accounts: [] + adminPrivateKey: this.DEFAULT_KEY, }) { const {} = args; const expiration = new Date(); expiration.setDate(expiration.getDate() + 30); + + const htsClient = Client.forNetwork(JSON.parse(this.network)); + htsClient.setOperator(AccountId.fromString(args.treasuryAccountId), args.adminPrivateKey); + const tokenCreate = await (await new TokenCreateTransaction() .setTokenName(args.tokenName) .setTokenSymbol(args.symbol) .setExpirationTime(expiration) .setDecimals(18) - .setTreasuryAccountId(this.client.operatorAccountId) + .setTreasuryAccountId(AccountId.fromString(args.treasuryAccountId)) .setInitialSupply(args.initialSupply) - .setTransactionId(TransactionId.generate(this.client.operatorAccountId)) - .setNodeAccountIds([this.client._network.getNodeAccountIdsForExecute()[0]])) - .execute(this.client); + .setTransactionId(TransactionId.generate(AccountId.fromString(args.treasuryAccountId))) + .setNodeAccountIds([htsClient._network.getNodeAccountIdsForExecute()[0]])) + .execute(htsClient); const receipt = await tokenCreate.getReceipt(this.client); - return receipt; + return { + client: htsClient, + receipt + }; } - async associateHTSToken(accountId, tokenId, privateKey) { + async associateHTSToken(accountId, tokenId, privateKey, htsClient) { const tokenAssociate = await (await new TokenAssociateTransaction() .setAccountId(accountId) .setTokenIds([tokenId]) - .freezeWith(this.client) + .freezeWith(htsClient) .sign(privateKey)) - .execute(this.client); + .execute(htsClient); - await tokenAssociate.getReceipt(this.client); + await tokenAssociate.getReceipt(htsClient); this.logger.info(`HTS Token ${tokenId} associated to : ${accountId}`); }; - async approveHTSToken(spenderId, tokenId) { + async approveHTSToken(spenderId, tokenId, htsClient) { const amount = 10000; const tokenApprove = await (new AccountAllowanceApproveTransaction() .addTokenAllowance(tokenId, spenderId, amount)) - .execute(this.client); + .execute(htsClient); - await tokenApprove.getReceipt(this.client); + await tokenApprove.getReceipt(htsClient); this.logger.info(`${amount} of HTS Token ${tokenId} can be spent by ${spenderId}`); };