Skip to content

Commit

Permalink
refactor(client): remove channel proxy
Browse files Browse the repository at this point in the history
  • Loading branch information
Kalovelo committed Sep 13, 2022
1 parent fb1e039 commit dace82d
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 116 deletions.
40 changes: 22 additions & 18 deletions client/src/utils/game-channel/game-channel.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,17 @@ describe('GameChannel', async () => {
signedTx: 'tx_mock',
});

vi.spyOn(gameChannel, 'fetchChannelConfig').mockResolvedValue(
{} as ChannelOptions
);

vi.spyOn(sdk, 'getContractInstance').mockResolvedValue({
source: contractSource,
compile: () => ({ bytecode: 'bytecode_mock' }),
} as unknown as ContractInstance);

const getChannelWithoutProxySpy = vi.spyOn(
gameChannel,
'getChannelWithoutProxy'
);
const channelInitializeSpy = vi.spyOn(Channel, 'initialize');
const channelReconnectSpy = vi.spyOn(Channel, 'reconnect');

beforeEach(async () => {
createTestingPinia({
Expand Down Expand Up @@ -149,37 +151,43 @@ describe('GameChannel', async () => {
});

describe('checkIfChannelIsEstablished()', async () => {
getChannelWithoutProxySpy
.mockReturnValueOnce({
channelInitializeSpy
.mockResolvedValueOnce({
state: function () {
return new Promise((resolve) => {
setTimeout(resolve, 5000);
});
},
} as Channel)
.mockReturnValueOnce({
on: () => ({}),
} as unknown as Channel)
.mockResolvedValueOnce({
state: function () {
return new Promise((resolve) => {
setTimeout(resolve, 100);
});
},
} as Channel);
on: () => ({}),
} as unknown as Channel);

it('returns false when state() promise takes more than 3000ms', async () => {
await gameChannel.initializeChannel();
expect(await gameChannel.checkIfChannelIsEstablished()).toBe(false);
});
it('returns true when state() promise takes less than 3000ms', async () => {
await gameChannel.initializeChannel();
expect(await gameChannel.checkIfChannelIsEstablished()).toBe(true);
});
});

describe('reconnectChannel()', async () => {
const gameChannel = new GameChannel();
const getChannelWithoutProxySpy = vi.spyOn(
gameChannel,
'getChannelWithoutProxy'
);
const reconnectSpy = vi.spyOn(Channel, 'reconnect');
channelInitializeSpy.mockResolvedValueOnce({
on: () => ({}),
} as unknown as Channel);
channelReconnectSpy.mockResolvedValue({
on: () => ({}),
} as unknown as Channel);

const checkIfChannelIsEstablishedSpy = vi.spyOn(
gameChannel,
'checkIfChannelIsEstablished'
Expand All @@ -188,7 +196,6 @@ describe('GameChannel', async () => {
const resetAppSpy = vi.spyOn(main, 'resetApp').mockResolvedValue();
const registerEventsSpy = vi.spyOn(gameChannel, 'registerEvents');

reconnectSpy.mockResolvedValue({} as Channel);
checkIfChannelIsEstablishedSpy
.mockResolvedValueOnce(false)
.mockResolvedValueOnce(true);
Expand All @@ -206,9 +213,6 @@ describe('GameChannel', async () => {
});

it('re-registers events on channel reconnect', async () => {
getChannelWithoutProxySpy.mockReturnValue({
on: () => ({}),
} as unknown as Channel);
gameChannel.channelConfig = {} as ChannelOptions;
localStorage.setItem('gameState', 'test');
await gameChannel.reconnectChannel();
Expand Down
53 changes: 21 additions & 32 deletions client/src/utils/game-channel/game-channel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import contractSource from '@aeternity/rock-paper-scissors';
import { ChannelOptions } from '@aeternity/aepp-sdk/es/channel/internal';
import { Encoded } from '@aeternity/aepp-sdk/es/utils/encoder';
import { BigNumber } from 'bignumber.js';
import { nextTick, toRaw } from 'vue';
import { nextTick } from 'vue';
import {
decodeCallData,
keypair,
Expand Down Expand Up @@ -48,9 +48,10 @@ function timeout(ms: number) {
});
}

let channel: Channel;

export class GameChannel {
channelConfig?: ChannelOptions;
channelInstance?: Channel;
channelRound?: number;
channelId?: string;
fsmId?: string;
Expand Down Expand Up @@ -97,16 +98,8 @@ export class GameChannel {
contractAddress?: Encoded.ContractAddress;
contractCreationChannelRound?: number;

// since gameChannel is reactive, we need to get the raw channel instance
getChannelWithoutProxy() {
if (!this.channelInstance) {
throw new Error('Channel is not initialized');
}
return toRaw(this.channelInstance);
}

getStatus() {
return this.getChannelWithoutProxy().status();
return channel.status();
}

getSelectionHash(selection: Selections): string {
Expand Down Expand Up @@ -178,7 +171,7 @@ export class GameChannel {
if (!config) config = await this.fetchChannelConfig();
this.channelConfig = config;
this.isFunded = true;
this.channelInstance = await Channel.initialize({
channel = await Channel.initialize({
...this.channelConfig,
debug: true,
role: 'responder',
Expand All @@ -201,7 +194,7 @@ export class GameChannel {
* hangs for a while, therefore we add a timeout
*/
async checkIfChannelIsEstablished() {
const statePromise = this.getChannelWithoutProxy().state();
const statePromise = channel.state();

try {
await Promise.race([statePromise, timeout(3000)]);
Expand All @@ -214,7 +207,7 @@ export class GameChannel {
async reconnectChannel() {
if (!this.channelConfig) throw new Error('Channel config is not set');
this.isOpening = true;
this.channelInstance = await Channel.reconnect(
channel = await Channel.reconnect(
{
...this.channelConfig,
debug: true,
Expand Down Expand Up @@ -247,7 +240,7 @@ export class GameChannel {

async closeChannel() {
localStorage.clear();
if (!this.channelInstance) {
if (!channel) {
throw new Error('Channel is not open');
}

Expand All @@ -267,7 +260,7 @@ export class GameChannel {
await channelClosing.then(async (canClose) => {
if (!canClose) return;
this.channelIsClosing = true;
await this.getChannelWithoutProxy()
await channel
.shutdown((tx: Encoded.Transaction) => this.signTx('channel_close', tx))
.then(async () => {
await this.saveResultsOnChain();
Expand Down Expand Up @@ -341,15 +334,14 @@ export class GameChannel {
}

registerEvents() {
if (this.channelInstance) {
this.getChannelWithoutProxy().on('statusChanged', (status) => {
if (channel) {
channel.on('statusChanged', (status) => {
if (status === 'open') {
this.isOpen = true;
this.isOpening = false;

if (!this.channelId)
this.channelId = this.getChannelWithoutProxy().id();
if (!this.fsmId) this.fsmId = this.getChannelWithoutProxy().fsmId();
if (!this.channelId) this.channelId = channel.id();
if (!this.fsmId) this.fsmId = channel.fsmId();
this.updateBalances();
}

Expand All @@ -359,8 +351,8 @@ export class GameChannel {
}
});

this.getChannelWithoutProxy().on('stateChanged', () => {
this.channelRound = this.getChannelWithoutProxy().round() ?? undefined;
channel.on('stateChanged', () => {
this.channelRound = channel.round() ?? undefined;
if (this.isOpen) this.saveStateToLocalStorage();
if (
this.gameRound.botSelection != Selections.none &&
Expand All @@ -371,11 +363,11 @@ export class GameChannel {
nextTick(() => this.revealRoundResult());
}
});
this.getChannelWithoutProxy().on('message', (message) => {
channel.on('message', (message) => {
const msg = JSON.parse(message.info);
this.handleMessage(msg);
});
this.getChannelWithoutProxy().on('onChainTx', async (onChainTx) => {
channel.on('onChainTx', async (onChainTx) => {
const onChainTxhash = buildTxHash(onChainTx);
const polledTx = await poll(onChainTxhash, {
onNode: node,
Expand Down Expand Up @@ -424,13 +416,13 @@ export class GameChannel {
params: unknown[],
amount?: number | BigNumber
) {
if (!this.channelInstance) {
if (!channel) {
throw new Error('Channel is not open');
}
if (!this.contract || !this.contractAddress) {
throw new Error('Contract is not set');
}
const result = await this.getChannelWithoutProxy().callContract(
const result = await channel.callContract(
{
amount: amount ?? this.gameRound.stake,
callData: this.contract.calldata.encode(
Expand Down Expand Up @@ -459,10 +451,7 @@ export class GameChannel {
throw new Error('Channel config is not set');
}
const { initiatorId, responderId } = this.channelConfig;
const balances = await this.getChannelWithoutProxy().balances([
initiatorId,
responderId,
]);
const balances = await channel.balances([initiatorId, responderId]);
this.balances.user = new BigNumber(balances[responderId]);
this.balances.bot = new BigNumber(balances[initiatorId]);
}
Expand Down Expand Up @@ -515,7 +504,7 @@ export class GameChannel {
if (!this.contract) throw new Error('Contract is not set');
if (!this.contractAddress) throw new Error('Contract address is not set');

return await this.getChannelWithoutProxy().getContractCall({
return await channel.getContractCall({
caller,
contract: this.contractAddress,
round,
Expand Down
87 changes: 21 additions & 66 deletions client/src/utils/sdk-service/sdk-service.test.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,32 @@
import { Encoded } from '@aeternity/aepp-sdk/es/utils/encoder';
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { Channel } from '@aeternity/aepp-sdk';
import { ChannelOptions } from '@aeternity/aepp-sdk/es/channel/internal';
import contractSource from '@aeternity/rock-paper-scissors';
import { createTestingPinia } from '@pinia/testing';
import SHA from 'sha.js';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { GameChannel } from '../../utils/game-channel/game-channel';
import {
getNewSdk,
FAUCET_ACCOUNT,
verifyContractBytecode,
initSdk,
refreshSdkAccount,
sdk,
verifyContractBytecode,
} from '../../utils/sdk-service/sdk-service';
import { createTestingPinia } from '@pinia/testing';
import { GameChannel } from '../../utils/game-channel/game-channel';
import contractSource from '@aeternity/rock-paper-scissors';
import SHA from 'sha.js';
import { Channel } from '@aeternity/aepp-sdk';
import { waitForChannelReady } from '../../../tests/utils';

describe('SDK', () => {
let gameChannel: GameChannel;
const channelInitializeSpy = vi.spyOn(Channel, 'initialize');

channelInitializeSpy.mockResolvedValue({
on: () => ({}),
} as unknown as Channel);

beforeEach(async () => {
await initSdk();
await refreshSdkAccount();
gameChannel = new GameChannel();
const fetchConfigSpy = vi.spyOn(gameChannel, 'fetchChannelConfig');

fetchConfigSpy.mockResolvedValue({} as ChannelOptions);
});

it('creates and returns an SDK instance', async () => {
Expand All @@ -41,16 +48,14 @@ describe('SDK', () => {
);
});
it('cannot call contract when contract is not deployed', async () => {
const gameChannel = new GameChannel();
createTestingPinia({
initialState: {
channel: {
channel: gameChannel,
},
},
});

gameChannel.channelInstance = {} as Channel;
await gameChannel.initializeChannel();

expect(gameChannel.contract).toBeFalsy();
await expect(gameChannel.callContract('init', [])).rejects.toThrowError(
Expand All @@ -64,8 +69,6 @@ describe('SDK', () => {
signedTx: 'tx_mock',
});

gameChannel.channelInstance = {} as Channel;

createTestingPinia({
initialState: {
channel: {
Expand Down Expand Up @@ -94,6 +97,8 @@ describe('SDK', () => {
false => ()
`;
const gameChannel = new GameChannel();
const fetchConfigSpy = vi.spyOn(gameChannel, 'fetchChannelConfig');
fetchConfigSpy.mockResolvedValue({} as ChannelOptions);

vi.spyOn(gameChannel, 'callContract').mockResolvedValue({
accepted: true,
Expand Down Expand Up @@ -124,54 +129,4 @@ describe('SDK', () => {
).resolves.toBeFalsy();
});
});

describe('integration', () => {
it('creates game channel instance, initializes Channel and returns coins to faucet on channel closing', async () => {
const gameChannel = new GameChannel();
await gameChannel.initializeChannel();
createTestingPinia({
initialState: {
channel: {
channel: gameChannel,
},
},
});
await waitForChannelReady(gameChannel.getChannelWithoutProxy());
const client = sdk;
const ae = await getNewSdk();

expect(client?.selectedAddress).toBeTruthy();
expect(gameChannel.getStatus()).toBe('open');

if (FAUCET_ACCOUNT) {
await ae.addAccount(FAUCET_ACCOUNT, { select: true });
}
const balance_before = await client.getBalance(
client.selectedAddress as Encoded.AccountAddress
);
expect(BigInt(balance_before)).toBeGreaterThan(0);

const faucet_balance_before = await ae.getBalance(
ae.selectedAddress as Encoded.AccountAddress
);
await new Promise((resolve) => setTimeout(resolve, 5000));

await gameChannel.closeChannel();
await new Promise((resolve) => setTimeout(resolve, 5000));
expect(gameChannel.getChannelWithoutProxy().status()).toBe(
'disconnected'
);

const balance_after = await client.getBalance(
client.selectedAddress as Encoded.AccountAddress
);
const faucet_balance_after = await ae.getBalance(
ae.selectedAddress as Encoded.AccountAddress
);
expect(balance_after).toBe('0');
expect(BigInt(faucet_balance_after)).toBeGreaterThan(
BigInt(faucet_balance_before)
);
}, 20000);
});
});
Loading

0 comments on commit dace82d

Please sign in to comment.