From 955d576edebfe59c644d477012c4cd819de17a70 Mon Sep 17 00:00:00 2001 From: Kalovelo Date: Fri, 29 Jul 2022 11:17:53 +0300 Subject: [PATCH 1/4] docs(server): add jsdoc --- server/src/services/bot/bot.constants.ts | 11 +++- server/src/services/bot/bot.service.spec.ts | 22 +++++-- server/src/services/bot/bot.service.ts | 62 +++++++++++++------ .../src/services/contract/contract.service.ts | 10 +++ server/src/services/sdk/sdk.service.ts | 10 +++ server/test/integration/bot.service.spec.ts | 15 ++++- 6 files changed, 103 insertions(+), 27 deletions(-) diff --git a/server/src/services/bot/bot.constants.ts b/server/src/services/bot/bot.constants.ts index a755f11..68c8169 100644 --- a/server/src/services/bot/bot.constants.ts +++ b/server/src/services/bot/bot.constants.ts @@ -1,7 +1,14 @@ +import { ChannelOptions } from '@aeternity/aepp-sdk/es/channel/internal'; import BigNumber from 'bignumber.js'; import { WEBSOCKET_URL } from '../sdk'; -export const MUTUAL_CHANNEL_CONFIGURATION = { +/** + * @see {@link https://github.com/aeternity/protocol/blob/master/node/api/channels_api_usage.md#channel-establishing-parameters} + */ +export const MUTUAL_CHANNEL_CONFIGURATION: Partial & { + minimumDepthStrategy: 'plain' | 'txFee'; + minimumDepth: number; +} = { url: WEBSOCKET_URL, pushAmount: 0, initiatorAmount: new BigNumber('4.5e18'), @@ -10,6 +17,8 @@ export const MUTUAL_CHANNEL_CONFIGURATION = { lockPeriod: 10, debug: false, timeoutIdle: 60000, + // How to calculate minimum depth - either txfee (default) or plain. We use + // `plain` with `minimumDepth` in order to reduce delay. minimumDepthStrategy: 'plain', minimumDepth: 0, } as const; diff --git a/server/src/services/bot/bot.service.spec.ts b/server/src/services/bot/bot.service.spec.ts index 0a22777..f39d2e5 100644 --- a/server/src/services/bot/bot.service.spec.ts +++ b/server/src/services/bot/bot.service.spec.ts @@ -9,7 +9,10 @@ import botService from './index'; jest.setTimeout(10000); -ContractService.deployContract = jest.fn(); +ContractService.deployContract = jest.fn().mockResolvedValue({ + instance: {} as any, + address: 'ct_test', +}); interface ChannelMock { listeners: { @@ -52,7 +55,9 @@ describe('botService', () => { await botService.addGameSession(channel, channelConfig); - expect(botService.gameSessionPool.has(channelConfig.initiatorId)).toBe(true); + expect(botService.gameSessionPool.has(channelConfig.initiatorId)).toBe( + true, + ); }); it('should remove a channel from botPool', async () => { @@ -63,9 +68,13 @@ describe('botService', () => { }); await botService.addGameSession(channel, channelConfig); - expect(botService.gameSessionPool.has(channelConfig.initiatorId)).toBe(true); + expect(botService.gameSessionPool.has(channelConfig.initiatorId)).toBe( + true, + ); botService.removeGameSession(channelConfig.initiatorId); - expect(botService.gameSessionPool.has(channelConfig.initiatorId)).toBe(false); + expect(botService.gameSessionPool.has(channelConfig.initiatorId)).toBe( + false, + ); }); it('registers events on channel', async () => { @@ -74,7 +83,10 @@ describe('botService', () => { const channelMock: ChannelMock = channel as unknown as ChannelMock; expect(channelMock.listeners.statusChanged).toBeDefined(); channelMock.listeners.statusChanged('open'); - expect(botService.gameSessionPool.has(channelConfig.initiatorId)).toBe(true); + await timeout(100); + expect(botService.gameSessionPool.has(channelConfig.initiatorId)).toBe( + true, + ); channelMock.listeners.statusChanged('closed'); await timeout(500); const channelRemoved = !botService.gameSessionPool.has( diff --git a/server/src/services/bot/bot.service.ts b/server/src/services/bot/bot.service.ts index 8de59ed..391d9d8 100644 --- a/server/src/services/bot/bot.service.ts +++ b/server/src/services/bot/bot.service.ts @@ -1,6 +1,5 @@ import { Channel, generateKeyPair, MemoryAccount } from '@aeternity/aepp-sdk'; import { ChannelOptions } from '@aeternity/aepp-sdk/es/channel/internal'; -import { ContractInstance } from '@aeternity/aepp-sdk/es/contract/aci'; import { EncodedData } from '@aeternity/aepp-sdk/es/utils/encoder'; import BigNumber from 'bignumber.js'; import logger from '../../logger'; @@ -17,15 +16,22 @@ import { GameSession } from './bot.interface'; export const gameSessionPool = new Map(); +/** + * Adds game session to the pool after deploying the contract + */ export async function addGameSession( channel: Channel, configuration: ChannelOptions, - contractPromise?: Promise<{ - instance: ContractInstance; - address: EncodedData<'ct'>; - }>, ) { - const { instance, address } = contractPromise != null && (await contractPromise); + const { instance, address } = await ContractService.deployContract( + configuration.initiatorId, + channel, + { + player0: configuration.responderId, + player1: configuration.initiatorId, + reactionTime: 60000, + }, + ); gameSessionPool.set(configuration.initiatorId, { channel, contractState: { @@ -42,13 +48,19 @@ export async function addGameSession( ); } -export function removeGameSession(botId: EncodedData<'ak'>) { - gameSessionPool.delete(botId); +/** + * Removes game session from the pool after the channel is closed + */ +export function removeGameSession(onAccount: EncodedData<'ak'>) { + gameSessionPool.delete(onAccount); logger.info( - `Removed from pool game session with bot ID: ${botId}. Total sessions: ${gameSessionPool.size}`, + `Removed from pool game session with bot ID: ${onAccount}. Total sessions: ${gameSessionPool.size}`, ); } +/** + * Returns funds to the faucet and removes the game session from the pool + */ export async function handleChannelClose(onAccount: EncodedData<'ak'>) { try { await sdk.transferFunds(1, FAUCET_PUBLIC_ADDRESS, { @@ -62,6 +74,11 @@ export async function handleChannelClose(onAccount: EncodedData<'ak'>) { sdk.removeAccount(onAccount); } +/** + * Triggered when channel operation is `OffChainCallContract`. + * Decodes the call data provided by the opponent and generates + * the next calldata to be sent to the opponent. + */ export async function handleOpponentCallUpdate( update: Update, gameSession: GameSession, @@ -73,6 +90,9 @@ export async function handleOpponentCallUpdate( gameSession.contractState.callDataToSend = nextCallDataToSend; } +/** + * Calls contract if game session has available data to send + */ async function respondToContractCall(gameSession: GameSession) { if (gameSession.contractState.callDataToSend == null) return; await gameSession.channel.callContract( @@ -89,6 +109,15 @@ async function respondToContractCall(gameSession: GameSession) { gameSession.contractState.callDataToSend = null; } +/** + * Registers channel events. + * If the channel is closed, + * funds are returned to the faucet and the game session is removed from the pool. + * If the channel is opened, + * bot deploys contract to the channel and the game session is added to the pool. + * If the channel is updated, + * bot decodes the call data provided by the opponent and responds. + */ export async function registerEvents( channelInstance: Channel, configuration: ChannelOptions, @@ -100,16 +129,7 @@ export async function registerEvents( if (status === 'open') { if (!gameSessionPool.has(configuration.initiatorId)) { - const contractPromise = ContractService.deployContract( - configuration.initiatorId, - channelInstance, - { - player0: configuration.responderId, - player1: configuration.initiatorId, - reactionTime: 3000, - }, - ); - void addGameSession(channelInstance, configuration, contractPromise); + void addGameSession(channelInstance, configuration); } } }); @@ -124,6 +144,10 @@ export async function registerEvents( }); } +/** + * Handles client request to create a new game session. + * @returns mutual channel configuration + */ export async function generateGameSession( playerAddress: EncodedData<'ak'>, playerNodeHost: string, diff --git a/server/src/services/contract/contract.service.ts b/server/src/services/contract/contract.service.ts index 80753f3..df7bad6 100644 --- a/server/src/services/contract/contract.service.ts +++ b/server/src/services/contract/contract.service.ts @@ -20,12 +20,19 @@ export async function getCompiledContract(onAccount: EncodedData<'ak'>) { return contract; } +/** + * Makes a random pick and returns calldata for `player1_move` method + */ export function getRandomMoveCallData(contract: ContractInstance) { const randomMove = Math.floor(Math.random() * 3); const move = Object.values(Moves)[randomMove]; return contract.calldata.encode(CONTRACT_NAME, Methods.player1_move, [move]); } +/** + * deploys the contract on channel and returns the instance and its address + * @param config - parameters used in contract's `init` method {@link 'https://github.com/aeternity/state-channel-demo/blob/develop/contract/contracts/RockPaperScissors.aes#L29'} + */ export async function deployContract( deployerAddress: EncodedData<'ak'>, channel: Channel, @@ -59,6 +66,9 @@ export async function deployContract( }; } +/** + * extracts latest callData and generates returns next callData to be sent + */ export async function getNextCallData( update: Update, contract: ContractInstance, diff --git a/server/src/services/sdk/sdk.service.ts b/server/src/services/sdk/sdk.service.ts index cc6ae76..e2516ef 100644 --- a/server/src/services/sdk/sdk.service.ts +++ b/server/src/services/sdk/sdk.service.ts @@ -37,6 +37,10 @@ export const genesisFund = async (address: EncodedData<'ak'>) => { if (sdk.accounts[FAUCET_PUBLIC_ADDRESS]) sdk.removeAccount(FAUCET_PUBLIC_ADDRESS); }; +/** + * Funds account with 5AE. + * Sometimes, the faucet may throw an error, so we retry the operation. + */ export async function fundThroughFaucet( account: EncodedData<'ak'>, options: { @@ -69,6 +73,9 @@ export async function fundThroughFaucet( } } +/** + * Funds account based on which network is used. + */ export async function fundAccount(account: EncodedData<'ak'>) { if (!IS_USING_LOCAL_NODE) { try { @@ -89,6 +96,9 @@ export async function fundAccount(account: EncodedData<'ak'>) { } } +/** + * Wrapper function to decode callData. + */ export async function decodeCallData( calldata: EncodedData<'cb'>, bytecode: string, diff --git a/server/test/integration/bot.service.spec.ts b/server/test/integration/bot.service.spec.ts index 6784dfd..8547845 100644 --- a/server/test/integration/bot.service.spec.ts +++ b/server/test/integration/bot.service.spec.ts @@ -110,6 +110,8 @@ describe('botService', () => { it('bot makes a random pick, player reveals and the game is complete', async () => { const playerSdk = await getSdk(); + + // The responder needs a contract instance in order to call contract const contract = (await playerSdk.getContractInstance({ source: contractSource, })) as RockPaperScissorsContract; @@ -122,6 +124,9 @@ describe('botService', () => { 3001, ); + // we need the contract creation round in order + // to fetch the contract address deployed by + // the initiator let contractCreationRound = '-1'; const playerChannel = await Channel.initialize({ @@ -143,14 +148,20 @@ describe('botService', () => { }, }); + // wait for channel to be opened await waitForChannelReady(playerChannel); expect(playerChannel.status()).toBe('open'); + + // wait for contract to be deployed await timeout(4000); + + // build contract address const contractAddress = buildContractId( responderConfig.initiatorId, parseInt(contractCreationRound, 10), ); + // arguments for contract's `provide_hash` method const hashKey = 'Aeternity'; const pick = Moves.paper; const dummyHash = createHash(pick, hashKey); @@ -171,9 +182,9 @@ describe('botService', () => { async (tx) => playerSdk.signTransaction(tx), ); + // wait for the next round await pollForRound(playerChannel.round() + 1, playerChannel); - const nextRound = playerChannel.round() + 1; await playerChannel.callContract( { amount: 0, @@ -187,7 +198,7 @@ describe('botService', () => { async (tx) => playerSdk.signTransaction(tx), ); - await pollForRound(nextRound, playerChannel); + await pollForRound(playerChannel.round() + 1, playerChannel); const result = await playerChannel.getContractCall({ caller: responderConfig.responderId, From 65b4b67e1c7a3f428e5f4e096972f5cd546afbe4 Mon Sep 17 00:00:00 2001 From: Kalovelo Date: Fri, 29 Jul 2022 11:28:53 +0300 Subject: [PATCH 2/4] chore(server): pump @aeternity/aepp-sdk to 12.1.1 --- server/package-lock.json | 174 +++++++++++++----- server/package.json | 2 +- server/src/route/route.interface.ts | 4 +- server/src/services/bot/bot.interface.ts | 10 +- server/src/services/bot/bot.service.spec.ts | 6 +- server/src/services/bot/bot.service.ts | 12 +- .../src/services/contract/contract.service.ts | 14 +- server/src/services/sdk/sdk.constants.ts | 8 +- server/src/services/sdk/sdk.interface.ts | 6 +- server/src/services/sdk/sdk.service.ts | 10 +- server/test/integration/bot.service.spec.ts | 8 +- 11 files changed, 168 insertions(+), 86 deletions(-) diff --git a/server/package-lock.json b/server/package-lock.json index 2ea0078..84749be 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "@aeternity/aepp-sdk": "^12.0.0", + "@aeternity/aepp-sdk": "^12.1.1", "@aeternity/rock-paper-scissors": "../contract", "axios": "^0.27.2", "bignumber.js": "^9.0.2", @@ -68,11 +68,11 @@ } }, "node_modules/@aeternity/aepp-calldata": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@aeternity/aepp-calldata/-/aepp-calldata-1.1.1.tgz", - "integrity": "sha512-1c8KgDMMOrtQFUq3XJ4mSt62mLaFnyiWCLaKeX4smmaEYW/C9sc4RfjzUzL/cb/ou/+G8XTqv5U1exgn1OcTrw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@aeternity/aepp-calldata/-/aepp-calldata-1.2.0.tgz", + "integrity": "sha512-hrlT8B4I7clcGSypqT3ZAuuQbMjH3WD/xr4wnVoW6TrGZ7+0dk8xrgkx3yeZDeR5kGexwVq9Yqm/CGs9kfvOgQ==", "dependencies": { - "@aeternity/blakejs": "^1.1.2", + "blakejs": "^1.2.1", "bs58": "^4.0.1", "rlp": "^3.0.0", "safe-buffer": "^5.2.1", @@ -96,16 +96,23 @@ } }, "node_modules/@aeternity/aepp-sdk": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/@aeternity/aepp-sdk/-/aepp-sdk-12.0.0.tgz", - "integrity": "sha512-mLG+edg+7TliFA0OqkJZ4J685mN0XJTO6sKF1K2rNoXIduy7AyRpDiW+lPlQr2SEgXJ15sR0J3cmDzkdJwDjaQ==", + "version": "12.1.1", + "resolved": "https://registry.npmjs.org/@aeternity/aepp-sdk/-/aepp-sdk-12.1.1.tgz", + "integrity": "sha512-QUB/PoLjdiRYkPOHWT/z9LwTBfPkBYaM/4Mjbw0YNs1W16asL+d8E1XFZjJtMyCJNreooHd9IqPWWMV9GL7abw==", "dependencies": { - "@aeternity/aepp-calldata": "^1.1.1", + "@aeternity/aepp-calldata": "^1.2.0", "@aeternity/argon2-browser": "^0.1.2", "@aeternity/uuid": "^0.0.1", "@azure/core-client": "^1.6.0", "@azure/core-rest-pipeline": "^1.9.0", - "@babel/runtime-corejs3": "^7.18.3", + "@babel/runtime-corejs3": "^7.18.9", + "@types/aes-js": "^3.1.1", + "@types/argon2-browser": "^1.18.1", + "@types/json-bigint": "^1.0.1", + "@types/sha.js": "^2.4.0", + "@types/uuid": "^8.3.4", + "@types/webextension-polyfill": "^0.9.0", + "@types/websocket": "^1.0.5", "aes-js": "^3.1.2", "bignumber.js": "^9.0.2", "bip32-path": "^0.4.2", @@ -128,11 +135,6 @@ "resolved": "https://registry.npmjs.org/@aeternity/argon2-browser/-/argon2-browser-0.1.2.tgz", "integrity": "sha512-nL3xZYf0JXVIm6HFyppXn9fgHjmSjYa1PBYXzD1/pLbECdikD+r3IuhIR3dAwn8nq9xShjHPr9ZnJUbbUvva4g==" }, - "node_modules/@aeternity/blakejs": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@aeternity/blakejs/-/blakejs-1.1.2.tgz", - "integrity": "sha512-RoLxLy3huy3glASelIzVMHjtXNgdFCVbYwHZYBSFcQ08KIWSBH2H6S67SzHzWf3+8bWUCMuvgt7qhamWlZV6eQ==" - }, "node_modules/@aeternity/rock-paper-scissors": { "resolved": "../contract", "link": true @@ -1989,9 +1991,9 @@ } }, "node_modules/@babel/runtime-corejs3": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.18.3.tgz", - "integrity": "sha512-l4ddFwrc9rnR+EJsHsh+TJ4A35YqQz/UqcjtlX2ov53hlJYG5CxtQmNZxyajwDVmCxwy++rtvGU5HazCK4W41Q==", + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.18.9.tgz", + "integrity": "sha512-qZEWeccZCrHA2Au4/X05QW5CMdm4VjUDCrGq5gf1ZDcM4hRqreKrtwAn7yci9zfgAS9apvnsFXiGBHBAxZdK9A==", "dependencies": { "core-js-pure": "^3.20.2", "regenerator-runtime": "^0.13.4" @@ -3098,6 +3100,16 @@ "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", "dev": true }, + "node_modules/@types/aes-js": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/aes-js/-/aes-js-3.1.1.tgz", + "integrity": "sha512-SDSGgXT3LRCH6qMWk8OHT1vLSVNuHNvCpKCx2/TYtQMbMGGgxJC9fspwSkQjqzRagrWnCrxuLL3jMNXLXHHvSw==" + }, + "node_modules/@types/argon2-browser": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/@types/argon2-browser/-/argon2-browser-1.18.1.tgz", + "integrity": "sha512-PZffP/CqH9m2kovDSRQMfMMxUC3V98I7i7/caa0RB0/nvsXzYbL9bKyqZpNMFmLFGZslROlG1R60ONt7abrwlA==" + }, "node_modules/@types/babel__core": { "version": "7.1.19", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.19.tgz", @@ -3236,6 +3248,11 @@ "pretty-format": "^28.0.0" } }, + "node_modules/@types/json-bigint": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/json-bigint/-/json-bigint-1.0.1.tgz", + "integrity": "sha512-zpchZLNsNuzJHi6v64UBoFWAvQlPhch7XAi36FkH6tL1bbbmimIF+cS7vwkzY4u5RaSWMoflQfu+TshMPPw8uw==" + }, "node_modules/@types/json-schema": { "version": "7.0.11", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", @@ -3263,8 +3280,7 @@ "node_modules/@types/node": { "version": "18.0.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.0.0.tgz", - "integrity": "sha512-cHlGmko4gWLVI27cGJntjs/Sj8th9aYwplmZFwmmgYQQvL5NUsgVJG7OddLvNfLqYS31KFN0s3qlaD9qCaxACA==", - "dev": true + "integrity": "sha512-cHlGmko4gWLVI27cGJntjs/Sj8th9aYwplmZFwmmgYQQvL5NUsgVJG7OddLvNfLqYS31KFN0s3qlaD9qCaxACA==" }, "node_modules/@types/normalize-package-data": { "version": "2.4.1", @@ -3306,6 +3322,14 @@ "@types/node": "*" } }, + "node_modules/@types/sha.js": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/sha.js/-/sha.js-2.4.0.tgz", + "integrity": "sha512-amxKgPy6WJTKuw8mpUwjX2BSxuBtBmZfRwIUDIuPJKNwGN8CWDli8JTg5ONTWOtcTkHIstvT7oAhhYXqEjStHQ==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/stack-utils": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", @@ -3331,6 +3355,24 @@ "@types/superagent": "*" } }, + "node_modules/@types/uuid": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz", + "integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==" + }, + "node_modules/@types/webextension-polyfill": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@types/webextension-polyfill/-/webextension-polyfill-0.9.0.tgz", + "integrity": "sha512-HG1y1o2hK8ag6Y7dfkrAbfKmMIP+B0E6SwAzUfmQ1dDxEIdLTtMyrStY26suHBPrAL7Xw/chlDW02ugc3uXWtQ==" + }, + "node_modules/@types/websocket": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/websocket/-/websocket-1.0.5.tgz", + "integrity": "sha512-NbsqiNX9CnEfC1Z0Vf4mE1SgAJ07JnRYcNex7AJ9zAVzmiGHmjKFEk7O4TJIsgv2B1sLEb6owKFZrACwdYngsQ==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/yargs": { "version": "17.0.10", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.10.tgz", @@ -4711,9 +4753,9 @@ } }, "node_modules/core-js-pure": { - "version": "3.23.2", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.23.2.tgz", - "integrity": "sha512-t6u7H4Ff/yZNk+zqTr74UjCcZ3k8ApBryeLLV4rYQd9aF3gqmjjGjjR44ENfeBMH8VVvSynIjAJ0mUuFhzQtrA==", + "version": "3.24.0", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.24.0.tgz", + "integrity": "sha512-uzMmW8cRh7uYw4JQtzqvGWRyC2T5+4zipQLQdi2FmiRqP83k3d6F3stv2iAlNhOs6cXN401FCD5TL0vvleuHgA==", "hasInstallScript": true, "funding": { "type": "opencollective", @@ -10816,11 +10858,11 @@ }, "dependencies": { "@aeternity/aepp-calldata": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@aeternity/aepp-calldata/-/aepp-calldata-1.1.1.tgz", - "integrity": "sha512-1c8KgDMMOrtQFUq3XJ4mSt62mLaFnyiWCLaKeX4smmaEYW/C9sc4RfjzUzL/cb/ou/+G8XTqv5U1exgn1OcTrw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@aeternity/aepp-calldata/-/aepp-calldata-1.2.0.tgz", + "integrity": "sha512-hrlT8B4I7clcGSypqT3ZAuuQbMjH3WD/xr4wnVoW6TrGZ7+0dk8xrgkx3yeZDeR5kGexwVq9Yqm/CGs9kfvOgQ==", "requires": { - "@aeternity/blakejs": "^1.1.2", + "blakejs": "^1.2.1", "bs58": "^4.0.1", "rlp": "^3.0.0", "safe-buffer": "^5.2.1", @@ -10846,16 +10888,23 @@ } }, "@aeternity/aepp-sdk": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/@aeternity/aepp-sdk/-/aepp-sdk-12.0.0.tgz", - "integrity": "sha512-mLG+edg+7TliFA0OqkJZ4J685mN0XJTO6sKF1K2rNoXIduy7AyRpDiW+lPlQr2SEgXJ15sR0J3cmDzkdJwDjaQ==", + "version": "12.1.1", + "resolved": "https://registry.npmjs.org/@aeternity/aepp-sdk/-/aepp-sdk-12.1.1.tgz", + "integrity": "sha512-QUB/PoLjdiRYkPOHWT/z9LwTBfPkBYaM/4Mjbw0YNs1W16asL+d8E1XFZjJtMyCJNreooHd9IqPWWMV9GL7abw==", "requires": { - "@aeternity/aepp-calldata": "^1.1.1", + "@aeternity/aepp-calldata": "^1.2.0", "@aeternity/argon2-browser": "^0.1.2", "@aeternity/uuid": "^0.0.1", "@azure/core-client": "^1.6.0", "@azure/core-rest-pipeline": "^1.9.0", - "@babel/runtime-corejs3": "^7.18.3", + "@babel/runtime-corejs3": "^7.18.9", + "@types/aes-js": "^3.1.1", + "@types/argon2-browser": "^1.18.1", + "@types/json-bigint": "^1.0.1", + "@types/sha.js": "^2.4.0", + "@types/uuid": "^8.3.4", + "@types/webextension-polyfill": "^0.9.0", + "@types/websocket": "^1.0.5", "aes-js": "^3.1.2", "bignumber.js": "^9.0.2", "bip32-path": "^0.4.2", @@ -10878,11 +10927,6 @@ "resolved": "https://registry.npmjs.org/@aeternity/argon2-browser/-/argon2-browser-0.1.2.tgz", "integrity": "sha512-nL3xZYf0JXVIm6HFyppXn9fgHjmSjYa1PBYXzD1/pLbECdikD+r3IuhIR3dAwn8nq9xShjHPr9ZnJUbbUvva4g==" }, - "@aeternity/blakejs": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@aeternity/blakejs/-/blakejs-1.1.2.tgz", - "integrity": "sha512-RoLxLy3huy3glASelIzVMHjtXNgdFCVbYwHZYBSFcQ08KIWSBH2H6S67SzHzWf3+8bWUCMuvgt7qhamWlZV6eQ==" - }, "@aeternity/rock-paper-scissors": { "version": "file:../contract", "requires": { @@ -12201,9 +12245,9 @@ } }, "@babel/runtime-corejs3": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.18.3.tgz", - "integrity": "sha512-l4ddFwrc9rnR+EJsHsh+TJ4A35YqQz/UqcjtlX2ov53hlJYG5CxtQmNZxyajwDVmCxwy++rtvGU5HazCK4W41Q==", + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.18.9.tgz", + "integrity": "sha512-qZEWeccZCrHA2Au4/X05QW5CMdm4VjUDCrGq5gf1ZDcM4hRqreKrtwAn7yci9zfgAS9apvnsFXiGBHBAxZdK9A==", "requires": { "core-js-pure": "^3.20.2", "regenerator-runtime": "^0.13.4" @@ -13067,6 +13111,16 @@ "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", "dev": true }, + "@types/aes-js": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/aes-js/-/aes-js-3.1.1.tgz", + "integrity": "sha512-SDSGgXT3LRCH6qMWk8OHT1vLSVNuHNvCpKCx2/TYtQMbMGGgxJC9fspwSkQjqzRagrWnCrxuLL3jMNXLXHHvSw==" + }, + "@types/argon2-browser": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/@types/argon2-browser/-/argon2-browser-1.18.1.tgz", + "integrity": "sha512-PZffP/CqH9m2kovDSRQMfMMxUC3V98I7i7/caa0RB0/nvsXzYbL9bKyqZpNMFmLFGZslROlG1R60ONt7abrwlA==" + }, "@types/babel__core": { "version": "7.1.19", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.19.tgz", @@ -13205,6 +13259,11 @@ "pretty-format": "^28.0.0" } }, + "@types/json-bigint": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/json-bigint/-/json-bigint-1.0.1.tgz", + "integrity": "sha512-zpchZLNsNuzJHi6v64UBoFWAvQlPhch7XAi36FkH6tL1bbbmimIF+cS7vwkzY4u5RaSWMoflQfu+TshMPPw8uw==" + }, "@types/json-schema": { "version": "7.0.11", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", @@ -13232,8 +13291,7 @@ "@types/node": { "version": "18.0.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.0.0.tgz", - "integrity": "sha512-cHlGmko4gWLVI27cGJntjs/Sj8th9aYwplmZFwmmgYQQvL5NUsgVJG7OddLvNfLqYS31KFN0s3qlaD9qCaxACA==", - "dev": true + "integrity": "sha512-cHlGmko4gWLVI27cGJntjs/Sj8th9aYwplmZFwmmgYQQvL5NUsgVJG7OddLvNfLqYS31KFN0s3qlaD9qCaxACA==" }, "@types/normalize-package-data": { "version": "2.4.1", @@ -13275,6 +13333,14 @@ "@types/node": "*" } }, + "@types/sha.js": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/sha.js/-/sha.js-2.4.0.tgz", + "integrity": "sha512-amxKgPy6WJTKuw8mpUwjX2BSxuBtBmZfRwIUDIuPJKNwGN8CWDli8JTg5ONTWOtcTkHIstvT7oAhhYXqEjStHQ==", + "requires": { + "@types/node": "*" + } + }, "@types/stack-utils": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", @@ -13300,6 +13366,24 @@ "@types/superagent": "*" } }, + "@types/uuid": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz", + "integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==" + }, + "@types/webextension-polyfill": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@types/webextension-polyfill/-/webextension-polyfill-0.9.0.tgz", + "integrity": "sha512-HG1y1o2hK8ag6Y7dfkrAbfKmMIP+B0E6SwAzUfmQ1dDxEIdLTtMyrStY26suHBPrAL7Xw/chlDW02ugc3uXWtQ==" + }, + "@types/websocket": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/websocket/-/websocket-1.0.5.tgz", + "integrity": "sha512-NbsqiNX9CnEfC1Z0Vf4mE1SgAJ07JnRYcNex7AJ9zAVzmiGHmjKFEk7O4TJIsgv2B1sLEb6owKFZrACwdYngsQ==", + "requires": { + "@types/node": "*" + } + }, "@types/yargs": { "version": "17.0.10", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.10.tgz", @@ -14286,9 +14370,9 @@ } }, "core-js-pure": { - "version": "3.23.2", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.23.2.tgz", - "integrity": "sha512-t6u7H4Ff/yZNk+zqTr74UjCcZ3k8ApBryeLLV4rYQd9aF3gqmjjGjjR44ENfeBMH8VVvSynIjAJ0mUuFhzQtrA==" + "version": "3.24.0", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.24.0.tgz", + "integrity": "sha512-uzMmW8cRh7uYw4JQtzqvGWRyC2T5+4zipQLQdi2FmiRqP83k3d6F3stv2iAlNhOs6cXN401FCD5TL0vvleuHgA==" }, "cors": { "version": "2.8.5", diff --git a/server/package.json b/server/package.json index ef3e44e..198b6ad 100644 --- a/server/package.json +++ b/server/package.json @@ -29,7 +29,7 @@ "author": "", "license": "ISC", "dependencies": { - "@aeternity/aepp-sdk": "^12.0.0", + "@aeternity/aepp-sdk": "^12.1.1", "@aeternity/rock-paper-scissors": "../contract", "axios": "^0.27.2", "bignumber.js": "^9.0.2", diff --git a/server/src/route/route.interface.ts b/server/src/route/route.interface.ts index 4cf92c4..380274d 100644 --- a/server/src/route/route.interface.ts +++ b/server/src/route/route.interface.ts @@ -1,7 +1,7 @@ -import { EncodedData } from '@aeternity/aepp-sdk/es/utils/encoder'; +import { Encoded } from '@aeternity/aepp-sdk/es/utils/encoder'; export interface ResponderBaseChannelConfig { - address: EncodedData<'ak'>; + address: Encoded.AccountAddress; port: number; host: string; } diff --git a/server/src/services/bot/bot.interface.ts b/server/src/services/bot/bot.interface.ts index ce53bb2..1b35371 100644 --- a/server/src/services/bot/bot.interface.ts +++ b/server/src/services/bot/bot.interface.ts @@ -1,16 +1,16 @@ import { Channel } from '@aeternity/aepp-sdk'; import { ContractInstance } from '@aeternity/aepp-sdk/es/contract/aci'; -import { EncodedData } from '@aeternity/aepp-sdk/es/utils/encoder'; +import { Encoded } from '@aeternity/aepp-sdk/es/utils/encoder'; export interface GameSession { channel: Channel; contractState?: { instance?: ContractInstance; - callDataToSend?: EncodedData<'cb'>; - address?: EncodedData<'ct'>; + callDataToSend?: Encoded.ContractBytearray; + address?: Encoded.ContractAddress; }; participants: { - responderId: EncodedData<'ak'>; - initiatorId: EncodedData<'ak'>; + responderId: Encoded.AccountAddress; + initiatorId: Encoded.AccountAddress; }; } diff --git a/server/src/services/bot/bot.service.spec.ts b/server/src/services/bot/bot.service.spec.ts index f39d2e5..5bdb115 100644 --- a/server/src/services/bot/bot.service.spec.ts +++ b/server/src/services/bot/bot.service.spec.ts @@ -1,6 +1,6 @@ import { Channel, generateKeyPair, MemoryAccount } from '@aeternity/aepp-sdk'; import { ChannelOptions } from '@aeternity/aepp-sdk/es/channel/internal'; -import { EncodedData } from '@aeternity/aepp-sdk/es/utils/encoder'; +import { Encoded } from '@aeternity/aepp-sdk/es/utils/encoder'; import BigNumber from 'bignumber.js'; import { mockChannel, timeout } from '../../../test'; import { ContractService } from '../contract'; @@ -33,8 +33,8 @@ describe('botService', () => { lockPeriod: 1, debug: false, role: 'initiator', - initiatorId: 'ak_initiator' as EncodedData<'ak'>, - responderId: 'ak_responder' as EncodedData<'ak'>, + initiatorId: 'ak_initiator' as Encoded.AccountAddress, + responderId: 'ak_responder' as Encoded.AccountAddress, sign: () => Promise.resolve('tx_txdata'), }; diff --git a/server/src/services/bot/bot.service.ts b/server/src/services/bot/bot.service.ts index 391d9d8..6c365b5 100644 --- a/server/src/services/bot/bot.service.ts +++ b/server/src/services/bot/bot.service.ts @@ -1,6 +1,6 @@ import { Channel, generateKeyPair, MemoryAccount } from '@aeternity/aepp-sdk'; import { ChannelOptions } from '@aeternity/aepp-sdk/es/channel/internal'; -import { EncodedData } from '@aeternity/aepp-sdk/es/utils/encoder'; +import { Encoded } from '@aeternity/aepp-sdk/es/utils/encoder'; import BigNumber from 'bignumber.js'; import logger from '../../logger'; import { @@ -51,7 +51,7 @@ export async function addGameSession( /** * Removes game session from the pool after the channel is closed */ -export function removeGameSession(onAccount: EncodedData<'ak'>) { +export function removeGameSession(onAccount: Encoded.AccountAddress) { gameSessionPool.delete(onAccount); logger.info( `Removed from pool game session with bot ID: ${onAccount}. Total sessions: ${gameSessionPool.size}`, @@ -61,7 +61,7 @@ export function removeGameSession(onAccount: EncodedData<'ak'>) { /** * Returns funds to the faucet and removes the game session from the pool */ -export async function handleChannelClose(onAccount: EncodedData<'ak'>) { +export async function handleChannelClose(onAccount: Encoded.AccountAddress) { try { await sdk.transferFunds(1, FAUCET_PUBLIC_ADDRESS, { onAccount, @@ -102,7 +102,7 @@ async function respondToContractCall(gameSession: GameSession) { abiVersion: CONTRACT_CONFIGURATION.abiVersion, callData: gameSession.contractState.callDataToSend, }, - async (tx: EncodedData<'tx'>) => sdk.signTransaction(tx, { + async (tx: Encoded.Transaction) => sdk.signTransaction(tx, { onAccount: gameSession.participants.initiatorId, }), ); @@ -149,7 +149,7 @@ export async function registerEvents( * @returns mutual channel configuration */ export async function generateGameSession( - playerAddress: EncodedData<'ak'>, + playerAddress: Encoded.AccountAddress, playerNodeHost: string, playerNodePort: number, ) { @@ -178,7 +178,7 @@ export async function generateGameSession( // @ts-ignore sign: ( _tag: string, - tx: EncodedData<'tx'>, + tx: Encoded.Transaction, options: { updates: Update[]; }, diff --git a/server/src/services/contract/contract.service.ts b/server/src/services/contract/contract.service.ts index df7bad6..03ecaeb 100644 --- a/server/src/services/contract/contract.service.ts +++ b/server/src/services/contract/contract.service.ts @@ -1,6 +1,6 @@ import { Channel } from '@aeternity/aepp-sdk'; import { ContractInstance } from '@aeternity/aepp-sdk/es/contract/aci'; -import { EncodedData } from '@aeternity/aepp-sdk/es/utils/encoder'; +import { Encoded } from '@aeternity/aepp-sdk/es/utils/encoder'; import contractSource from '@aeternity/rock-paper-scissors'; import logger from '../../logger'; import { decodeCallData, sdk, Update } from '../sdk'; @@ -11,7 +11,7 @@ import { Moves, } from './contract.constants'; -export async function getCompiledContract(onAccount: EncodedData<'ak'>) { +export async function getCompiledContract(onAccount: Encoded.AccountAddress) { const contract = await sdk.getContractInstance({ source: contractSource, onAccount, @@ -34,11 +34,11 @@ export function getRandomMoveCallData(contract: ContractInstance) { * @param config - parameters used in contract's `init` method {@link 'https://github.com/aeternity/state-channel-demo/blob/develop/contract/contracts/RockPaperScissors.aes#L29'} */ export async function deployContract( - deployerAddress: EncodedData<'ak'>, + deployerAddress: Encoded.AccountAddress, channel: Channel, config: { - player0: EncodedData<'ak'>; - player1: EncodedData<'ak'>; + player0: Encoded.AccountAddress; + player1: Encoded.AccountAddress; reactionTime: number; debugTimestamp?: number; }, @@ -51,7 +51,7 @@ export async function deployContract( code: contract.bytecode, callData: contract.calldata.encode(CONTRACT_NAME, Methods.init, [ ...Object.values(config), - ]) as string, + ]) as Encoded.ContractBytearray, }, async (tx) => sdk.signTransaction(tx, { onAccount: deployerAddress, @@ -62,7 +62,7 @@ export async function deployContract( return { instance: contract, address: res.address } as { instance: ContractInstance; - address: EncodedData<'ct'>; + address: Encoded.ContractAddress; }; } diff --git a/server/src/services/sdk/sdk.constants.ts b/server/src/services/sdk/sdk.constants.ts index 275ade5..f16517e 100644 --- a/server/src/services/sdk/sdk.constants.ts +++ b/server/src/services/sdk/sdk.constants.ts @@ -1,5 +1,5 @@ import { MemoryAccount } from '@aeternity/aepp-sdk'; -import { EncodedData } from '@aeternity/aepp-sdk/es/utils/encoder'; +import { Encoded } from '@aeternity/aepp-sdk/es/utils/encoder'; import env from '../../env'; const ENVIRONMENT = process.env.NODE_ENV as keyof typeof env; @@ -10,10 +10,8 @@ export const NETWORK_ID = env[ENVIRONMENT]?.NETWORK_ID ?? process.env.NETWORK_ID export const IGNORE_NODE_VERSION = env[ENVIRONMENT]?.IGNORE_VERSION === 'true' ?? process.env.IGNORE_VERSION === 'true'; export const FAUCET_PUBLIC_ADDRESS = env[ENVIRONMENT]?.FAUCET_PUBLIC_ADDRESS - ?? (process.env.FAUCET_PUBLIC_ADDRESS as EncodedData<'ak'>); -export const IS_USING_LOCAL_NODE = !NODE_URL?.includes( - 'testnet.aeternity.io', -); + ?? (process.env.FAUCET_PUBLIC_ADDRESS as Encoded.AccountAddress); +export const IS_USING_LOCAL_NODE = !NODE_URL?.includes('testnet.aeternity.io'); // ! LOCAL NODE USAGE ONLY const FAUCET_SECRET_KEY = (ENVIRONMENT === 'development' && env[ENVIRONMENT]?.FAUCET_SECRET_KEY) || process.env.FAUCET_SECRET_KEY; diff --git a/server/src/services/sdk/sdk.interface.ts b/server/src/services/sdk/sdk.interface.ts index b69ba6f..58f9ae2 100644 --- a/server/src/services/sdk/sdk.interface.ts +++ b/server/src/services/sdk/sdk.interface.ts @@ -1,7 +1,7 @@ -import { EncodedData } from '@aeternity/aepp-sdk/es/utils/encoder'; +import { Encoded } from '@aeternity/aepp-sdk/es/utils/encoder'; export interface Update { - call_data: EncodedData<'cb'>; - contract_id: EncodedData<'ct'>; + call_data: Encoded.ContractBytearray; + contract_id: Encoded.ContractAddress; op: 'OffChainCallContract' | 'OffChainNewContract'; } diff --git a/server/src/services/sdk/sdk.service.ts b/server/src/services/sdk/sdk.service.ts index e2516ef..e4d0b3a 100644 --- a/server/src/services/sdk/sdk.service.ts +++ b/server/src/services/sdk/sdk.service.ts @@ -1,4 +1,4 @@ -import { EncodedData } from '@aeternity/aepp-sdk/es/utils/encoder'; +import { Encoded } from '@aeternity/aepp-sdk/es/utils/encoder'; import { AeSdk, Node } from '@aeternity/aepp-sdk'; import axios, { AxiosError } from 'axios'; import BigNumber from 'bignumber.js'; @@ -29,7 +29,7 @@ export const sdk = new AeSdk({ * ! LOCAL NODE USAGE ONLY * a helper function to fund account */ -export const genesisFund = async (address: EncodedData<'ak'>) => { +export const genesisFund = async (address: Encoded.AccountAddress) => { if (!IS_USING_LOCAL_NODE) throw new Error('genesis fund is only for local node usage'); await sdk.addAccount(FAUCET_ACCOUNT, { select: true }); await sdk.awaitHeight(2); @@ -42,7 +42,7 @@ export const genesisFund = async (address: EncodedData<'ak'>) => { * Sometimes, the faucet may throw an error, so we retry the operation. */ export async function fundThroughFaucet( - account: EncodedData<'ak'>, + account: Encoded.AccountAddress, options: { maxRetries?: number; } = { @@ -76,7 +76,7 @@ export async function fundThroughFaucet( /** * Funds account based on which network is used. */ -export async function fundAccount(account: EncodedData<'ak'>) { +export async function fundAccount(account: Encoded.AccountAddress) { if (!IS_USING_LOCAL_NODE) { try { await fundThroughFaucet(account, { @@ -100,7 +100,7 @@ export async function fundAccount(account: EncodedData<'ak'>) { * Wrapper function to decode callData. */ export async function decodeCallData( - calldata: EncodedData<'cb'>, + calldata: Encoded.ContractBytearray, bytecode: string, ) { return sdk.compilerApi.decodeCalldataBytecode({ diff --git a/server/test/integration/bot.service.spec.ts b/server/test/integration/bot.service.spec.ts index 8547845..2e87220 100644 --- a/server/test/integration/bot.service.spec.ts +++ b/server/test/integration/bot.service.spec.ts @@ -4,7 +4,7 @@ import { sha256hash, unpackTx, } from '@aeternity/aepp-sdk'; -import { EncodedData } from '@aeternity/aepp-sdk/es/utils/encoder'; +import { Encoded } from '@aeternity/aepp-sdk/es/utils/encoder'; import contractSource from '@aeternity/rock-paper-scissors'; import BigNumber from 'bignumber.js'; import botService from '../../src/services/bot'; @@ -46,7 +46,7 @@ describe('botService', () => { const playerChannel = await Channel.initialize({ ...responderConfig, role: 'responder', - sign: (_tag: string, tx: EncodedData<'tx'>, options) => { + sign: (_tag: string, tx: Encoded.Transaction, options) => { // @ts-expect-error if (options?.updates[0]?.op === 'OffChainNewContract') { // @ts-expect-error @@ -89,7 +89,7 @@ describe('botService', () => { const playerChannel = await Channel.initialize({ ...responderConfig, role: 'responder', - sign: (_tag: string, tx: EncodedData<'tx'>) => playerSdk.signTransaction(tx), + sign: (_tag: string, tx: Encoded.Transaction) => playerSdk.signTransaction(tx), }); await waitForChannelReady(playerChannel); await timeout(5000); @@ -135,7 +135,7 @@ describe('botService', () => { // @ts-expect-error sign: async ( _tag: string, - tx: EncodedData<'tx'>, + tx: Encoded.Transaction, options: { updates: Update[]; }, From 7c51fc5024550cf83ad96283da0a84b2df8c1ae0 Mon Sep 17 00:00:00 2001 From: Kalovelo Date: Fri, 29 Jul 2022 12:56:19 +0300 Subject: [PATCH 3/4] test(server): unit test contract methods without channel dep --- docker-compose.yml | 2 +- .../contract/contract.service.spec.ts | 86 ++++++++++++++++++- .../src/services/contract/contract.service.ts | 12 +-- server/src/services/sdk/sdk.service.ts | 1 + server/test/integration/bot.service.spec.ts | 19 ++-- server/test/utils.ts | 9 +- 6 files changed, 110 insertions(+), 19 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index b4a9676..0f7388f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,7 +2,7 @@ version: "3.8" services: node: - image: aeternity/aeternity:${NODE_TAG-v6.5.1} + image: aeternity/aeternity:${NODE_TAG-v6.5.2} hostname: node ports: ["3013:3013", "3113:3113", "3014:3014", "3114:3114"] volumes: diff --git a/server/src/services/contract/contract.service.spec.ts b/server/src/services/contract/contract.service.spec.ts index 41a05b4..8804c6e 100644 --- a/server/src/services/contract/contract.service.spec.ts +++ b/server/src/services/contract/contract.service.spec.ts @@ -1,7 +1,91 @@ -import { ContractService } from '.'; +import { ContractInstance } from '@aeternity/aepp-sdk/es/contract/aci'; +import contractSource from '@aeternity/rock-paper-scissors'; +import { CONTRACT_NAME, Methods, Moves } from './contract.constants'; +import { ContractService, RockPaperScissorsContract } from '.'; +import { decodeCallData, sdk, Update } from '../sdk'; +import { createHash } from '../../../test'; describe('ContractService', () => { it('should be defined', () => { expect(ContractService).toBeDefined(); }); + + let contract: ContractInstance; + + beforeAll(async () => { + contract = (await sdk.getContractInstance({ + source: contractSource, + })) as RockPaperScissorsContract; + await contract.compile(); + }); + + describe('getRandomMoveCallData()', () => { + it('should generate callData with either rock, paper, or scissors', async () => { + const callData = ContractService.getRandomMoveCallData(contract); + const decodedCallData = await decodeCallData(callData, contract.bytecode); + + const { arguments: usedArguments } = decodedCallData; + + expect(usedArguments.length).toBe(1); + expect(decodedCallData.function).toBe('player1_move'); + expect( + Object.values(Moves).includes(usedArguments[0].value as Moves), + ).toBeTruthy(); + }); + }); + + describe('getNextCallData()', () => { + it('should make a move when last called method was provide_hash', async () => { + const hashKey = 'Aeternity'; + const pick = Moves.paper; + const dummyHash = createHash(pick, hashKey); + + const callData = contract.calldata.encode( + CONTRACT_NAME, + Methods.provide_hash, + [dummyHash], + ); + + const update: Update = { + call_data: callData, + contract_id: 'ct_', + op: 'OffChainCallContract', + }; + + const nextCallData = await ContractService.getNextCallData( + update, + contract, + ); + + const decodedCallData = await decodeCallData( + nextCallData, + contract.bytecode, + ); + const { arguments: usedArguments } = decodedCallData; + + expect(usedArguments.length).toBe(1); + expect(decodedCallData.function).toBe('player1_move'); + expect( + Object.values(Moves).includes(usedArguments[0].value as Moves), + ).toBeTruthy(); + }); + + it('should throw an error if update contains an unhandled method', async () => { + const callData = contract.calldata.encode( + CONTRACT_NAME, + Methods.set_timestamp, + [1], + ); + + const update: Update = { + call_data: callData, + contract_id: 'ct_', + op: 'OffChainCallContract', + }; + + await expect( + ContractService.getNextCallData(update, contract), + ).rejects.toThrow('Unhandled method: set_timestamp'); + }); + }); }); diff --git a/server/src/services/contract/contract.service.ts b/server/src/services/contract/contract.service.ts index 03ecaeb..798410a 100644 --- a/server/src/services/contract/contract.service.ts +++ b/server/src/services/contract/contract.service.ts @@ -11,6 +11,9 @@ import { Moves, } from './contract.constants'; +/** + * @category sdk-wrapper + */ export async function getCompiledContract(onAccount: Encoded.AccountAddress) { const contract = await sdk.getContractInstance({ source: contractSource, @@ -23,7 +26,9 @@ export async function getCompiledContract(onAccount: Encoded.AccountAddress) { /** * Makes a random pick and returns calldata for `player1_move` method */ -export function getRandomMoveCallData(contract: ContractInstance) { +export function getRandomMoveCallData( + contract: ContractInstance, +): Encoded.ContractBytearray { const randomMove = Math.floor(Math.random() * 3); const move = Object.values(Moves)[randomMove]; return contract.calldata.encode(CONTRACT_NAME, Methods.player1_move, [move]); @@ -60,10 +65,7 @@ export async function deployContract( logger.info(`${deployerAddress} deployed contract: ${res.address}`); - return { instance: contract, address: res.address } as { - instance: ContractInstance; - address: Encoded.ContractAddress; - }; + return { instance: contract, address: res.address }; } /** diff --git a/server/src/services/sdk/sdk.service.ts b/server/src/services/sdk/sdk.service.ts index e4d0b3a..5a17eb3 100644 --- a/server/src/services/sdk/sdk.service.ts +++ b/server/src/services/sdk/sdk.service.ts @@ -97,6 +97,7 @@ export async function fundAccount(account: Encoded.AccountAddress) { } /** + * @category sdk-wrapper * Wrapper function to decode callData. */ export async function decodeCallData( diff --git a/server/test/integration/bot.service.spec.ts b/server/test/integration/bot.service.spec.ts index 2e87220..9a198bb 100644 --- a/server/test/integration/bot.service.spec.ts +++ b/server/test/integration/bot.service.spec.ts @@ -1,9 +1,4 @@ -import { - buildContractId, - Channel, - sha256hash, - unpackTx, -} from '@aeternity/aepp-sdk'; +import { buildContractId, Channel, unpackTx } from '@aeternity/aepp-sdk'; import { Encoded } from '@aeternity/aepp-sdk/es/utils/encoder'; import contractSource from '@aeternity/rock-paper-scissors'; import BigNumber from 'bignumber.js'; @@ -20,11 +15,13 @@ import { import { Update } from '../../src/services/sdk'; import { FAUCET_PUBLIC_ADDRESS } from '../../src/services/sdk/sdk.constants'; import { - getSdk, pollForRound, timeout, waitForChannelReady, + createHash, + getSdk, + pollForRound, + timeout, + waitForChannelReady, } from '../utils'; -const createHash = (move: Moves, key: string) => sha256hash(key + move); - describe('botService', () => { jest.setTimeout(30000); @@ -184,7 +181,7 @@ describe('botService', () => { // wait for the next round await pollForRound(playerChannel.round() + 1, playerChannel); - + const nextRound = playerChannel.round() + 1; await playerChannel.callContract( { amount: 0, @@ -198,7 +195,7 @@ describe('botService', () => { async (tx) => playerSdk.signTransaction(tx), ); - await pollForRound(playerChannel.round() + 1, playerChannel); + await pollForRound(nextRound, playerChannel); const result = await playerChannel.getContractCall({ caller: responderConfig.responderId, diff --git a/server/test/utils.ts b/server/test/utils.ts index 3d553cb..e348bfa 100644 --- a/server/test/utils.ts +++ b/server/test/utils.ts @@ -4,8 +4,10 @@ import { MemoryAccount, generateKeyPair, Channel, + sha256hash, } from '@aeternity/aepp-sdk'; import { setTimeout as awaitSetTimeout } from 'timers/promises'; +import { Moves } from '../src/services/contract'; import { NETWORK_ID, COMPILER_URL, @@ -37,7 +39,10 @@ export const getSdk = async () => { return sdk; }; -export async function waitForChannelReady(channel: Channel, statuses = ['open']): Promise { +export async function waitForChannelReady( + channel: Channel, + statuses = ['open'], +): Promise { return new Promise((resolve) => { channel.on('statusChanged', (newStatus: string) => { if (statuses.includes(newStatus)) { @@ -54,3 +59,5 @@ export async function pollForRound(desiredRound: number, channel: Channel) { currentRound = channel.round(); } } + +export const createHash = (move: Moves, key: string) => sha256hash(key + move); From 6fc8f3fd5cfab52b63345394d527246b1b108e3a Mon Sep 17 00:00:00 2001 From: Kalovelo Date: Fri, 29 Jul 2022 13:18:08 +0300 Subject: [PATCH 4/4] chore(server): replace deprecated function sha256hash --- server/src/services/contract/contract.service.spec.ts | 2 +- server/test/integration/bot.service.spec.ts | 2 +- server/test/utils.ts | 6 ++++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/server/src/services/contract/contract.service.spec.ts b/server/src/services/contract/contract.service.spec.ts index 8804c6e..d818fb0 100644 --- a/server/src/services/contract/contract.service.spec.ts +++ b/server/src/services/contract/contract.service.spec.ts @@ -38,7 +38,7 @@ describe('ContractService', () => { it('should make a move when last called method was provide_hash', async () => { const hashKey = 'Aeternity'; const pick = Moves.paper; - const dummyHash = createHash(pick, hashKey); + const dummyHash = await createHash(pick, hashKey); const callData = contract.calldata.encode( CONTRACT_NAME, diff --git a/server/test/integration/bot.service.spec.ts b/server/test/integration/bot.service.spec.ts index 9a198bb..ffb2089 100644 --- a/server/test/integration/bot.service.spec.ts +++ b/server/test/integration/bot.service.spec.ts @@ -161,7 +161,7 @@ describe('botService', () => { // arguments for contract's `provide_hash` method const hashKey = 'Aeternity'; const pick = Moves.paper; - const dummyHash = createHash(pick, hashKey); + const dummyHash = await createHash(pick, hashKey); const callData = contract.calldata.encode( CONTRACT_NAME, diff --git a/server/test/utils.ts b/server/test/utils.ts index e348bfa..1dbd2ac 100644 --- a/server/test/utils.ts +++ b/server/test/utils.ts @@ -4,9 +4,9 @@ import { MemoryAccount, generateKeyPair, Channel, - sha256hash, } from '@aeternity/aepp-sdk'; import { setTimeout as awaitSetTimeout } from 'timers/promises'; +import Crypto from 'crypto'; import { Moves } from '../src/services/contract'; import { NETWORK_ID, @@ -60,4 +60,6 @@ export async function pollForRound(desiredRound: number, channel: Channel) { } } -export const createHash = (move: Moves, key: string) => sha256hash(key + move); +export const createHash = async (move: Moves, key: string) => Crypto.createHash('sha256') + .update(key + move) + .digest('hex');