From 4f1dd330cb989a51b6d67add4ba721b7a31f2f15 Mon Sep 17 00:00:00 2001 From: AliReza Seyfpour Date: Sat, 3 Apr 2021 12:17:51 +0200 Subject: [PATCH 1/2] types(dataunion): Transfer amount to a specific member Added transferToMemberInContract to dataunion Signed-off-by: AliReza Seyfpour --- src/dataunion/DataUnion.ts | 15 +++++++++++++++ src/dataunion/abi.ts | 9 +++++++++ 2 files changed, 24 insertions(+) diff --git a/src/dataunion/DataUnion.ts b/src/dataunion/DataUnion.ts index a2ad23c59..7ebc8aa05 100644 --- a/src/dataunion/DataUnion.ts +++ b/src/dataunion/DataUnion.ts @@ -467,6 +467,21 @@ export class DataUnion { return tx.wait() } + /** + * Transfer amount to specific member in DataunionSidechain + * @param memberAddress - the other member who gets their tokens out of the Data Union + * @param amountTokenWei - the amount that want to add to the member + * @returns receipt once transfer transaction is confirmed + */ + async transferToMemberInContract( + memberAddress: EthereumAddress, + amountTokenWei: BigNumber|number|string + ): Promise { + const address = getAddress(memberAddress) // throws if bad address + const duSidechain = await this.getContracts().getSidechainContract(this.contractAddress) + return duSidechain.transferToMemberInContract(address, amountTokenWei) + } + /** * Create a new DataUnionMainnet contract to mainnet with DataUnionFactoryMainnet * This triggers DataUnionSidechain contract creation in sidechain, over the bridge (AMB) diff --git a/src/dataunion/abi.ts b/src/dataunion/abi.ts index df28546ac..7e0e5066f 100644 --- a/src/dataunion/abi.ts +++ b/src/dataunion/abi.ts @@ -125,6 +125,15 @@ export const dataUnionSidechainABI = [{ ], anonymous: false, type: 'event' +}, { + name: 'transferToMemberInContract', + inputs: [ + { name: 'recipient', type: 'address', internalType: 'address' }, + { name: 'amount', type: 'uint256', internalType: 'uint256' } + ], + outputs: [], + stateMutability: 'nonpayable', + type: 'function' }] // Only the part of ABI that is needed by deployment (and address resolution) From 84fa5dd78146c3bf4be4c6d36cc915c1fd6a297f Mon Sep 17 00:00:00 2001 From: AliReza Seyfpour Date: Sat, 17 Apr 2021 19:17:27 +0200 Subject: [PATCH 2/2] test(dataunion): Integration test Added an integration test for transfer to member Signed-off-by: AliReza Seyfpour --- test/integration/dataunion/transfer.test.ts | 145 ++++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100644 test/integration/dataunion/transfer.test.ts diff --git a/test/integration/dataunion/transfer.test.ts b/test/integration/dataunion/transfer.test.ts new file mode 100644 index 000000000..726c2b14f --- /dev/null +++ b/test/integration/dataunion/transfer.test.ts @@ -0,0 +1,145 @@ +import { BigNumber, Contract, providers, Wallet } from 'ethers' +import { parseEther } from 'ethers/lib/utils' +import debug from 'debug' +import * as Token from '../../../contracts/TestToken.json' +import config from '../config' +import { getEndpointUrl, until } from '../../../src/utils' +import { MemberStatus } from '../../../src/dataunion/DataUnion' +import { StreamrClient } from '../../../src/StreamrClient' +import { EthereumAddress } from '../../../src/types' +import authFetch from '../../../src/rest/authFetch' + +const log = debug('StreamrClient::DataUnion::integration-test-transfer') + +const providerSidechain = new providers.JsonRpcProvider(config.clientOptions.sidechain) +const providerMainnet = new providers.JsonRpcProvider(config.clientOptions.mainnet) + +const tokenAdminWallet = new Wallet(config.tokenAdminPrivateKey, providerMainnet) +const tokenMainnet = new Contract(config.clientOptions.tokenAddress, Token.abi, tokenAdminWallet) + +const adminWalletSidechain = new Wallet(config.clientOptions.auth.privateKey, providerSidechain) +const tokenSidechain = new Contract(config.clientOptions.tokenSidechainAddress, Token.abi, adminWalletSidechain) + +const sendTokensToSidechain = async (receiverAddress: EthereumAddress, amount: BigNumber) => { + const relayTokensAbi = [ + { + inputs: [ + { + internalType: 'address', + name: 'token', + type: 'address' + }, + { + internalType: 'address', + name: '_receiver', + type: 'address' + }, + { + internalType: 'uint256', + name: '_value', + type: 'uint256' + }, + { + internalType: 'bytes', + name: '_data', + type: 'bytes' + } + ], + name: 'relayTokensAndCall', + outputs: [], + stateMutability: 'nonpayable', + type: 'function' + } + ] + const tokenMediator = new Contract(config.tokenMediator, relayTokensAbi, tokenAdminWallet) + const tx1 = await tokenMainnet.approve(tokenMediator.address, amount) + await tx1.wait() + log('Approved') + const tx2 = await tokenMediator.relayTokensAndCall(tokenMainnet.address, receiverAddress, amount, '0x1234') // dummy 0x1234 + await tx2.wait() + log('Relayed tokens') + await until(async () => !(await tokenSidechain.balanceOf(receiverAddress)).eq('0'), 300000, 3000) + log('Sidechain balance changed') +} + +describe('DataUnion transfer within contract', () => { + let adminClient: StreamrClient + + beforeAll(async () => { + log(`Connecting to Ethereum networks, config = ${JSON.stringify(config)}`) + const network = await providerMainnet.getNetwork() + log('Connected to "mainnet" network: ', JSON.stringify(network)) + const network2 = await providerSidechain.getNetwork() + log('Connected to sidechain network: ', JSON.stringify(network2)) + log(`Minting 100 tokens to ${tokenAdminWallet.address}`) + const tx1 = await tokenMainnet.mint(tokenAdminWallet.address, parseEther('100')) + await tx1.wait() + + await sendTokensToSidechain(adminWalletSidechain.address, parseEther('10')) + + adminClient = new StreamrClient(config.clientOptions as any) + }, 150000) + + afterAll(() => { + providerMainnet.removeAllListeners() + providerSidechain.removeAllListeners() + }) + + it('transfer token to member', async () => { + const dataUnion = await adminClient.deployDataUnion() + const secret = await dataUnion.createSecret('test secret') + // eslint-disable-next-line no-underscore-dangle + const contract = await dataUnion._getContract() + log(`DU owner: ${await dataUnion.getAdminAddress()}`) + log(`Sending tx from ${await adminClient.getAddress()}`) + + // product is needed for join requests to analyze the DU version + const createProductUrl = getEndpointUrl(config.clientOptions.restUrl, 'products') + await authFetch(createProductUrl, adminClient.session, { + method: 'POST', + body: JSON.stringify({ + beneficiaryAddress: dataUnion.getAddress(), + type: 'DATAUNION', + dataUnionVersion: 2 + }) + }) + + const memberWallet = new Wallet(`0x100000000000000000000000000000000000000012300000001${Date.now()}`, providerSidechain) + const memberClient = new StreamrClient({ + ...config.clientOptions, + auth: { + privateKey: memberWallet.privateKey + } + } as any) + const res = await memberClient.getDataUnion(dataUnion.getAddress()).join(secret) + log(`Member joined data union: ${JSON.stringify(res)}`) + log(`DU member count: ${await contract.sidechain.activeMemberCount()}`) + + const stats = await memberClient.getDataUnion(dataUnion.getAddress()).getMemberStats(memberWallet.address) + log(`Stats: ${JSON.stringify(stats)}`) + + const approve = await tokenSidechain.approve(dataUnion.getSidechainAddress(), parseEther('1')) + await approve.wait() + log(`Approve DU ${dataUnion.getSidechainAddress()} to access 1 token from ${adminWalletSidechain.address}`) + + const tx = await dataUnion.transferToMemberInContract(memberWallet.address, parseEther('1')) + await tx.wait() + log(`Transfer 1 token with transferWithinContract to ${memberWallet.address}`) + + const newStats = await memberClient.getDataUnion(dataUnion.getAddress()).getMemberStats(memberWallet.address) + log(`Stats: ${JSON.stringify(newStats)}`) + + expect(stats).toMatchObject({ + status: MemberStatus.ACTIVE, + earningsBeforeLastJoin: BigNumber.from(0), + totalEarnings: BigNumber.from(0), + withdrawableEarnings: BigNumber.from(0) + }) + expect(newStats).toMatchObject({ + status: MemberStatus.ACTIVE, + earningsBeforeLastJoin: parseEther('1'), + totalEarnings: parseEther('1'), + withdrawableEarnings: parseEther('1') + }) + }, 150000) +})