diff --git a/.test/docker-compose.yml b/.test/docker-compose.yml index 5e6aa4a7..f28df0ad 100644 --- a/.test/docker-compose.yml +++ b/.test/docker-compose.yml @@ -2,7 +2,7 @@ version: "2" services: echo: - image: echoprotocol/echo:0.17.1-rc.5 + image: echoprotocol/echo:0.18.0-rc.7 command: --start-echorand --account-info="[\"1.2.6\", \"5KkYp8qdQBaRmLqLz8WVrGjzkt7E13qVcr7cpdLowgJ1mjRyDx2\"]" @@ -24,11 +24,11 @@ services: container_name: echo echo-wallet: - image: echoprotocol/echo-wallet:0.17.1-rc.5 + image: echoprotocol/echo-wallet:0.18.0-rc.7 container_name: echo-wallet command: -s ws://echo:6311 - --chain-id="8cc5ced88147e2b6bef07d0c016274fb3dca8db8d1f20f1d15b5e34fa80cb89f" + --chain-id="ae55e9cc332c46bdc8d4eac06e6e909ca51ad074150ce42775ee90345fdeff0e" --wallet-file="/echo/walletdata/wallet.json" --history-file="/echo/walletdata/history" -r 0.0.0.0:6312 diff --git a/.test/genesis.json b/.test/genesis.json index 6ba80be3..0a8c4487 100644 --- a/.test/genesis.json +++ b/.test/genesis.json @@ -107,6 +107,7 @@ "eth_update_addr_method": { "method": "7ff203ab", "gas": 1000000 }, "eth_withdraw_token_method": { "method": "1c69c0e2", "gas": 1000000 }, "eth_collect_tokens_method": { "method": "5940a240", "gas": 1000000 }, + "eth_update_contract_address": { "method": "3659cfe6", "gas": 1000000 }, "eth_committee_updated_topic": "514bf7702a7d2aca90dcf3d947158aad29563a17c1dbdc76d2eae84c22420142", "eth_gen_address_topic": "1855f12530a368418f19b2b15227f19225915b8113c7e17d4c276e2a10225039", "eth_deposit_topic": "77227a376c41a7533c952ebde8d7b44ee36c7a6cec0d3448f1a1e4231398356f", @@ -217,6 +218,5 @@ "eth_address": "298915A83Bd8a0D536aDBe300F12cA1e4388C3BA", "btc_public_key": "02c16e97132e72738c9c0163656348cd1be03521de17efeb07e496e742ac84512e" }], - "initial_chain_id": "aa34045518f1469a28fa4578240d5f039afa9959c0b95ce3b39674efa691fb21", - "immutable_parameters": { "min_committee_member_count": 5 } + "initial_chain_id": "aa34045518f1469a28fa4578240d5f039afa9959c0b95ce3b39674efa691fb21" } diff --git a/docs/API.md b/docs/API.md index cff7bc58..f47cc07a 100644 --- a/docs/API.md +++ b/docs/API.md @@ -988,9 +988,6 @@ try { { id:String, chain_id:String, - immutable_parameters:{ - min_committee_member_count:Number - } } ``` @@ -1039,6 +1036,7 @@ try { eth_gen_address_method:{method:String,gas:Number}, eth_withdraw_method:{method:String,gas:Number}, eth_update_addr_method:{method:String,gas:Number}, + eth_update_contract_address:{method:String,gas:Number}, eth_withdraw_token_method:{method:String,gas:Number}, eth_collect_tokens_method:{method:String,gas:Number}, eth_committee_updated_topic:String, @@ -1048,14 +1046,13 @@ try { erc20_deposit_topic:String, erc20_withdraw_topic:String, ETH_asset_id:String, - waiting_eth_blocks:Number, - fines:{generate_eth_address:Number}, - waiting_blocks:Number, BTC_asset_id:String, - waiting_btc_blocks:Number, + fines:{generate_eth_address:Number|String}, + gas_price:Number|String, satoshis_per_byte:Number, - echo_blocks_per_aggregation:Number, - gas_price:String, + coefficient_waiting_blocks:Number, + btc_deposit_withdrawal_min:Number|String, + btc_deposit_withdrawal_fee:Number|String, }, gas_price:{ price:Number|String, diff --git a/docs/Serializers.md b/docs/Serializers.md index 9369a7ae..6b908324 100644 --- a/docs/Serializers.md +++ b/docs/Serializers.md @@ -331,8 +331,8 @@ Available protocol object id serializers: * `vestingBalanceId` * `balanceId` * `contractId` -* `depositId` -* `withdrawId` +* `ethDepositId` +* `ethWithdrawId` * `erc20TokenId` Example: @@ -434,6 +434,7 @@ console.log(s.serialize(input).toString('hex')); |eth_gen_address_method|[`eth method`](#eth-method)| |eth_withdraw_method|[`eth method`](#eth-method)| |eth_update_addr_method|[`eth method`](#eth-method)| +|eth_update_contract_address|[`eth method`](#eth-method)| |eth_withdraw_token_method|[`eth method`](#eth-method)| |eth_collect_tokens_method|[`eth method`](#eth-method)| |eth_committee_updated_topic|[`eth topic`](#eth-topic)| @@ -443,13 +444,13 @@ console.log(s.serialize(input).toString('hex')); |erc20_deposit_topic|[`eth topic`](#eth-topic)| |erc20_withdraw_topic|[`eth topic`](#eth-topic)| |ETH_asset_id|[`assetId`](#protocol-object-id)| -|waiting_eth_blocks|[`uint32`](#integers)| -|fines|[`fines`](#fines)| -|waiting_blocks|[`uint32`](#integers)| |BTC_asset_id|[`assetId`](#protocol-object-id)| -|waiting_btc_blocks|[`uint32`](#integers)| +|fines|[`fines`](#fines)|: typeof sidechainFinesSerializer, +|gas_price|[`uint64`](#integers)| |satoshis_per_byte|[`uint32`](#integers)| -|echo_blocks_per_aggregation|[`uint32`](#integers)| +|coefficient_waiting_blocks|[`uint32`](#integers)| +|btc_deposit_withdrawal_min|[`uint64`](#integers)| +|btc_deposit_withdrawal_fee|[`uint64`](#integers)| #### ERC20 config diff --git a/package.json b/package.json index 7dbc4054..adf615e1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "echojs-lib", - "version": "1.12.0-rc.0", + "version": "1.12.0-rc.1", "description": "Pure JavaScript ECHO library for node.js", "main": "./dist/index.js", "types": "./types/index.d.ts", diff --git a/src/constants/chain-config.js b/src/constants/chain-config.js index 9a4d9fe6..ee1097fa 100644 --- a/src/constants/chain-config.js +++ b/src/constants/chain-config.js @@ -3,6 +3,7 @@ export const ADDRESS_PREFIX = 'ECHO'; export const EXPIRE_IN_SECONDS = 15; export const EXPIRE_IN_SECONDS_PROPOSAL = 24 * 60 * 60; export const REVIEW_IN_SECONDS_COMMITTEE = 24 * 60 * 60; +export const CHAIN_ID_LENGTH = 64; export const NETWORKS = { ECHO_DEV: { diff --git a/src/constants/chain/config.js b/src/constants/chain/config.js new file mode 100644 index 00000000..69283acd --- /dev/null +++ b/src/constants/chain/config.js @@ -0,0 +1,2 @@ +// eslint-disable-next-line import/prefer-default-export +export const ECHO_MAX_SHARE_SUPPLY = 1000000000000000; diff --git a/src/constants/chain/index.js b/src/constants/chain/index.js new file mode 100644 index 00000000..90b987eb --- /dev/null +++ b/src/constants/chain/index.js @@ -0,0 +1,4 @@ +import * as config from './config'; + +// eslint-disable-next-line import/prefer-default-export +export { config }; diff --git a/src/constants/index.js b/src/constants/index.js index ae76747d..5f1e30b7 100644 --- a/src/constants/index.js +++ b/src/constants/index.js @@ -2,6 +2,7 @@ import BigNumber from 'bignumber.js'; import * as API_CONFIG from './api-config'; import * as CACHE_MAPS from './cache-maps'; +import * as chain from './chain'; import * as CHAIN_CONFIG from './chain-config'; import * as CHAIN_TYPES from './chain-types'; import * as PROTOCOL_OBJECT_TYPE_ID from './object-types'; @@ -35,6 +36,7 @@ export const DEFAULT_MIN_CACHE_CLEANING_TIME = 500; /** @typedef {typeof OPERATIONS_IDS[keyof typeof OPERATIONS_IDS]} OperationId */ export { + chain, OPERATIONS_IDS, CACHE_MAPS, CHAIN_TYPES, diff --git a/src/constants/object-types.js b/src/constants/object-types.js index b906f86b..67d5f979 100644 --- a/src/constants/object-types.js +++ b/src/constants/object-types.js @@ -15,8 +15,8 @@ export const ETH_ADDRESS = 13; export const SIDECHAIN_ETH_DEPOSIT = 14; export const SIDECHAIN_ETH_WITHDRAW = 15; export const ERC20_TOKEN = 16; -export const SIDECHAIN_ERC20_DEPOSIT_TOKEN = 17; -export const SIDECHAIN_ERC20_WITHDRAW_TOKEN = 18; +export const ERC20_DEPOSIT_TOKEN = 17; +export const ERC20_WITHDRAW_TOKEN = 18; export const BTC_ADDRESS = 19; export const BTC_INTERMEDIATE_DEPOSIT = 20; export const BTC_DEPOSIT = 21; diff --git a/src/crypto/ecdsa.js b/src/crypto/ecdsa.js index 99b3d04d..0ccc5395 100644 --- a/src/crypto/ecdsa.js +++ b/src/crypto/ecdsa.js @@ -13,7 +13,7 @@ import { validateUnsignedSafeInteger } from '../utils/validators'; */ export function deterministicGenerateK(curve, hash, d, checkSig, nonce) { if (nonce) hash = sha256(Buffer.concat([hash, Buffer.alloc(nonce)])); - // sanity check + // sanity transaction.js if (hash.length !== 32) throw new Error('invalid sha256 hash length'); const x = d.toBuffer(32); let k = Buffer.alloc(32); diff --git a/src/echo/api.js b/src/echo/api.js index 80f200d8..5d4f5483 100644 --- a/src/echo/api.js +++ b/src/echo/api.js @@ -1,8 +1,8 @@ /* eslint-disable no-continue,no-await-in-loop,camelcase,no-restricted-syntax */ +import * as assert from 'assert'; import { ok } from 'assert'; -import BigNumber from 'bignumber.js'; import { Map, List, fromJS } from 'immutable'; - +import BigNumber from 'bignumber.js'; import { isNumber, isArray, @@ -27,10 +27,9 @@ import { isOperationId, isDynamicGlobalObjectId, isBtcAddressId, - isUInt32, isObject, isInt64, - validateSidechainType, + validateSidechainType, isUInt32, } from '../utils/validators'; import { solveRegistrationTask, validateRegistrationOptions } from '../utils/pow-solver'; @@ -38,10 +37,14 @@ import { solveRegistrationTask, validateRegistrationOptions } from '../utils/pow /** @typedef {import('./ws-api').default} WSAPI */ import { ECHO_ASSET_ID, DYNAMIC_GLOBAL_OBJECT_ID, API_CONFIG, CACHE_MAPS } from '../constants'; -import { transaction, signedTransaction, operation, basic } from '../serializers'; +import { transaction, signedTransaction, operation, basic, chain } from '../serializers'; import { PublicKey } from '../crypto'; +import { toRawContractLogsFilterOptions } from '../utils/converters'; +/** @typedef {import("bignumber.js").default} BigNumber */ +/** @typedef {import("../../types/interfaces/vm/types").Log} Log */ /** @typedef {import("./ws-api/database-api").SidechainType} SidechainType */ +/** @typedef {typeof basic.integers["uint64"]["__TInput__"]} UInt64 */ /** @typedef { * { @@ -108,9 +111,6 @@ import { PublicKey } from '../crypto'; * { * id:String, * chain_id:String, -* immutable_parameters:{ -* min_committee_member_count:Number -* } * } * } ChainProperties */ @@ -156,6 +156,7 @@ import { PublicKey } from '../crypto'; * eth_gen_address_method:{method:String,gas:Number}, * eth_withdraw_method:{method:String,gas:Number}, * eth_update_addr_method:{method:String,gas:Number}, +* eth_update_contract_address:{method:String,gas:Number}, * eth_withdraw_token_method:{method:String,gas:Number}, * eth_collect_tokens_method:{method:String,gas:Number}, * eth_committee_updated_topic:String, @@ -165,14 +166,13 @@ import { PublicKey } from '../crypto'; * erc20_deposit_topic:String, * erc20_withdraw_topic:String, * ETH_asset_id:String, -* waiting_eth_blocks:Number, -* fines:{generate_eth_address:Number}, -* waiting_blocks:Number, * BTC_asset_id:String, -* waiting_btc_blocks:Number, +* fines:{generate_eth_address:Number|String}, +* gas_price:Number|String, * satoshis_per_byte:Number, -* echo_blocks_per_aggregation:Number, -* gas_price:String, +* coefficient_waiting_blocks:Number, +* btc_deposit_withdrawal_min:Number|String, +* btc_deposit_withdrawal_fee:Number|String, * }, * gas_price:{ * price:Number|String, @@ -1943,13 +1943,13 @@ class API { */ async getContractLogs(opts = { }) { if (opts.contracts !== undefined) { - ok(Array.isArray(opts.contracts), '"contracts" option is not an array'); - for (const contractId of opts.contracts) ok(isContractId(contractId)); + ok(Array.isArray(opts.contracts), 'contracts: vector is not an array'); + for (const contractId of opts.contracts) ok(isContractId(contractId), 'invalid object type id'); } /** @type {typeof opts["topics"]} */ let topics; if (opts.topics !== undefined) { - ok(Array.isArray(opts.topics), '"topics" option is not an array'); + ok(Array.isArray(opts.topics), '`topics` is not an array'); topics = new Array(opts.topics.length).fill(null); for (let topicIndex = 0; topicIndex < opts.topics.length; topicIndex += 1) { let topicVariants = opts.topics[topicIndex]; @@ -1983,6 +1983,24 @@ class API { }); } + /** + * @param {(result: Log[]) => any} callback + * @param {ContractLogsFilterOptions_t} [opts] + * @returns {Promise} + */ + async subscribeContractLogs(callback, opts = {}) { + return this.wsApi.database.subscribeContractLogs((result) => { + assert.ok(Array.isArray(result)); + assert.strictEqual(result.length, 1); + callback(result[0]); + }, toRawContractLogsFilterOptions(opts)); + } + + /** @param {UInt64} subscribeId */ + async unsubscribeContractLogs(subscribeId) { + return this.wsApi.database.unsubscribeContractLogs(basic.integers.uint64.toRaw(subscribeId)); + } + /** * @method getContractResult * @@ -2301,6 +2319,22 @@ class API { ); } + /** + * @param {typeof chain.ids["protocol"]["contractId"]["__TInput__"]} contract + * @param {Object} [options] + * @param {typeof basic.integers["uint64"]["__TInput__"]} [options.stop] + * @param {typeof basic.integers["uint32"]["__TInput__"]} [options.limit] + * @param {typeof basic.integers["uint64"]["__TInput__"]} [options.start] + */ + async getRelativeContractHistory(contract, options = {}) { + contract = chain.ids.protocol.contractId.toRaw(contract); + const stop = options.stop === undefined ? 0 : basic.integers.uint64.toRaw(options.stop); + const limit = options.limit === undefined ? 100 : basic.integers.uint32.toRaw(options.limit); + const start = options.stop === undefined ? 0 : basic.integers.uint64.toRaw(options.start); + if (limit > 100) throw new Error('Limit is greater than 100'); + return this.wsApi.history.getRelativeContractHistory(contract, stop, limit, start); + } + /** * @method getFullContract * Get contract info. @@ -2466,15 +2500,6 @@ class API { return this.wsApi.database.getBalanceObjects(keys); } - /** - * @method getBlockRewards - * @param {typeof uint32["__TInput__"]} blockNum - * @returns {Promise} - */ - getBlockRewards(blockNum) { - return this.wsApi.database.getBlockRewards(basic.integers.uint32.toRaw(blockNum)); - } - /** * * @param {Number} blockNum diff --git a/src/echo/subscriber.js b/src/echo/subscriber.js index 77be9da7..ec124764 100644 --- a/src/echo/subscriber.js +++ b/src/echo/subscriber.js @@ -38,6 +38,8 @@ import { import { handleConnectionClosedError } from '../utils/helpers'; import { IMPLEMENTATION_OBJECT_TYPE_ID } from '../constants/chain-types'; +/** @typedef {import("../../types/interfaces/vm/types").Log} Log */ + class Subscriber extends EventEmitter { /** @@ -183,7 +185,7 @@ class Subscriber extends EventEmitter { * @return {null} */ _updateObject(object) { - // check is id param exists -> if no - check settle order params + // transaction.js is id param exists -> if no - transaction.js settle order params if (!object.id) { if (object.balance && object.owner && object.settlement_date) { this.emit('settle-order-update', object); @@ -204,7 +206,7 @@ class Subscriber extends EventEmitter { [], ); - // check interested by id type + // transaction.js interested by id type if (isTransactionId(object.id)) { return null; } @@ -286,7 +288,7 @@ class Subscriber extends EventEmitter { return null; } - // check if dynamic global object + // transaction.js if dynamic global object if (isDynamicGlobalObjectId(object.id)) { const dynamicGlobalObject = new Map(object); @@ -495,7 +497,7 @@ class Subscriber extends EventEmitter { const orders = []; const updates = messages.filter((msg) => { - // check is object id + // transaction.js is object id if (isObjectId(msg)) { return false; } @@ -897,8 +899,7 @@ class Subscriber extends EventEmitter { } /** - * @method _contractLogsUpdate - * + * @method _contractLogsUpdate * @param {Array>} contractLogsMap * @param {*} result * @@ -970,18 +971,6 @@ class Subscriber extends EventEmitter { await this._subscribeToContractLogs(contractsToSubscribe); } - /** - * @method removeContractLogsSubscribe - * - * @param {String} contractId - * @param {Function} callback - * - * @return {undefined} - */ - removeContractLogsSubscribe(contractId, callback) { - this.subscribers.logs[contractId] = this.subscribers.logs[contractId] - .filter((c) => (c !== callback)); - } /** * @method _subscribeCache diff --git a/src/echo/transaction.js b/src/echo/transaction.js index 4ec9c7af..b2d9b155 100644 --- a/src/echo/transaction.js +++ b/src/echo/transaction.js @@ -2,11 +2,17 @@ import BigNumber from 'bignumber.js'; import { cloneDeep } from 'lodash'; import Api from './api'; -import { isObject, validateUnsignedSafeInteger } from '../utils/validators'; +import { + isObject, + validateUnsignedSafeInteger, + validatePositiveSafeInteger, + isHex, + isUInt32, +} from '../utils/validators'; import PrivateKey from '../crypto/private-key'; import PublicKey from '../crypto/public-key'; import Signature from '../crypto/signature'; -import { ECHO_ASSET_ID, DYNAMIC_GLOBAL_OBJECT_ID } from '../constants'; +import { ECHO_ASSET_ID, DYNAMIC_GLOBAL_OBJECT_ID, CHAIN_CONFIG } from '../constants'; import { EXPIRATION_SECONDS } from '../constants/api-config'; import { transaction, signedTransaction, operation } from '../serializers'; @@ -17,23 +23,55 @@ import { transaction, signedTransaction, operation } from '../serializers'; class Transaction { /** - * @readonly * @type {number} */ get refBlockNum() { - this.checkFinalized(); return this._refBlockNum; } + /** @param {number|undefined} value */ + set refBlockNum(value) { + validatePositiveSafeInteger(value); + if ((value > 0xffff)) throw new Error('number is not safe'); + this._refBlockNum = value; + } + /** * @readonly * @type {number} */ get refBlockPrefix() { - this.checkFinalized(); return this._refBlockPrefix; } + /** @param {string|number|undefined} value */ + set refBlockPrefix(value) { + if (isHex(value)) { + this._refBlockPrefix = Buffer.from(value, 'hex').readUInt32LE(4); + return; + } + if (isUInt32(value)) { + this._refBlockPrefix = value; + return; + } + throw new Error('invalid refBlockPrefix format'); + } + + /** @param {string|undefined} value */ + set chainId(value) { + if (isHex(value) && value.length === CHAIN_CONFIG.CHAIN_ID_LENGTH) { + this._chainId = value; + return; + } + throw new Error('invalid chainId format or length'); + } + + /** + * @readonly + * @type {string} + */ + get chainId() { return this._chainId; } + /** * @readonly * @type {Array<_Operation>} @@ -44,14 +82,20 @@ class Transaction { * @readonly * @type {boolean} */ - get finalized() { return this._finalized; } + get finalized() { + return this._refBlockNum !== undefined && + this._refBlockPrefix !== undefined && !!this._chainId && this.hasAllFees; + } /** @type {Api} */ - get api() { return this._api; } + get api() { + if (!this._api) throw new Error('Api instance does not exist, check your connection'); + return this._api; + } /** @param {Api} value */ set api(value) { - if (!(value instanceof Api)) throw new Error('value is not a Api instance'); + if (value && !(value instanceof Api)) throw new Error('value is not a Api instance'); /** * @private * @type {Api} @@ -98,11 +142,6 @@ class Transaction { * @type {Array} */ this._signatures = []; - /** - * @private - * @type {boolean} - */ - this._finalized = false; /** * @private * @type {number} @@ -113,6 +152,14 @@ class Transaction { checkNotFinalized() { if (this.finalized) throw new Error('already finalized'); } checkFinalized() { if (!this.finalized) throw new Error('transaction is not finalized'); } + async getGlobalChainData() { + if (this.refBlockPrefix && this.refBlockNum) { + return { refBlockNum: this.refBlockNum, refBlockPrefix: this.refBlockPrefix }; + } + const globalChainData = await this.api.getObject(DYNAMIC_GLOBAL_OBJECT_ID, true); + return { refBlockNum: globalChainData.head_block_number, refBlockPrefix: globalChainData.head_block_id }; + } + /** * @param {OperationId} operationId * @param {{ [key: string]: any }} [props] @@ -200,7 +247,6 @@ class Transaction { * @returns {Transaction} */ addSigner(privateKey, publicKey = privateKey.toPublicKey()) { - this.checkNotFinalized(); if (Buffer.isBuffer(privateKey)) privateKey = PrivateKey.fromBuffer(privateKey); if (!(privateKey instanceof PrivateKey)) throw new Error('private key is not instance of PrivateKey class'); if (!(publicKey instanceof PublicKey)) throw new Error('public key is not instance of PublicKey class'); @@ -216,26 +262,24 @@ class Transaction { * @param {PrivateKey=} _privateKey */ async sign(_privateKey) { - this.checkNotFinalized(); if (_privateKey !== undefined) this.addSigner(_privateKey); - if (!this.hasAllFees) await this.setRequiredFees(); - const dynamicGlobalChainData = await this.api.getObject(DYNAMIC_GLOBAL_OBJECT_ID, true); + + if (!this.finalized) { + const { refBlockNum, refBlockPrefix } = await this.getGlobalChainData(); + + if (!this.hasAllFees) await this.setRequiredFees(); + if (!this.refBlockNum) { + this.refBlockNum = refBlockNum; + } + if (!this.refBlockPrefix) { + this.refBlockPrefix = refBlockPrefix; + } + if (!this.chainId) { + this.chainId = await this.api.getChainId(); + } + } if (this.expiration === undefined) this.expiration = Math.ceil(Date.now() / 1e3) + EXPIRATION_SECONDS; - const chainId = await this.api.getChainId(); - // one more check to avoid that the sign method was called several times - // without waiting for the first call to be executed - this.checkNotFinalized(); - this._finalized = true; - /** - * @private - * @type {number|undefined} - */ - this._refBlockNum = dynamicGlobalChainData.head_block_number & 0xffff; // eslint-disable-line no-bitwise - /** - * @private - * @type {number|undefined} - */ - this._refBlockPrefix = Buffer.from(dynamicGlobalChainData.head_block_id, 'hex').readUInt32LE(4); + const transactionBuffer = transaction.serialize({ ref_block_num: this.refBlockNum, ref_block_prefix: this.refBlockPrefix, @@ -244,7 +288,7 @@ class Transaction { extensions: [], }); - const chainBuffer = Buffer.from(chainId, 'hex'); + const chainBuffer = Buffer.from(this.chainId, 'hex'); const bufferToSign = Buffer.concat([chainBuffer, Buffer.from(transactionBuffer)]); this._signatures = this._signers.map(({ privateKey }) => Signature.signBuffer(bufferToSign, privateKey)); } @@ -277,6 +321,7 @@ class Transaction { operations: this.operations, extensions: [], signatures: this._signatures.map((signature) => signature.toBuffer()), + signed_with_echorand_key: false, }); } @@ -284,6 +329,10 @@ class Transaction { return transaction.serialize(this.transactionObject); } + signedTransactionSerializer() { + return signedTransaction.serialize(this.transactionObject); + } + /** * @param {()=>* =} wasBroadcastedCallback * @returns {Promise<*>} diff --git a/src/echo/ws-api/database-api.js b/src/echo/ws-api/database-api.js index cd7d4c47..677c0384 100644 --- a/src/echo/ws-api/database-api.js +++ b/src/echo/ws-api/database-api.js @@ -1,3 +1,5 @@ +/** @typedef {import("../../../types/interfaces/vm/types").Log} Log */ + /** @typedef {"" | "eth" | "btc"} SidechainType */ class DatabaseAPI { @@ -510,16 +512,14 @@ class DatabaseAPI { } /** - * @method subscribeContractLogs - * - * @param {Function} callback - * @param {Array>} contractTopicsMap - * - * @return {Promise} + * @param {(result: Log[]) => any} cb + * @param {ContractLogsFilterOptionsRaw} options + * @returns {Promise} */ - subscribeContractLogs(callback, contractTopicsMap) { - return this.db.exec('subscribe_contract_logs', [callback, contractTopicsMap]); - } + subscribeContractLogs(cb, options) { return this.db.exec('subscribe_contract_logs', [cb, options]); } + + /** @param {number|string} cbId */ + unsubscribeContractLogs(cbId) { return this.db.exec('unsubscribe_contract_logs', [cbId]); } /** * @method getContractResult @@ -632,15 +632,6 @@ class DatabaseAPI { return this.db.exec('get_balance_objects', [keys]); } - /** - * @method getBlockRewards - * @param {number} blockNum - * @returns {Promise} - */ - getBlockRewards(blockNum) { - return this.db.exec('get_block_rewards', [blockNum]); - } - /** * @method getBlockVirtualOperations * diff --git a/src/echo/ws-api/history-api.js b/src/echo/ws-api/history-api.js index 865d1821..39571d30 100644 --- a/src/echo/ws-api/history-api.js +++ b/src/echo/ws-api/history-api.js @@ -1,5 +1,9 @@ import { START_OPERATION_ID } from '../../constants'; +/** @typedef {import("../../../types/interfaces/chain").OperationHistoryObject} OperationHistoryObject */ +/** @typedef {import("../../serializers/basic/integers")} Integer_t */ +/** @typedef {import("../../serializers/chain/id/protocol")["contractId"]["__TOutput__"]} ContractId */ + class HistoryAPI { /** @@ -78,6 +82,17 @@ class HistoryAPI { return this.db.exec('get_contract_history', [contractId, stop, limit, start]); } + /** + * @param {ContractId} contract + * @param {Integer_t["uint64"]["__TOutput__"]} stop + * @param {Integer_t["uint32"]["__TOutput__"]} limit + * @param {Integer_t["uint64"]["__TOutput__"]} start + * @returns {Promise} + */ + getRelativeContractHistory(contract, stop, limit, start) { + return this.db.exec('get_relative_contract_history', [contract, stop, limit, start]); + } + } export default HistoryAPI; diff --git a/src/echo/ws-api/wallet-api.js b/src/echo/ws-api/wallet-api.js index df3a72b4..c95d62f3 100644 --- a/src/echo/ws-api/wallet-api.js +++ b/src/echo/ws-api/wallet-api.js @@ -19,6 +19,7 @@ import { isPublicKey, isUInt64, isCommitteeMemberId, + validateAmount, } from '../../utils/validators'; const { ethAddress, accountListing } = serializers.protocol; @@ -29,7 +30,7 @@ const { ripemd160, } = serializers.chain; const { options, bitassetOptions } = serializers.protocol.asset; -const { priceFeed } = serializers.protocol; +const { price } = serializers.protocol; const { config } = serializers.plugins.echorand; const { anyObjectId } = serializers.chain.ids; @@ -65,6 +66,7 @@ const { committeeMemberId, } = serializers.chain.ids.protocol; +/** @typedef {import("bignumber.js").BigNumber} BigNumber */ /** * @typedef {typeof import("../../serializers/transaction")['signedTransactionSerializer']} SignedTransactionSerializer */ @@ -445,7 +447,7 @@ class WalletAPI { * Upload/Create a contract. * @param {string} accountNameOrId name of the account creating the contract * @param {string} contractCode code of the contract - * @param {number} amount the amount of asset transferred to the contract + * @param {number|string|BigNumber} amount the amount of asset transferred to the contract * @param {string} assetType the type of the asset transferred to the contract * @param {string} supportedAssetId the asset that can be used to create/call the contract * (see https://echo-dev.io/developers/smart-contracts/solidity/introduction/#flag-of-supported-asset) @@ -454,7 +456,7 @@ class WalletAPI { * @param {boolean} shouldSaveToWallet whether to save the contract to the wallet * @returns {Promise} the signed transaction creating the contract */ - createContract( + async createContract( accountNameOrId, contractCode, amount, @@ -463,14 +465,13 @@ class WalletAPI { useEthereumAssetAccuracy, shouldSaveToWallet, ) { - if (!isAccountIdOrName(accountNameOrId)) { - return Promise.reject(new Error('Accounts id or name should be string and valid')); - } - if (!isContractCode(contractCode)) return Promise.reject(new Error('Byte code should be string and valid')); + if (!isAccountIdOrName(accountNameOrId)) throw new Error('Accounts id or name should be string and valid'); + if (!isContractCode(contractCode)) throw new Error('Byte code should be string and valid'); + amount = validateAmount(amount); return this.wsRpc.call([0, 'create_contract', [ string.toRaw(accountNameOrId), string.toRaw(contractCode), - uint64.toRaw(amount), + string.toRaw(amount), assetId.toRaw(assetType), assetId.toRaw(supportedAssetId), bool.toRaw(useEthereumAssetAccuracy), @@ -483,12 +484,12 @@ class WalletAPI { * @param {string} accountNameOrId name of the account calling the contract * @param {string} idOfContract the id of the contract to call * @param {string} contractCode the hash of the method to call - * @param {number} amount the amount of asset transferred to the contract + * @param {number|BigNumber|string} amount the amount of asset transferred to the contract * @param {string} assetType the type of the asset transferred to the contract * @param {boolean} shouldSaveToWallet whether to save the contract call to the wallet * @returns {Promise} the signed transaction calling the contract */ - callContract( + async callContract( accountNameOrId, idOfContract, contractCode, @@ -496,15 +497,14 @@ class WalletAPI { assetType, shouldSaveToWallet, ) { - if (!isAccountIdOrName(accountNameOrId)) { - return Promise.reject(new Error('Accounts id or name should be string and valid')); - } - if (!isContractCode(contractCode)) return Promise.reject(new Error('Byte code should be string and valid')); + if (!isAccountIdOrName(accountNameOrId)) throw new Error('Accounts id or name should be string and valid'); + if (!isContractCode(contractCode)) throw new Error('Byte code should be string and valid'); + amount = validateAmount(amount); return this.wsRpc.call([0, 'call_contract', [ string.toRaw(accountNameOrId), contractId.toRaw(idOfContract), string.toRaw(contractCode), - uint64.toRaw(amount), + string.toRaw(amount), assetId.toRaw(assetType), bool.toRaw(shouldSaveToWallet), ]]); @@ -542,6 +542,35 @@ class WalletAPI { return this.wsRpc.call([0, 'get_contract_result', [string.toRaw(contractResultId)]]); } + /** + * Returns the most recent operations on the contract id. + * + * This returns a list of operation history objects, which describe activity on the contract. + * @param {typeof contractId["__TInput__"]} _contractId the ID of the contract + * @param {typeof uint32["__TInput__"]} limit the number of entries to return (starting from the most recent) + * @returns {Promise} a list of accounts mapping account names to account ids + */ + async getContractHistory(_contractId, limit) { + return this.wsRpc.call([0, 'get_contract_history', [contractId.toRaw(_contractId), uint32.toRaw(limit)]]); + } + + /** + * Returns the relative operations on the id contract from start number + * @param {typeof contractId["__TInput__"]} _contractId the ID of the contract + * @param {typeof uint32["__TInput__"]} stop Sequence number of earliest operation + * @param {typeof uint32["__TInput__"]} limit the number of entries to return + * @param {typeof uint32["__TInput__"]} start the sequence number where to start looping back throw the history + * @returns {Promise} a list of operation history objects + */ + async getRelativeContractHistory(_contractId, stop, limit, start) { + return this.wsRpc.call([0, 'get_relative_contract_history', [ + contractId.toRaw(_contractId), + uint32.toRaw(stop), + uint32.toRaw(limit), + uint32.toRaw(start), + ]]); + } + /** * Transfer an amount from one account to another. * @param {string} fromAccountNameOrId the name or id of the account sending the funds @@ -1013,7 +1042,7 @@ class WalletAPI { * @param {boolean} shouldDoBroadcastToNetwork true if you wish to broadcast the transaction * @returns {Promise} the signed version of the transaction */ - generateEthAddress(accountIdOrName, shouldDoBroadcastToNetwork) { + createEthAddress(accountIdOrName, shouldDoBroadcastToNetwork) { if (!isAccountIdOrName(accountIdOrName)) { return Promise.reject(new Error('Accounts id or name should be string and valid')); } @@ -1194,11 +1223,11 @@ class WalletAPI { * * @param {string} accountIdOrName the account publishing the price feed * @param {string} assetIdOrName the name or id of the asset whose feed we're publishing - * @param {any} priceOfFeed object containing the three prices making up the feed + * @param {any} coreEchangeRate object containing the three prices making up the feed * @param {boolean} shouldDoBroadcastToNetwork true to broadcast the transaction on the network * @returns {Promise} the signed transaction updating the price feed for the given asset */ - publishAssetFeed(accountIdOrName, assetIdOrName, priceOfFeed, shouldDoBroadcastToNetwork) { + publishAssetFeed(accountIdOrName, assetIdOrName, coreEchangeRate, shouldDoBroadcastToNetwork) { if (!isAccountIdOrName(accountIdOrName)) { return Promise.reject(new Error('Accounts id or name should be string and valid')); } @@ -1208,7 +1237,7 @@ class WalletAPI { return this.wsRpc.call([0, 'publish_asset_feed', [ string.toRaw(accountIdOrName), string.toRaw(assetIdOrName), - priceFeed.toRaw(priceOfFeed), + price.toRaw(coreEchangeRate), bool.toRaw(shouldDoBroadcastToNetwork), ]]); } @@ -1645,13 +1674,13 @@ class WalletAPI { /** * @method getBtcAddress - * @param {String} accountId - * @returns {Promise} + * @param {string} account + * @returns {Promise} */ - getBtcAddress(accountIdValue) { - if (!isAccountId(accountIdValue)) throw new Error('account should be valid'); + getBtcAddress(account) { + if (!isAccountId(account) || !isAccountName(account)) throw new Error('account should be valid'); - return this.wsRpc.call([0, 'get_btc_address', [accountIdValue]]); + return this.wsRpc.call([0, 'get_btc_address', [account]]); } /** diff --git a/src/echo/ws/reconnection-websocket.js b/src/echo/ws/reconnection-websocket.js index 4becf8fb..265cfe20 100644 --- a/src/echo/ws/reconnection-websocket.js +++ b/src/echo/ws/reconnection-websocket.js @@ -214,12 +214,7 @@ class ReconnectionWebSocket { method === 'set_block_applied_callback' || method === 'set_echorand_message_callback' || method === 'subscribe_contract_logs' || method === 'submit_registration_solution' || method === 'get_contract_logs' ) { - // Store callback in subs map - this._subs[this._cbId] = { - callback: params[2][0], - }; - - // Replace callback with the callback id + this._subs[this._cbId] = { callback: params[2][0] }; params[2][0] = this._cbId; if (method === 'get_contract_logs') { @@ -257,7 +252,7 @@ class ReconnectionWebSocket { this._cbs[this._cbId] = { time: new Date(), - resolve, + resolve: method === 'subscribe_contract_logs' ? () => resolve(params[2][0]) : resolve, reject, timeoutId, }; @@ -382,7 +377,7 @@ class ReconnectionWebSocket { } /** - * make call for check connection + * make call for transaction.js connection * @private */ async _ping() { diff --git a/src/serializers/basic/integers/Int32.js b/src/serializers/basic/integers/Int32.js new file mode 100644 index 00000000..1ca0ddcb --- /dev/null +++ b/src/serializers/basic/integers/Int32.js @@ -0,0 +1,28 @@ +import IIntSerializer from './IIntSerializer'; + +/** @typedef {import("bytebuffer")} ByteBuffer */ + +/** @typedef {import("../../ISerializer").default} ISerializer */ + +/** + * @template {ISerializer} T + * @typedef {import("../../ISerializer").SerializerInput} SerializerInput + */ + +/** @augments {IIntSerializer} */ +export default class Int32Serializer extends IIntSerializer { + + constructor() { + super(32); + } + + /** + * @param {SerializerInput} value + * @param {ByteBuffer} bytebuffer + */ + appendToByteBuffer(value, bytebuffer) { + const raw = this.toRaw(value); + bytebuffer.writeInt32(raw); + } + +} diff --git a/src/serializers/basic/integers/Int64.js b/src/serializers/basic/integers/Int64.js index e50adfa1..225412af 100644 --- a/src/serializers/basic/integers/Int64.js +++ b/src/serializers/basic/integers/Int64.js @@ -9,7 +9,7 @@ import IIntSerializer from './IIntSerializer'; * @typedef {import("../../ISerializer").SerializerInput} SerializerInput */ -/** @augments {IIntSerializer} */ +/** @augments {IIntSerializer} */ export default class Int64Serializer extends IIntSerializer { constructor() { diff --git a/src/serializers/basic/integers/UInt64.js b/src/serializers/basic/integers/UInt64.js index 39b81ca3..de570e18 100644 --- a/src/serializers/basic/integers/UInt64.js +++ b/src/serializers/basic/integers/UInt64.js @@ -9,7 +9,7 @@ import IUIntSerializer from './IUIntSerializer'; * @typedef {import("../../ISerializer").SerializerInput} SerializerInput */ -/** @augments {IUIntSerializer} */ +/** @augments {IUIntSerializer} */ export default class UInt64Serializer extends IUIntSerializer { constructor() { super(64); } diff --git a/src/serializers/basic/integers/index.js b/src/serializers/basic/integers/index.js index e9a48444..40f12d90 100644 --- a/src/serializers/basic/integers/index.js +++ b/src/serializers/basic/integers/index.js @@ -1,4 +1,5 @@ import IIntSerializer from './IIntSerializer'; +import Int32Serializer from './Int32'; import Int64Serializer from './Int64'; import IUIntSerializer from './IUIntSerializer'; import UInt8Serializer from './UInt8'; @@ -8,6 +9,7 @@ import UInt64Serializer from './UInt64'; import UInt256Serializer from './UInt256'; import VarInt32Serializer from './VarInt32'; +export const int32 = new Int32Serializer(); export const int64 = new Int64Serializer(); export const uint8 = new UInt8Serializer(); export const uint16 = new UInt16Serializer(); @@ -18,6 +20,7 @@ export const varint32 = new VarInt32Serializer(); export { IIntSerializer, + Int32Serializer, Int64Serializer, IUIntSerializer, UInt8Serializer, diff --git a/src/serializers/chain/id/ObjectId.js b/src/serializers/chain/id/ObjectId.js index b4fea888..21b6895c 100644 --- a/src/serializers/chain/id/ObjectId.js +++ b/src/serializers/chain/id/ObjectId.js @@ -32,7 +32,7 @@ const _objectTypeIds = { /** * @template {ReservedSpaceId} T - * @augments {ISerializer} + * @augments {ISerializer} */ export default class ObjectIdSerializer extends ISerializer { diff --git a/src/serializers/chain/id/protocol.js b/src/serializers/chain/id/protocol.js index da8caedf..20f58950 100644 --- a/src/serializers/chain/id/protocol.js +++ b/src/serializers/chain/id/protocol.js @@ -19,22 +19,22 @@ export const frozenBalanceId = new ObjectIdSerializer( PROTOCOL_OBJECT_TYPE_ID.FROZEN_BALANCE, ); export const contractId = new ObjectIdSerializer(RESERVED_SPACE_ID.PROTOCOL, PROTOCOL_OBJECT_TYPE_ID.CONTRACT); -export const depositId = new ObjectIdSerializer( +export const ethDepositId = new ObjectIdSerializer( RESERVED_SPACE_ID.PROTOCOL, PROTOCOL_OBJECT_TYPE_ID.SIDECHAIN_ETH_DEPOSIT, ); -export const withdrawId = new ObjectIdSerializer( +export const ethWithdrawId = new ObjectIdSerializer( RESERVED_SPACE_ID.PROTOCOL, PROTOCOL_OBJECT_TYPE_ID.SIDECHAIN_ETH_WITHDRAW, ); export const erc20TokenId = new ObjectIdSerializer(RESERVED_SPACE_ID.PROTOCOL, PROTOCOL_OBJECT_TYPE_ID.ERC20_TOKEN); export const depositErc20TokenId = new ObjectIdSerializer( RESERVED_SPACE_ID.PROTOCOL, - PROTOCOL_OBJECT_TYPE_ID.SIDECHAIN_ERC20_DEPOSIT_TOKEN, + PROTOCOL_OBJECT_TYPE_ID.ERC20_DEPOSIT_TOKEN, ); export const withdrawErc20TokenId = new ObjectIdSerializer( RESERVED_SPACE_ID.PROTOCOL, - PROTOCOL_OBJECT_TYPE_ID.SIDECHAIN_ERC20_WITHDRAW_TOKEN, + PROTOCOL_OBJECT_TYPE_ID.ERC20_WITHDRAW_TOKEN, ); export const btcAddressId = new ObjectIdSerializer(RESERVED_SPACE_ID.PROTOCOL, PROTOCOL_OBJECT_TYPE_ID.BTC_ADDRESS); export const btcIntermediateDepositId = new ObjectIdSerializer( diff --git a/src/serializers/collections/Set.js b/src/serializers/collections/Set.js index 3273b973..3d5eebeb 100644 --- a/src/serializers/collections/Set.js +++ b/src/serializers/collections/Set.js @@ -1,3 +1,4 @@ +import { ok } from 'assert'; import VectorSerializer from './Vector'; /** @typedef {import("bytebuffer")} ByteBuffer */ @@ -27,7 +28,8 @@ export default class SetSerializer extends VectorSerializer { * @param {TInput} value * @returns {SerializerOutput>} */ - toRaw(value) { + toRaw(value, errorField = 'set') { + ok(typeof errorField === 'string'); if (value === undefined) value = []; if (value instanceof Set) value = [...value]; /** @type {ReturnType['toRaw']>} */ @@ -35,7 +37,7 @@ export default class SetSerializer extends VectorSerializer { try { raw = super.toRaw(value); } catch (error) { - throw new Error(`set: ${error.message}`); + throw new Error(`${errorField}: ${error.message}`); } /** @type {string[]} */ const serializedElements = new Array(raw.length); @@ -43,7 +45,7 @@ export default class SetSerializer extends VectorSerializer { serializedElements[i] = this.serializer.serialize(raw[i]).toString('hex'); for (let j = 0; j < i; j += 1) { if (serializedElements[i] === serializedElements[j]) { - throw new Error(`set element with index ${i} is equals to the other one with index ${j}`); + throw new Error(`${errorField} element with index ${i} is equals to the other one with index ${j}`); } } } @@ -57,7 +59,7 @@ export default class SetSerializer extends VectorSerializer { */ readFromBuffer(buffer, offset = 0) { const { res, newOffset } = super.readFromBuffer(buffer, offset); - // `this.toRaw` is used here to check duplicates + // `this.toRaw` is used here to transaction.js duplicates return { res: this.toRaw(res), newOffset }; } diff --git a/src/serializers/collections/Vector.js b/src/serializers/collections/Vector.js index b4177fc6..ae53ace3 100644 --- a/src/serializers/collections/Vector.js +++ b/src/serializers/collections/Vector.js @@ -45,15 +45,15 @@ export default class VectorSerializer extends ISerializer { * @param {TInput} value * @returns {TOutput} */ - toRaw(value) { - if (!Array.isArray(value)) throw new Error('value is not an array'); + toRaw(value, errorField = 'vector') { + if (!Array.isArray(value)) throw new Error(`${errorField} is not an array`); const raw = new Array(value.length); for (let i = 0; i < value.length; i += 1) { try { const element = value[i]; raw[i] = this.serializer.toRaw(element); } catch (error) { - throw new Error(`vector element with index ${i}: ${error.message}`); + throw new Error(`${errorField} element with index ${i}: ${error.message}`); } } return raw; diff --git a/src/serializers/plugins/sidechain/config.js b/src/serializers/plugins/sidechain/config.js index 8b1df3d8..e3321134 100644 --- a/src/serializers/plugins/sidechain/config.js +++ b/src/serializers/plugins/sidechain/config.js @@ -19,6 +19,7 @@ export const sidechainConfigSerializer = struct({ eth_gen_address_method: ethMethodSerializer, eth_withdraw_method: ethMethodSerializer, eth_update_addr_method: ethMethodSerializer, + eth_update_contract_address: ethMethodSerializer, eth_withdraw_token_method: ethMethodSerializer, eth_collect_tokens_method: ethMethodSerializer, eth_committee_updated_topic: ethTopicSerializer, @@ -28,14 +29,13 @@ export const sidechainConfigSerializer = struct({ erc20_deposit_topic: ethTopicSerializer, erc20_withdraw_topic: ethTopicSerializer, ETH_asset_id: assetId, - waiting_eth_blocks: uint32, - fines: sidechainFinesSerializer, - waiting_blocks: uint32, BTC_asset_id: assetId, - waiting_btc_blocks: uint32, + fines: sidechainFinesSerializer, + gas_price: uint64, satoshis_per_byte: uint32, - echo_blocks_per_aggregation: uint32, - echo_blocks_per_deposit: uint32, + coefficient_waiting_blocks: uint32, + btc_deposit_withdrawal_min: uint64, + btc_deposit_withdrawal_fee: uint64, }); export const sidechainERC20ConfigSerializer = struct({ diff --git a/src/serializers/protocol/sidechain/btc.js b/src/serializers/protocol/sidechain/btc.js index 50a98097..27201811 100644 --- a/src/serializers/protocol/sidechain/btc.js +++ b/src/serializers/protocol/sidechain/btc.js @@ -11,7 +11,7 @@ import { btcTransactionDetailsSerializer } from '../../chain/sidechain/btc'; import { struct, set, map, optional } from '../../collections'; import { string as stringSerializer, integers } from '../../basic'; import btcPublicKey from '../btcPublicKey'; -import { uint8 } from '../../basic/integers'; +import { uint8, uint32 } from '../../basic/integers'; export const sidechainBtcCreateAddressOperationPropsSerializer = struct({ fee: asset, @@ -62,6 +62,7 @@ export const sidechainBtcAggregateOperationPropsSerializer = struct({ sma_address: struct({ address: stringSerializer }), committee_member_ids_in_script: map(accountId, btcPublicKey), aggregation_out_value: integers.uint64, + btc_block_number: uint32, previous_aggregation: optional(btcAggregatingId), cpfp_depth: uint8, signatures: map(integers.uint32, stringSerializer), diff --git a/src/serializers/protocol/sidechain/eth.js b/src/serializers/protocol/sidechain/eth.js index b0e8742e..54ba80c8 100644 --- a/src/serializers/protocol/sidechain/eth.js +++ b/src/serializers/protocol/sidechain/eth.js @@ -1,7 +1,7 @@ import ethAddress from '../ethAddress'; import { uint64 } from '../../basic/integers'; import { asset, extensions } from '../../chain'; -import { accountId, depositId } from '../../chain/id/protocol'; +import { accountId, ethDepositId, ethWithdrawId } from '../../chain/id/protocol'; import { struct, vector } from '../../collections'; export const sidechainEthCreateAddressOperationPropsSerializer = struct({ @@ -31,11 +31,10 @@ export const sidechainEthDepositOperationPropsSerializer = struct({ export const sidechainEthSendDepositOperationPropsSerializer = struct({ fee: asset, committee_member_id: accountId, - deposit_id: depositId, + deposit_id: ethDepositId, extensions, }); - export const sidechainEthWithdrawOperationPropsSerializer = struct({ fee: asset, account: accountId, @@ -47,7 +46,7 @@ export const sidechainEthWithdrawOperationPropsSerializer = struct({ export const sidechainEthSendWithdrawOperationPropsSerializer = struct({ fee: asset, committee_member_id: accountId, - deposit_id: depositId, + withdraw_id: ethWithdrawId, extensions, }); diff --git a/src/serializers/protocol/sidechain/index.js b/src/serializers/protocol/sidechain/index.js index 6824e68c..b73b209e 100644 --- a/src/serializers/protocol/sidechain/index.js +++ b/src/serializers/protocol/sidechain/index.js @@ -3,13 +3,13 @@ import * as _eth from './eth'; import * as _btc from './btc'; import { struct } from '../../collections'; import { asset, extensions } from '../../chain'; -import { accountId, depositId, withdrawId } from '../../chain/id/protocol'; +import { accountId, ethDepositId, ethWithdrawId } from '../../chain/id/protocol'; export const sidechainIssueOperationPropsSerializer = struct({ fee: asset, value: asset, account: accountId, - deposit_id: depositId, + deposit_id: ethDepositId, extensions, }); @@ -18,7 +18,7 @@ export const sidechainBurnOperationPropsSerializer = struct({ fee: asset, value: asset, account: accountId, - withdraw_id: withdrawId, + withdraw_id: ethWithdrawId, extensions, }); diff --git a/src/serializers/transaction.js b/src/serializers/transaction.js index 9eeaddc6..df59f849 100644 --- a/src/serializers/transaction.js +++ b/src/serializers/transaction.js @@ -1,6 +1,6 @@ import { struct, vector } from './collections'; import { uint16, uint32, int64 } from './basic/integers'; -import { timePointSec, bytes } from './basic'; +import { timePointSec, bytes, bool } from './basic'; import { extensions } from './chain'; import OperationSerializer from './operation'; import OperationResultSerializer from './operation_result'; @@ -17,6 +17,7 @@ export default transactionSerializer; export const signedTransactionSerializer = struct({ ...transactionSerializer.serializers, signatures: vector(bytes(64)), + signed_with_echorand_key: bool, }); export const processedTransactionSerializer = struct({ diff --git a/src/utils/converters.js b/src/utils/converters.js index 6ba468e6..d162dc72 100644 --- a/src/utils/converters.js +++ b/src/utils/converters.js @@ -1,6 +1,7 @@ import { Long } from 'bytebuffer'; import BigNumber from 'bignumber.js'; import { idRegex } from './validators'; +import { collections, chain, basic } from '../serializers'; const MAX_UINT64 = new BigNumber(2).pow(64).minus(1); const MAX_INT64 = new BigNumber(2).pow(63).minus(1); @@ -39,3 +40,49 @@ export function toId(address, reservedSpaceId, objectTypeId) { else if (!objectTypeId.includes(actualObjectTypeId)) throw new Error('invalid objectTypeId'); return id; } + +/** @typedef {string | number | BigNumber} ObjectId_t */ +/** @typedef {number | BigNumber | string} Integer_t */ +/** + * @template T + * @typedef {Set | T[] | undefined} Set_t + */ + +/** + * @typedef {Object} ContractLogsFilterOptions_t + * @property {Set_t} [contracts] + * @property {Array>} [topics] + * @property {Integer_t} [fromBlock] + * @property {Integer_t} [toBlock] + */ + +/** + * @typedef {Object} ContractLogsFilterOptionsRaw + * @property {string[]} [contracts] + * @property {Array} [topics] + * @property {number} [from_block] + * @property {number} [to_block] + */ + +/** + * @param {ContractLogsFilterOptions_t} opts + * @returns {ContractLogsFilterOptionsRaw} + */ +export function toRawContractLogsFilterOptions(opts) { + /** @type {string[]} */ + const contractsList = opts.contracts === undefined ? undefined : + collections.set(chain.ids.protocol.contractId).toRaw(opts.contracts, 'contracts'); + /** @type {Array} */ + const topicsList = opts.topics === undefined ? undefined : + collections.vector(collections.set(basic.string)).toRaw(opts.topics, '`topics`'); + const from = opts.fromBlock === undefined ? undefined : basic.integers.int32.toRaw(opts.fromBlock, 'fromBlock'); + const to = opts.toBlock === undefined ? undefined : basic.integers.int32.toRaw(opts.toBlock, 'toBlock'); + if (from !== undefined && from < 0) throw new Error('`fromBlock` must be greater than or equal to zero'); + if (to !== undefined && to <= 0) throw new Error('`toBlock` must be greater than zero'); + return { + contracts: contractsList, + topics: topicsList, + from_block: from, + to_block: to, + }; +} diff --git a/src/utils/validators.js b/src/utils/validators.js index 2c7eb698..dcd3f6c8 100644 --- a/src/utils/validators.js +++ b/src/utils/validators.js @@ -4,7 +4,7 @@ import bs58 from 'bs58'; import { ADDRESS_PREFIX, LENGTH_DECODE_PRIVATE_KEY, LENGTH_DECODE_PUBLIC_KEY } from '../config/chain-config'; import { CHAIN_APIS } from '../constants/ws-constants'; -import { PROTOCOL_OBJECT_TYPE_ID, CHAIN_TYPES, AMOUNT_MAX_NUMBER, ECHO_MAX_SHARE_SUPPLY } from '../constants'; +import { PROTOCOL_OBJECT_TYPE_ID, CHAIN_TYPES, AMOUNT_MAX_NUMBER, ECHO_MAX_SHARE_SUPPLY, chain } from '../constants'; import { walletAPIMethodsArray, operationPrototypeArray } from './methods-operations-data'; export function validateSafeInteger(value, fieldName) { @@ -54,6 +54,7 @@ const contractIdRegex = generateProtocolObjectIdRegExp(PROTOCOL_OBJECT_TYPE_ID.C const contractResultIdRegex = generateProtocolObjectIdRegExp(PROTOCOL_OBJECT_TYPE_ID.CONTRACT_RESULT); const ethAddressIdRegex = generateProtocolObjectIdRegExp(PROTOCOL_OBJECT_TYPE_ID.ETH_ADDRESS); const btcAddressIdRegex = generateProtocolObjectIdRegExp(PROTOCOL_OBJECT_TYPE_ID.BTC_ADDRESS); +const erc20TokenIdRegex = generateProtocolObjectIdRegExp(PROTOCOL_OBJECT_TYPE_ID.ERC20_TOKEN); const dynamicGlobalObjectIdRegex = new RegExp(`^2\\.${CHAIN_TYPES.IMPLEMENTATION_OBJECT_TYPE_ID.DYNAMIC_GLOBAL_PROPERTY}\\.0$`); const dynamicAssetDataIdRegex = generateProtocolImplObjectIdRegExp(CHAIN_TYPES.IMPLEMENTATION_OBJECT_TYPE_ID.ASSET_DYNAMIC_DATA); @@ -139,6 +140,7 @@ export const isAccountId = (v) => isString(v) && accountIdRegex.test(v); export const isAccountAddressId = (v) => isString(v) && accountAddressRegex.test(v); export const isAssetId = (v) => isString(v) && assetIdRegex.test(v); export const isBtcAddressId = (v) => isString(v) && btcAddressIdRegex.test(v); +export const isERC20TokenId = (v) => isString(v) && erc20TokenIdRegex.test(v); export const isEthAddressId = (v) => isString(v) && ethAddressIdRegex.test(v); export const isCommitteeMemberId = (v) => isString(v) && committeeMemberIdRegex.test(v); @@ -359,3 +361,18 @@ export function validateSidechainType(v) { if (typeof v !== 'string') throw new Error('Type is not a string'); if (!['', 'eth', 'btc'].includes(v)) throw new Error(`Unsupported withdrawal type "${v}"`); } + +/** + * @param {number|BN|string} v + * @returns {string} + */ +export function validateAmount(v) { + if (typeof v === 'number' || typeof v === 'string') v = new BN(v); + if (!(v instanceof BN)) throw new Error('amount: invalid type'); + if (v.isNaN()) throw new Error('amount: not a number'); + const dp = v.dp(); + if (dp > 12) throw new Error('amont: invalid precision'); + const minSatoshis = v.times(`1e${dp.toString(10)}`).abs(); + if (minSatoshis > chain.config.ECHO_MAX_SHARE_SUPPLY) throw new Error('amount: overflow'); + return v.toString(10); +} diff --git a/test/_event-emitter-contract.js b/test/_event-emitter-contract.js new file mode 100644 index 00000000..7f79bee5 --- /dev/null +++ b/test/_event-emitter-contract.js @@ -0,0 +1,80 @@ +import assert from "assert"; +import { OPERATIONS_IDS } from ".."; +import { accountId, privateKey } from "./_test-data"; + +/** @typedef {import("..").Echo} Echo */ + +/* + * pragma solidity ^0.4.23; + * + * contract Emitter { + * event Event1(uint256 indexed indexedField, uint256 nonIndexedField); + * event Event2(uint256 indexed indexedField, uint256 nonIndexedField); + * function emit1(uint256 indexedField, uint256 nonIndexedField) public { + * emit Event1(indexedField, nonIndexedField); + * } + * function emit2(uint256 indexedField, uint256 nonIndexedField) public { + * emit Event2(indexedField, nonIndexedField); + * } + * } + */ +const bytecode = [ + "608060405234801561001057600080fd5b50610163806100206000396000f30060806040526004361061004c576000357c010000000000000", + "0000000000000000000000000000000000000000000900463ffffffff168063156d44ef146100515780631baea0ab14610088575b600080fd", + "5b34801561005d57600080fd5b5061008660048036038101908080359060200190929190803590602001909291905050506100bf565b005b3", + "4801561009457600080fd5b506100bd60048036038101908080359060200190929190803590602001909291905050506100fb565b005b817f", + "d3610b1c54575b7f4f0dc03d210b8ac55624ae007679b7a928a4f25a709331a8826040518082815260200191505060405180910390a250505", + "65b817f6a822560072e19c1981d3d3bb11e5954a77efa0caf306eb08d053f37de0040ba826040518082815260200191505060405180910390", + "a250505600a165627a7a72305820fddab02616eb79d169bffcec273868d1795db6ede88d13a007343987fa332a370029", +].join(""); + +export const emit1 = "156d44ef"; +export const emit2 = "1baea0ab"; + +/** + * @param {Echo} echo + * @param {number} [expirationOffset] + * @returns {Promise} + */ +export async function deploy(echo, expirationOffset = 0) { + const tx = echo.createTransaction().addOperation(OPERATIONS_IDS.CONTRACT_CREATE, { + registrar: accountId, + value: { amount: 0, asset_id: "1.3.0" }, + code: bytecode, + eth_accuracy: false, + extensions: [], + }).addSigner(privateKey); + tx.expiration = Math.ceil(Date.now() / 1e3) + expirationOffset; + const txRes = await tx.broadcast(); + /** @type {string} */ + const opResId = txRes[0].trx.operation_results[0][1]; + const opRes = await echo.api.getObject(opResId); + const contractId = opRes.contracts_id[0]; + assert.ok(contractId !== undefined); + return contractId; +} + +/** + * @param {Echo} echo + * @param {string} contractId + * @param {(typeof emit1)|(typeof emit2)} methodSignature + * @param {number} indexed + * @param {number} notIndexed + */ +export async function emit(echo, contractId, methodSignature, indexed, notIndexed) { + assert.ok(indexed >= 0); + assert.ok(notIndexed >= 0); + assert.ok(Number.isSafeInteger(indexed)); + assert.ok(Number.isSafeInteger(notIndexed)); + await echo.createTransaction().addOperation(OPERATIONS_IDS.CONTRACT_CALL, { + registrar: accountId, + value: { amount: 0, asset_id: '1.3.0' }, + code: [ + methodSignature, + indexed.toString(16).padStart(64, '0'), + notIndexed.toString(16).padStart(64, '0'), + ].join(''), + callee: contractId, + extensions: [], + }).addSigner(privateKey).broadcast(); +} diff --git a/test/_test-utils.js b/test/_test-utils.js index 8c92bf29..f03dca9f 100644 --- a/test/_test-utils.js +++ b/test/_test-utils.js @@ -43,3 +43,8 @@ export function getRandomAssetSymbol() { const result = String.fromCharCode(...charCodes); return result; } + +/** + * @template T + * @typedef {T extends Promise ? U : T} UnPromisify + */ diff --git a/test/api/database/get-contract-logs.test.js b/test/api/database/get-contract-logs.test.js new file mode 100644 index 00000000..1a16d49b --- /dev/null +++ b/test/api/database/get-contract-logs.test.js @@ -0,0 +1,136 @@ +import * as assert from "assert"; +import { Echo, OPERATIONS_IDS, BigNumber } from "../../.."; +import { url, accountId, privateKey } from "../../_test-data"; +import { shouldReject } from "../../_test-utils"; +import { deploy, emit, emit1, emit2 } from "../../_event-emitter-contract"; + +/** + * @template T + * @typedef {import("../../_test-utils").UnPromisify} UnPromisify + */ + +describe("getContractLogs", () => { + const echo = new Echo(); + + before(async () => await echo.connect(url)); + after(async () => await echo.disconnect()); + + describe('when `contracts` is not an `set_t`', () => { + shouldReject(async () => { + await echo.api.getContractLogs({ contracts: 'not an array' }); + }, 'contracts: vector is not an array'); + }); + + describe('when `contracts` first element is not a contract id', () => { + shouldReject(async () => { + await echo.api.getContractLogs({ contracts: ['1.10.1'] }); + }, 'invalid object type id'); + }); + + describe('when `topics` is not an `set_t`', () => { + shouldReject(async () => { + await echo.api.getContractLogs({ topics: 'not an array' }); + }, '`topics` is not an array'); + }); + + describe('when different events are emitted in different blocks', () => { + /** @type {string} */ + let contractId; + /** @type {string} */ + let contractAddress; + before(async function () { + this.timeout(25e3); + contractId = await deploy(echo); + contractAddress = `01${new BigNumber(contractId.split('.')[2]).toString(16).padStart(38, '0')}`; + await emit(echo, contractId, emit1, 123, 234); + await emit(echo, contractId, emit2, 345, 456); + }); + describe('when no filter provided', () => { + /** @type {UnPromisify>} */ + let res; + it('should succeed', async () => { + res = await echo.api.getContractLogs(); + assert.ok(res !== undefined); + }); + it('should return array', function () { + if (res === undefined) this.skip(); + assert.ok(Array.isArray(res)); + }); + /** @type {typeof res} */ + let events; + it('with length greater than or equals to 2', function () { + if (res === undefined || !Array.isArray(res)) this.skip(); + assert.ok(res.length >= 2); + events = res.slice(res.length - 2); + }); + it('with all logs of contract', function () { + if (events === undefined) this.skip(); + assert.ok(events.every(({ address }) => address === contractAddress)); + }); + it('with different blocks', function () { + if (events === undefined) this.skip(); + assert.ok(events[0].block_num !== events[1].block_num); + }); + }); + describe('when there are another contract with the same logs', () => { + /** @type {string} */ + let anotherContractId; + /** @type {string} */ + let anotherContractAddress; + before(async function () { + this.timeout(30e3); + anotherContractId = await deploy(echo); + const anotherContractInstanceIndex = new BigNumber(anotherContractId.split('.')[2]); + anotherContractAddress = `01${anotherContractInstanceIndex.toString(16).padStart(38, '0')}`; + await emit(echo, anotherContractId, emit1, 123, 234); + await emit(echo, anotherContractId, emit2, 345, 456); + }); + describe('when first contract provided in `contracts` filter', () => { + /** @type {UnPromisify>} */ + let res; + it('should succeed', async () => { + res = await echo.api.getContractLogs({ contracts: [contractId] }); + assert.ok(res !== undefined); + }); + it('should return array', function () { + if (res === undefined) this.skip(); + assert.ok(Array.isArray(res)); + }); + it('with length equals to 2', function () { + if (res === undefined || !Array.isArray(res)) this.skip(); + assert.strictEqual(res.length, 2); + }); + it('with all logs of contract', function () { + if (res === undefined || !Array.isArray(res) || res.length < 2) this.skip(); + assert.ok(res.every(({ address }) => address === contractAddress)); + }); + it('with different blocks', function () { + if (res === undefined || !Array.isArray(res) || res.length < 2) this.skip(); + assert.ok(res[0].block_num !== res[1].block_num); + }); + }); + describe('when both contracts are provided in `contracts` filter', () => { + /** @type {UnPromisify>} */ + let res; + it('should succeed', async () => { + res = await echo.api.getContractLogs({ contracts: [contractId, anotherContractId] }); + assert.ok(res !== undefined); + }); + it('should return array', function () { + if (res === undefined) this.skip(); + assert.ok(Array.isArray(res)); + }); + it('with length equals to 4', function () { + if (res === undefined || !Array.isArray(res)) this.skip(); + assert.strictEqual(res.length, 4); + }); + it('with all logs of both contracts', function () { + if (res === undefined || !Array.isArray(res) || res.length < 4) this.skip(); + assert.deepStrictEqual(res.map(({ address }) => { + return address; + }), [contractAddress, contractAddress, anotherContractAddress, anotherContractAddress]); + }); + }); + }); + }); +}); diff --git a/test/api/database/get-contract-logs2.test.js b/test/api/database/get-contract-logs2.test.js deleted file mode 100644 index c09d09ee..00000000 --- a/test/api/database/get-contract-logs2.test.js +++ /dev/null @@ -1,53 +0,0 @@ -import { Echo, OPERATIONS_IDS } from "../../.."; -import { url, accountId, privateKey } from "../../_test-data"; -import { inspect } from "util"; -import { ok, deepStrictEqual } from "assert"; - -/* - * pragma solidity ^0.4.24; - * contract Qwe { - * event Test(uint256 indexed indexedField, uint256 nonIndexedField); - * function test(uint256 indexedField, uint256 nonIndexedField) public { emit Test(indexedField, nonIndexedField); } - * } - */ -const bytecode = [ - "608060405234801561001057600080fd5b5060e08061001f6000396000f300608060405260043610603f576000357c010000000000000000", - "0000000000000000000000000000000000000000900463ffffffff168063eb8ac921146044575b600080fd5b348015604f57600080fd5b50", - "607660048036038101908080359060200190929190803590602001909291905050506078565b005b817f91916a5e2c96453ddf6b58549726", - "2675140eb9f7a774095fb003d93e6dc69216826040518082815260200191505060405180910390a250505600a165627a7a7230582085d4cd", - "c749b60050d7df224ded452a41549e1880caf023c417b9e1aca69645670029", -].join(""); - -describe("getContractLogs", () => { - const echo = new Echo(); - /** @type {string} */ - let contractId; - before(async function () { - this.timeout(12e3); - await echo.connect(url); - const txRes = await echo.createTransaction().addOperation(OPERATIONS_IDS.CONTRACT_CREATE, { - registrar: accountId, - value: { amount: 0, asset_id: "1.3.0" }, - code: bytecode, - eth_accuracy: false, - extensions: [], - }).addSigner(privateKey).broadcast(); - /** @type {string} */ - const opResId = txRes[0].trx.operation_results[0][1]; - const opRes = await echo.api.getObject(opResId); - contractId = opRes.contracts_id[0]; - ok(contractId !== undefined); - }); - describe("when options are not provided", () => { - /** @type {[]} */ - let result; - it("should not rejects", async () => { - result = await echo.api.getContractLogs(); - ok(result !== undefined); - }); - it("should return empty array", function () { - if (result === undefined) this.skip(); - else deepStrictEqual(result, []); - }); - }); -}); diff --git a/test/api/database/subscribe-contract-logs.test.js b/test/api/database/subscribe-contract-logs.test.js new file mode 100644 index 00000000..d9897a8a --- /dev/null +++ b/test/api/database/subscribe-contract-logs.test.js @@ -0,0 +1,56 @@ +import assert from "assert"; +import { Echo, BigNumber } from "../../.."; +import { url } from "../../_test-data"; +import { deploy, emit, emit1 } from "../../_event-emitter-contract"; + +/** @typedef {import("../../../types/interfaces/vm/types").Log} Log */ + +describe('subscribeContractLogs', () => { + const echo = new Echo(); + let contract1; + let contract2; + before(async function () { + this.timeout(10e3); + await echo.connect(url); + [contract1, contract2] = await Promise.all(new Array(2).fill(null).map((_, i) => deploy(echo, 10 + i))); + }); + after(async () => echo.disconnect()); + describe('when no any options are provided', () => { + /** @type {number|string} */ + let subscribeId; + /** @type {Log[]} */ + let emits = []; + it('should succeed', async () => { + subscribeId = await echo.api.subscribeContractLogs((logs) => emits.push(...logs)); + }); + describe('should returns subscribeId', () => { + /** @type {BigNumber} */ + let subIdBN; + before('should returns subscribeId', () => { + assert.ok(subscribeId !== undefined); + subIdBN = new BigNumber(subscribeId); + }); + it('number', () => assert.ok(!new BigNumber(subscribeId).isNaN())); + it('integer', () => assert.ok(new BigNumber(subscribeId).isInteger())); + it('non-negative', () => assert.ok(new BigNumber(subscribeId).gte(0))); + it('less than 2**64', () => assert.ok(new BigNumber(subscribeId).lt(new BigNumber(2).pow(64)))); + }); + describe('when different contracts emit', () => { + before(() => emits = []); + it('should not rejects', async function () { + this.timeout(10e3); + await Promise.all([contract1, contract2].map(async (contract) => { + await emit(echo, contract, emit1, 123, 234); + })); + await new Promise((resolve) => setTimeout(() => resolve(), 1e3)); + }); + it('should call callback', () => assert.ok(emits.length > 0)); + it('should call callback twice', () => assert.strictEqual(emits.length, 2)); + it('with both of contracts', function () { + if (emits.length !== 2) this.skip(); + assert.deepStrictEqual(new Set([contract1, contract2]), new Set(emits.map((log) => log[1].address))); + }); + }); + after(async () => { if (subscribeId !== undefined) await echo.api.unsubscribeContractLogs(subscribeId); }); + }); +}); diff --git a/test/api/index.api.test.js b/test/api/index.api.test.js index 87e527ee..c77994a9 100644 --- a/test/api/index.api.test.js +++ b/test/api/index.api.test.js @@ -1,6 +1,7 @@ describe('api', () => { require('./database/check-erc20-token.test'); - require('./database/get-contract-logs2.test'); + require('./database/get-contract-logs.test'); + require('./database/subscribe-contract-logs.test'); require('./register.account.test'); require('./network.api.test'); }); diff --git a/test/cache.test.js b/test/cache.test.js index df28035f..f7a7bda1 100644 --- a/test/cache.test.js +++ b/test/cache.test.js @@ -191,6 +191,7 @@ describe('cache', () => { }); it('should not clean more often then minCleaningTime', async () => { + let tickTimeoutId; try { const promises = []; const expirationTime = 100; @@ -204,7 +205,8 @@ describe('cache', () => { res(); } else { if (currentCacheSize !== 0) { - setTimeout(() => { + tickTimeoutId = setTimeout(() => { + tickTimeoutId = undefined; tick(res, callCount + 1, currentCacheSize); }, tickInterval); } @@ -238,6 +240,7 @@ describe('cache', () => { }); } catch (e) { + if (tickTimeoutId !== undefined) clearTimeout(tickTimeoutId); throw(e); } }) diff --git a/test/subscriber.test.js b/test/subscriber.test.js index 5a0df56e..5c88f498 100644 --- a/test/subscriber.test.js +++ b/test/subscriber.test.js @@ -437,24 +437,6 @@ describe('SUBSCRIBER', () => { }); }); - describe('setContractLogsSubscribe', () => { - it('test', async () => { - await echo.subscriber.setContractLogsSubscribe([[`1.${CONTRACT}.0`, []]], () => {}); - expect(echo.subscriber.subscribers.logs[`1.${CONTRACT}.0`].length).to.equal(1); - }); - }); - - describe('removeContractLogsSubscribe', () => { - it('test', async () => { - const callback = () => {}; - await echo.subscriber.setContractLogsSubscribe([[`1.${CONTRACT}.0`, []]], callback); - - const { length } = echo.subscriber.subscribers.logs[`1.${CONTRACT}.0`]; - await echo.subscriber.removeContractLogsSubscribe(`1.${CONTRACT}.0`, callback); - expect(echo.subscriber.subscribers.logs[`1.${CONTRACT}.0`].length).to.equal(length - 1); - }); - }); - after(async () => { await echo.disconnect(); }); diff --git a/test/transaction.test.js b/test/transaction.test.js index 05f06cc7..96c996e4 100644 --- a/test/transaction.test.js +++ b/test/transaction.test.js @@ -1,18 +1,34 @@ import 'mocha'; import { expect } from 'chai'; -import { Echo } from '../src'; -import Transaction from '../src/echo/transaction'; +import { Echo, Transaction } from '../src'; import { strictEqual, notStrictEqual, deepStrictEqual, fail, ok } from 'assert'; -import { TRANSFER } from '../src/constants/operations-ids'; +import { CONTRACT_CREATE, TRANSFER } from '../src/constants/operations-ids'; import PrivateKey from '../src/crypto/private-key'; -import { url } from './_test-data'; +import { url, WIF } from './_test-data'; import { ACCOUNT, ASSET} from '../src/constants/object-types'; +import { DYNAMIC_GLOBAL_OBJECT_ID } from '../src/constants'; +import { bytecode } from './operations/_contract.test'; const echo = new Echo(); +const options = { + from: `1.${ACCOUNT}.6`, + to: `1.${ACCOUNT}.10`, + // eth_accuracy: false, + amount: { asset_id: `1.${ASSET}.0`, amount: 10 }, + fee: { asset_id: '1.3.0', amount: 0 }, + extensions: [], +}; +const options2 = { + registrar: `1.${ACCOUNT}.6`, + value: { asset_id: '1.3.0', amount: 0 }, + eth_accuracy: false, + code: bytecode, + fee: { asset_id: '1.3.0', amount: 500 }, +}; -describe.skip('Transaction', () => { +describe('Transaction', () => { //skip before(() => echo.connect(url)); @@ -27,7 +43,7 @@ describe.skip('Transaction', () => { // deepStrictEqual(transaction.operations, []); // }); // }); - + // // describe('addOperation', () => { // describe('failure', () => { // it('without name', () => { @@ -67,7 +83,7 @@ describe.skip('Transaction', () => { // // TODO: test with excess fields // }); // }); - + // // describe('setRequiredFees', () => { // describe('failure', () => { // it('no operations', async () => { @@ -110,10 +126,10 @@ describe.skip('Transaction', () => { // strictEqual(fee.asset_id, `1.${ASSET}.1`); // ok(fee.amount > 0); // }); - + // // }); // }); - + // // describe('broadcast', () => { // it('qwe', async () => { // const pk = 'WIF'; @@ -129,19 +145,135 @@ describe.skip('Transaction', () => { // }).timeout(11000); // }); - describe('get potential signatures', () => { - it('asd', async () => { - const pk = '5KPT6sFAgx8sEiNyuF2QijsNCAPAvs4r6MV9Vn26z4NuTv86mfd'; - const transaction = echo.createTransaction(); - transaction.addOperation(TRANSFER, { - from: `1.${ACCOUNT}.6`, - to: `1.${ACCOUNT}.7`, - amount: { asset_id: `1.${ASSET}.0`, amount: 1000 }, - }); - // await transaction.sign(PrivateKey.fromWif(pk)); - // console.log(await transaction.broadcast(console.log)); - console.log(await transaction.getPotentialSignatures()); - }).timeout(12e3); - }); + // describe('get potential signatures', () => { + // it('asd', async () => { + // const pk = '5KPT6sFAgx8sEiNyuF2QijsNCAPAvs4r6MV9Vn26z4NuTv86mfd'; + // const transaction = echo.createTransaction(); + // transaction.addOperation(TRANSFER, { + // from: `1.${ACCOUNT}.6`, + // to: `1.${ACCOUNT}.7`, + // amount: { asset_id: `1.${ASSET}.0`, amount: 1000 }, + // }); + // // await transaction.sign(PrivateKey.fromWif(pk)); + // // console.log(await transaction.broadcast(console.log)); + // console.log(await transaction.getPotentialSignatures()); + // }).timeout(12e3); + // }); + + describe('sing transaction offline', () => { + it('should fall, already finalized', async () => { + const tx = echo.createTransaction(); + const dynamicGlobalChainData = await echo.api.getObject(DYNAMIC_GLOBAL_OBJECT_ID, true); + console.log('dynamicGlobalChainData.head_block_id', dynamicGlobalChainData.head_block_id); + tx.refBlockNum = dynamicGlobalChainData.head_block_number; + tx.refBlockPrefix = dynamicGlobalChainData.head_block_id; + tx.chainId = await echo.api.getChainId(); + try { + tx.addOperation(TRANSFER, options); + } catch (err) { + expect(err.message).to.equal('already finalized'); + } + }).timeout(11000); + it('should fall, when use invalid chainId data', async () => { + const tx = new Transaction(); + try { + tx.chainId = 'you shall not pass!!!'; + } catch (err) { + expect(err.message).to.equal('invalid chainId format or length'); + } + }); + it('should fall, when use negative refBlockNum value', async () => { + const tx = new Transaction(); + try { + tx.refBlockNum = -1; + } catch (err) { + expect(err.message).to.equal('value is not positive'); + } + }); + it('should fall, when use not integer as refBlockNum value', async () => { + const tx = new Transaction(); + try { + tx.refBlockNum = 1.23; + await tx.sign(privateKey); + } catch (err) { + expect(err.message).to.equal('undefined is not a integer'); + } + }); + it('should fall, when use not a number as refBlockNum value', async () => { + const tx = new Transaction(); + try { + tx.refBlockNum = 'you shall not pass!!!'; + await tx.sign(privateKey); + } catch (err) { + expect(err.message).to.equal('undefined is not a number'); + } + }); + it('should fall, when use not safe number as refBlockNum value', async () => { + const tx = new Transaction(); + try { + tx.refBlockNum = 100000000000000; + await tx.sign(privateKey); + } catch (err) { + expect(err.message).to.equal('number is not safe'); + } + }); + it('should fall, when use not UInt32 as refBlockPrefix value', async () => { + const tx = new Transaction(); + try { + tx.refBlockPrefix = 'notPass'; + await tx.sign(privateKey); + } catch (err) { + expect(err.message).to.equal('invalid refBlockPrefix format'); + } + }); + it('should fall, when use offline sign did not provide all needed data', async () => { + const tx = new Transaction(); + const dynamicGlobalChainData = await echo.api.getObject(DYNAMIC_GLOBAL_OBJECT_ID, true); + const privateKey = PrivateKey.fromWif(WIF); + tx.addOperation(TRANSFER, options); + tx.refBlockNum = dynamicGlobalChainData.head_block_number; + try { + await tx.sign(privateKey); + } catch (err) { + expect(err.message).to.equal('Api instance does not exist, check your connection'); + } + }); + it('should succeeded, sing offline and broadcast', async () => { + const tx = new Transaction(); + const dynamicGlobalChainData = await echo.api.getObject(DYNAMIC_GLOBAL_OBJECT_ID, true); + const privateKey = PrivateKey.fromWif(WIF); + tx.addOperation(TRANSFER, options); + tx.addOperation(CONTRACT_CREATE, options2); + tx.chainId = await echo.api.getChainId(); + tx.refBlockNum = dynamicGlobalChainData.head_block_number; + tx.refBlockPrefix = dynamicGlobalChainData.head_block_id; + await tx.sign(privateKey); + // const result2 = await echo.api.broadcastTransactionWithCallback(tx); + // console.log('result2', result2); + }); + }); + describe('sing transaction online', () => { + it('should fall, when try sing offline', async () => { + const tx = new Transaction(); + const dynamicGlobalChainData = await echo.api.getObject(DYNAMIC_GLOBAL_OBJECT_ID, true); + const privateKey = PrivateKey.fromWif(WIF); + tx.addOperation(TRANSFER, options); + tx.refBlockPrefix = dynamicGlobalChainData.head_block_id; + try { + await tx.sign(privateKey); + } catch (err) { + expect(err.message).to.equal('Api instance does not exist, check your connection'); + } + }); + it('should succeeded, sing with provided data and get rest of needed data', async () => { + const dynamicGlobalChainData = await echo.api.getObject(DYNAMIC_GLOBAL_OBJECT_ID, true); + const privateKey = PrivateKey.fromWif(WIF); + const tx = echo.createTransaction(); + tx.addOperation(TRANSFER, options); + tx.refBlockPrefix = dynamicGlobalChainData.head_block_id; + await tx.sign(privateKey); + // await tx.broadcast(); + }); + }) }); diff --git a/test/wallet-api.test.js b/test/wallet-api.test.js index 7d9935d0..4b0f7958 100644 --- a/test/wallet-api.test.js +++ b/test/wallet-api.test.js @@ -55,7 +55,7 @@ describe('WALLET API', () => { before(async () => { await echo.connect(url, { - connectionTimeout: 10000, + connectionTimeout: 1000, apis: constants.WS_CONSTANTS.CHAIN_APIS, wallet: walletURL, } @@ -1007,9 +1007,9 @@ describe('WALLET API', () => { }).timeout(5000); }); - describe('#generateEthAddress()', () => { + describe('#createEthAddress()', () => { it('Should generate eth address', async () => { - const result = await echo.walletApi.generateEthAddress(accountId, shouldDoBroadcastToNetwork); + const result = await echo.walletApi.createEthAddress(accountId, shouldDoBroadcastToNetwork); expect(result) .to .be @@ -1192,34 +1192,20 @@ describe('WALLET API', () => { describe('#publishAssetFeed()', () => { it('Should publishes a price feed for the named asset', async () => { - const priceFeed = { - "settlement_price": { - "base": { - "amount": 1, - "asset_id": "1.3.0", - }, - "quote": { - "amount": 1, - "asset_id": "1.3.1", - }, + const core_exchange_rate = { + "base": { + "amount": 1, + "asset_id": '1.3.0', }, - "core_exchange_rate": { - "base": { - "amount": 1, - "asset_id": "1.3.0", - }, - "quote": { - "amount": 1, - "asset_id": "1.3.1", - }, + "quote": { + "amount": 1, + "asset_id": '1.3.1', }, - "maintenance_collateral_ratio": 32e3, - "maximum_short_squeeze_ratio": 32e3, }; const result = await echo.walletApi.publishAssetFeed( accountId, constants.ECHO_ASSET_ID, - priceFeed, + core_exchange_rate, shouldDoBroadcastToNetwork, ); expect(result) @@ -1337,34 +1323,20 @@ describe('WALLET API', () => { describe('#publishAssetFeed()', () => { it('Should publishes a price feed for the named asset', async () => { - const priceFeed = { - "settlement_price": { - "base": { - "amount": 1, - "asset_id": "1.3.0", - }, - "quote": { - "amount": 1, - "asset_id": "1.3.1", - }, + const core_exchange_rate = { + "base": { + "amount": 1, + "asset_id": '1.3.0', }, - "core_exchange_rate": { - "base": { - "amount": 1, - "asset_id": "1.3.0", - }, - "quote": { - "amount": 1, - "asset_id": "1.3.1", - }, + "quote": { + "amount": 1, + "asset_id": '1.3.1', }, - "maintenance_collateral_ratio": 32e3, - "maximum_short_squeeze_ratio": 32e3, }; const result = await echo.walletApi.publishAssetFeed( accountId, constants.ECHO_ASSET_ID, - priceFeed, + core_exchange_rate, shouldDoBroadcastToNetwork, ); expect(result) @@ -1408,7 +1380,7 @@ describe('WALLET API', () => { bitassetOpts, shouldDoBroadcastToNetwork, ); - console.log('check', check.operations[0][1]); + console.log('transaction.js.js', check.operations[0][1]); const bitasset = '1.3.0'; const result = await echo.walletApi.getBitassetData(bitasset); expect(result) diff --git a/types/constants/chain/config.d.ts b/types/constants/chain/config.d.ts new file mode 100644 index 00000000..157acc89 --- /dev/null +++ b/types/constants/chain/config.d.ts @@ -0,0 +1 @@ +export const ECHO_MAX_SHARE_SUPPLY = 1000000000000000; diff --git a/types/constants/chain/index.d.ts b/types/constants/chain/index.d.ts new file mode 100644 index 00000000..b95f0087 --- /dev/null +++ b/types/constants/chain/index.d.ts @@ -0,0 +1,3 @@ +import * as config from './config'; + +export { config }; diff --git a/types/constants/index.d.ts b/types/constants/index.d.ts index 30ce6d7e..953abc11 100644 --- a/types/constants/index.d.ts +++ b/types/constants/index.d.ts @@ -1,5 +1,6 @@ import * as API_CONFIG from './api-config'; import * as CACHE_MAPS from './cache-maps'; +import * as chain from './chain'; import * as CHAIN_CONFIG from './chain-config'; import * as CHAIN_TYPES from './chain-types'; import PROTOCOL_OBJECT_TYPE_ID = require('./object-types'); @@ -21,4 +22,14 @@ export const BITASSET_UPDATE: string; export const ECHO_ASSET_ID: string; export const DYNAMIC_GLOBAL_OBJECT_ID: string; -export { API_CONFIG, CACHE_MAPS, CHAIN_CONFIG, CHAIN_TYPES, PROTOCOL_OBJECT_TYPE_ID, WS_CONSTANTS, protocol, NET } +export { + chain, + API_CONFIG, + CACHE_MAPS, + CHAIN_CONFIG, + CHAIN_TYPES, + PROTOCOL_OBJECT_TYPE_ID, + WS_CONSTANTS, + protocol, + NET, +} diff --git a/types/echo/api.d.ts b/types/echo/api.d.ts index 0b6092e6..da03738a 100644 --- a/types/echo/api.d.ts +++ b/types/echo/api.d.ts @@ -15,28 +15,43 @@ import ChainProperties from '../interfaces/ChainProperties'; import GlobalProperties from '../interfaces/GlobalProperties'; import Config from '../interfaces/Config'; import DynamicGlobalProperties from '../interfaces/DynamicGlobalProperties'; -import Asset from '../interfaces/Asset'; import ContractHistory from '../interfaces/ContractHistory'; import ContractResult from '../interfaces/ContractResult'; import FrozenBalance from '../interfaces/FrozenBalance'; import BtcAddress from '../interfaces/BtcAddress'; +import { OperationHistoryObject } from '../interfaces/chain'; import { PotentialPeerRecord } from '../interfaces/net/peer-database'; +import { IObject, IAssetObject, IAccountObject } from '../interfaces/objects'; import RegistrationTask from '../interfaces/RegistrationTask'; import PeerDetails from '../interfaces/PeerDetails'; +import { Log } from '../interfaces/vm/types'; import { asset } from '../serializers/chain'; -import { VectorSerializer } from '../serializers/collections'; import { signedTransaction } from '../serializers'; -import { committeeMemberId } from '../serializers/chain/id/protocol'; -import { uint32 } from '../serializers/basic/integers'; +import { StringSerializer } from '../serializers/basic'; +import { uint32, uint64, int32 } from '../serializers/basic/integers'; +import { committeeMemberId, contractId } from '../serializers/chain/id/protocol'; +import { VectorSerializer, SetSerializer } from '../serializers/collections'; +import { Contract } from '../interfaces/Contract'; type SidechainType = "" | "eth" | "btc"; +interface ContractLogsFilterOptions { + /** IDs of the contract */ + contracts?: SetSerializer["__TInput__"], + /** Filters by certain events if any provided */ + topics?: VectorSerializer>["__TInput__"], + /** Number of block to start retrieve from */ + fromBlock?: typeof int32["__TInput__"], + /** Number of block to end to retrieve */ + toBlock?: typeof int32["__TInput__"], +} + export default class Api { broadcastTransaction(tr: object): Promise; broadcastTransactionWithCallback(signedTransactionObject: object, wasBroadcastedCallback?: () => any): Promise; checkERC20Token(contractId: string): Promise; get24Volume(baseAssetName: string, quoteAssetName: string): Promise; - getAccounts(accountIds: Array, force?: boolean): Promise>; + getAccounts(accountIds: Array, force?: boolean): Promise; getAccountBalances( accountId: string, @@ -44,23 +59,36 @@ export default class Api { force?: boolean, ): Promise['__TOutput__']>; - getAccountByName(accountName: string, force?: boolean): Promise; + getAccountByName(accountName: string, force?: boolean): Promise; getAccountByAddress(address: string): Promise; getAccountCount(): Promise; getAccountDeposits(account: string, type: SidechainType): Promise; getAccountHistory(accountId: string, stop: string, limit: number, start: string): Promise>; - getAccountHistoryOperations(accountId: string, operationId: string, start: number, stop: number, limit: number): Promise>; - getAccountReferences(accountId: string, force?: boolean): Promise; + + getAccountHistoryOperations( + accountId: string, + operationId: string, + start: number, + stop: number, + limit: number, + ): Promise; + + getAccountReferences(accountId: string, force?: boolean): Promise; getAccountWithdrawals(account: string, type: SidechainType): Promise; getAllAssetHolders(): Promise>; - getAssetHolders(assetId: string, start: number, limit: number): Promise>; + + getAssetHolders(assetId: string, start: number, limit: number): Promise>; + getAssetHoldersCount(assetId: string): Promise; - getAssets(assetIds: Array, force?: boolean): Promise>; - getBalanceObjects(keys: object): any; - getBitAssetData(bitAssetId: string, force?: boolean): Promise; + getAssets(assetIds: Array, force?: boolean): Promise>; + getBalanceObjects(keys: Object): any; + getBitAssetData(bitAssetId: string, force?: boolean): Promise; getBlock(blockNum: number): Promise; getBlockHeader(blockNum: number): Promise; - getBlockRewards(blockNum: typeof uint32["__TInput__"]): Promise; getBlockVirtualOperations(blockNum: number): any; getBtcAddress(accountId: string): Promise>; getAccountAddresses(accountId: string, from: number, limit: number): Promise>; @@ -72,39 +100,82 @@ export default class Api { getCommitteeMembers(committeeMemberIds: Array, force?: boolean): Promise>; getCommitteeMemberByAccount(accountId: string, force?: boolean): Promise; getConfig(force?: boolean): Promise; - getContract(contractId: string): Promise>; + getContract(contractId: string): Promise; getContractBalances(contractId: string, force?: boolean): Promise; getContractPoolWhitelist(contractId: string): Promise; - getContractHistory(operationId: string, stop: number, limit: number, start: number): Promise>; - getContracts(contractIds: Array, force?: boolean): Promise>; - getContractLogs(opts: { - contracts?: string[], - topics?: Array>, - fromBlock?: number | BigNumber, - toBlock?: number | BigNumber, - }): Promise>; + + getContractHistory( + operationId: string, + stop: number, + limit: number, + start: number, + ): Promise; + + /** + * Get operations relevant to the specified contract referenced by an event numbering specific to the contract. + * The current number of operations for the contract can be found in the contract statistics (or use 0 for start). + * @param contract the contract whose history should be queried + * @param options.stop + * Sequence number of earliest operation. 0 is default and will query `limit` number of operations. + * @param options.limit Maximum number of operations to retrive (must not exceed 100) + * @param options.start Sequence number of the most recent operation to retrive. + * 0 is default, which will start querying from the most recent operation. + * @returns A list of operations performed by contract, ordered from most recent to oldest + */ + getRelativeContractHistory(contract: typeof contractId["__TInput__"], options?: { + stop?: typeof uint64["__TInput__"], + limit?: typeof uint64["__TInput__"], + start?: typeof uint64["__TInput__"], + }): Promise; + + getContracts(contractIds: Array, force?: boolean): Promise>; + + /** + * Get logs of specified contract logs filter options + * @param opts Contract logs filter options (see {@link ContractLogsFilterOptions}) + * @returns The contracts logs from specified blocks interval + */ + getContractLogs(opts?: ContractLogsFilterOptions): Promise; getContractPoolBalance(resultContractId: string, force?: boolean): Promise<{asset_id: string, amount: number}>; getContractResult(resultContractId: string, force?: boolean): Promise; getDynamicAssetData(dynamicAssetDataId: string, force?: boolean): Promise; getDynamicGlobalProperties(force?: boolean): Promise; getFeePool(assetId: string): Promise; getFrozenBalances(accountId: string): Promise>; - getFullAccounts(accountNamesOrIds: Array, subscribe?: boolean, force?: boolean): Promise>; - getFullContract(contractId: string, force?: boolean): Promise; + + getFullAccounts( + accountNamesOrIds: string[], + subscribe?: boolean, + force?: boolean, + ): Promise; + + getFullContract(contractId: string, force?: boolean): Promise; getGlobalProperties(force?: boolean): Promise; getKeyReferences(keys: Array, force?: boolean): Promise; getMarginPositions(accountId: string): Promise; - getNamedAccountBalances(accountName: string, assetIds: Array, force?: boolean): Promise; - getObject(objectId: string, force?: boolean): Promise; - getObjects(objectIds: string, force?: boolean): Promise>; - getPotentialSignatures(tr: object): Promise; + getNamedAccountBalances(accountName: string, assetIds: Array, force?: boolean): Promise; + getObject(objectId: string, force?: boolean): Promise; + getObjects(objectIds: string, force?: boolean): Promise>; + getPotentialSignatures(tr: Object): Promise; getProposedTransactions(accountNameOrId: string): Promise; getRecentTransactionById(transactionId: string): Promise; - getRelativeAccountHistory(accountId: string, stop: number, limit: number, start: number): Promise>; - getRequiredFees(operations: Array, assetId: string): Promise>; - getRequiredSignatures(tr: object, availableKey: Array): Promise; + getRelativeAccountHistory(accountId: string, stop: number, limit: number, start: number): Promise; + getRequiredFees(operations: Array, assetId: string): Promise>; + getRequiredSignatures(tr: Object, availableKey: Array): Promise; getTicker(baseAssetName: string, quoteAssetName: string): Promise; - getTradeHistory(baseAssetName: string, quoteAssetName: number, start: number, stop: number, limit: number): Promise; + + getTradeHistory( + baseAssetName: string, + quoteAssetName: number, + start: number, + stop: number, + limit: number, + ): Promise; + getTransaction(blockNum: number, transactionIndex: number): Promise; getTransactionHex(tr: object): Promise; getVestedBalances(balanceIds: Array): Promise; @@ -117,10 +188,10 @@ export default class Api { code: string, ): Promise; - listAssets(lowerBoundSymbol: string, limit: number): Promise>; + listAssets(lowerBoundSymbol: string, limit: number): Promise; lookupAccounts(lowerBoundName: string, limit: number): Promise>; - lookupAccountNames(accountNames: Array, force?: boolean): Promise>; - lookupAssetSymbols(symbolsOrIds: Array, force?: boolean): Promise>; + lookupAccountNames(accountNames: Array, force?: boolean): Promise; + lookupAssetSymbols(symbolsOrIds: Array, force?: boolean): Promise; lookupCommitteeMemberAccounts(lowerBoundName: string, limit: number): Promise; registerAccount( @@ -132,9 +203,23 @@ export default class Api { ): Promise<[{ block_num: number, tx_id: string }]>; requestRegistrationTask(): Promise - validateTransaction(tr: object): Promise; - verifyAuthority(tr: object): Promise; - verifyAccountAuthority(accountNameOrId: object, signers: Array): Promise; + + /** + * @param cb + * @param options Contract logs filter options (see `ContractLogsFilterOptions` method) + * @returns Callback id which should be referenced in `unsubscribeContractLogs` + */ + subscribeContractLogs(cb: (result: Log[]) => any, options?: ContractLogsFilterOptions): Promise; + + /** + * Unsubscribe from contract log subscription + * @param subscribeId Subscribe id (returns by `subscribeContractLogs`) + */ + unsubscribeContractLogs(subscribeId: typeof uint64["__TInput__"]): Promise; + + validateTransaction(tr: Object): Promise; + verifyAuthority(tr: Object): Promise; + verifyAccountAuthority(accountNameOrId: Object, signers: Array): Promise; getConnectedPeers(): Promise>; getPotentialPeers(): Promise; diff --git a/types/echo/subscriber.d.ts b/types/echo/subscriber.d.ts index bdafd820..a47a7532 100644 --- a/types/echo/subscriber.d.ts +++ b/types/echo/subscriber.d.ts @@ -1,3 +1,7 @@ +import { Log } from "../interfaces/vm/types"; +import { ContractLogsFilterOptions } from "./api"; + export default class Subscriber { setBlockApplySubscribe(cb: (blockHash: unknown) => any): Promise; + setContractLogsSubscribe(cb: (result: Log[]) => any, options?: ContractLogsFilterOptions): Promise; } diff --git a/types/echo/transaction.d.ts b/types/echo/transaction.d.ts index fa93fbc9..1af18f17 100644 --- a/types/echo/transaction.d.ts +++ b/types/echo/transaction.d.ts @@ -33,6 +33,8 @@ export default class Transaction { readonly transactionObject: any; readonly operations: SerializerOutput[]; + expiration: number; + addOperation( operationId: T, props?: TOperationInput[1], @@ -44,4 +46,5 @@ export default class Transaction { broadcast(wasBroadcastedCallback?: () => any): Promise<[BroadcastingResult]>; setRequiredFees(assetId?: string): Promise; serialize(): Buffer; + signedTransactionSerializer(): Buffer; } diff --git a/types/index.d.ts b/types/index.d.ts index 3712b721..f9e5cfe5 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -5,6 +5,7 @@ import * as validators from './utils/validators' import * as converters from './utils/converters' import * as crypto from './crypto' +export { OPERATIONS_IDS } from "./constants"; export { default as Transaction } from './echo/transaction'; export { default as PublicKey } from './crypto/public-key'; export { default as PrivateKey } from './crypto/private-key'; @@ -16,10 +17,9 @@ import * as serializers from './serializers'; import Contract, { encode, decode } from "./contract"; export { handleConnectionClosedError } from './utils/helpers'; - declare const echo: Echo; export default echo; -export declare const { OPERATIONS_IDS, CACHE_MAPS }: typeof constants; +export declare const { CACHE_MAPS }: typeof constants; export { BigNumber, Echo, constants, validators, converters, serializers, Contract, encode, decode, crypto }; diff --git a/types/interfaces/ChainProperties.d.ts b/types/interfaces/ChainProperties.d.ts index 51d70a24..86b6ee21 100644 --- a/types/interfaces/ChainProperties.d.ts +++ b/types/interfaces/ChainProperties.d.ts @@ -1,7 +1,4 @@ export default interface ChainProperties { id: string, chain_id: string, - immutable_parameters:{ - min_committee_member_count: number - } } diff --git a/types/interfaces/Contract.d.ts b/types/interfaces/Contract.d.ts new file mode 100644 index 00000000..42cc3d59 --- /dev/null +++ b/types/interfaces/Contract.d.ts @@ -0,0 +1,9 @@ +import { VMType } from "./objects"; + +export type Contract = [T, { + [VMType.EVM]: { + code: string, + storage: { [key: string]: [string, string] }, + }, + [VMType.X86_64]: { code: string }, +}[T]]; diff --git a/types/interfaces/OperationId.d.ts b/types/interfaces/OperationId.d.ts index 9a1cc49b..7587f40c 100644 --- a/types/interfaces/OperationId.d.ts +++ b/types/interfaces/OperationId.d.ts @@ -45,7 +45,7 @@ declare enum OperationId { SIDECHAIN_ETH_SEND_DEPOSIT = 42, SIDECHAIN_ETH_WITHDRAW = 43, SIDECHAIN_ETH_SEND_WITHDRAW = 44, - IDECHAIN_ETH_APPROVE_WITHDRAW = 45, + SIDECHAIN_ETH_APPROVE_WITHDRAW = 45, SIDECHAIN_ETH_UPDATE_CONTRACT_ADDRESS = 46, SIDECHAIN_ISSUE = 47, // VIRTUAL SIDECHAIN_BURN = 48, // VIRTUAL @@ -67,5 +67,3 @@ declare enum OperationId { BLOCK_REWARD = 64,// VIRTUAL EVM_ADDRESS_REGISTER = 65, } - - diff --git a/types/interfaces/ProtocolObjectTypeId.d.ts b/types/interfaces/ProtocolObjectTypeId.d.ts index 97921d65..284795c5 100644 --- a/types/interfaces/ProtocolObjectTypeId.d.ts +++ b/types/interfaces/ProtocolObjectTypeId.d.ts @@ -16,8 +16,8 @@ declare enum PROTOCOL_OBJECT_TYPE_ID { SIDECHAIN_ETH_DEPOSIT = 14, SIDECHAIN_ETH_WITHDRAW = 15, ERC20_TOKEN = 16, - SIDECHAIN_ERC20_DEPOSIT_TOKEN = 17, - SIDECHAIN_ERC20_WITHDRAW_TOKEN = 18, + ERC20_DEPOSIT_TOKEN = 17, + ERC20_WITHDRAW_TOKEN = 18, BTC_ADDRESS = 19, BTC_INTERMEDIATE_DEPOSIT = 20, BTC_DEPOSIT = 21, diff --git a/types/interfaces/chain.d.ts b/types/interfaces/chain.d.ts new file mode 100644 index 00000000..c98c70fa --- /dev/null +++ b/types/interfaces/chain.d.ts @@ -0,0 +1,17 @@ +import { RESERVED_SPACE_ID } from "../constants/chain-types"; +import { OperationSerializer, operationResult, chain } from "../serializers"; +import { uint32, uint16 } from "../serializers/basic/integers"; +import { extensions } from "../serializers/chain"; +import { ObjectIdSerializer } from "../serializers/chain/id"; +import { StructSerializer } from "../serializers/collections"; + +export type OperationHistoryObject = StructSerializer<{ + id: ObjectIdSerializer; + op: OperationSerializer; + result: typeof operationResult; + block_num: typeof uint32; + trx_in_block: typeof uint16; + op_in_trx: typeof uint16; + virtual_op: typeof uint16; + extensions: typeof extensions; +}>["__TOutput__"]; diff --git a/types/interfaces/objects.d.ts b/types/interfaces/objects.d.ts new file mode 100644 index 00000000..30578f57 --- /dev/null +++ b/types/interfaces/objects.d.ts @@ -0,0 +1,57 @@ +import { protocol } from "../serializers"; + +export enum VMType { + EVM = 0, + X86_64 = 1, +} + +export interface IObject { id: string; } + +export interface IContractObject extends IObject { + type: VMType; + destroyed: boolean; + statistics: string; + supported_asset_id?: string; + owner?: string; + extensions: unknown[]; +} + +export interface IERC20TokenObject extends IObject { + owner: string; + eth_addr: string; + contract: string; + name: string; + symbol: string; + decimals: number; + extensions: unknown[]; +} + +export interface IAssetObject extends IObject { + symbol: string; + precision: number; + issuer: string; + options: typeof protocol.asset.options["__TOutput__"]; + dynamic_asset_data_id: string; + bitasset_data_id?: string; + buyback_account?: string; + extensions: unknown[]; +} + +export interface IAccountObject extends IObject { + registrar: string; + name: string; + active: typeof protocol.authority["__TOutput__"]; + echorand_key: string; + active_delegate_share: number; + options: typeof protocol.account.options["__TOutput__"]; + statistics: string; + whitelisting_accounts: string[]; + blacklisting_accounts: string[]; + whitelisted_accounts: string[]; + blacklisted_accounts: string[]; + active_special_authority: unknown; + top_n_control_flags: number; + allowed_assets?: string[]; + accumulated_reward: number | string; + extensions: unknown[]; +} diff --git a/types/interfaces/vm/index.d.ts b/types/interfaces/vm/index.d.ts new file mode 100644 index 00000000..83d0ba55 --- /dev/null +++ b/types/interfaces/vm/index.d.ts @@ -0,0 +1,3 @@ +import * as types from "./types"; + +export { types }; diff --git a/types/interfaces/vm/types.d.ts b/types/interfaces/vm/types.d.ts new file mode 100644 index 00000000..d2d9c58b --- /dev/null +++ b/types/interfaces/vm/types.d.ts @@ -0,0 +1,12 @@ +import { VMType } from "../objects"; + +export interface LogEntry { + address: string; + log: string[]; + data: string; + block_num: number; + trx_num: number; + op_num: number; +} + +export type Log = [VMType, LogEntry]; diff --git a/types/serializers/basic/integers/Int32.d.ts b/types/serializers/basic/integers/Int32.d.ts new file mode 100644 index 00000000..3bf73cc0 --- /dev/null +++ b/types/serializers/basic/integers/Int32.d.ts @@ -0,0 +1,7 @@ +import * as ByteBuffer from "bytebuffer"; +import IIntSerializer from "./IIntSerializer"; +import { SerializerInput, SerializerOutput } from "../../ISerializer"; + +export default class Int32Serializer extends IIntSerializer { + appendToByteBuffer(value: SerializerInput, bytebuffer: ByteBuffer): void; +} diff --git a/types/serializers/basic/integers/index.d.ts b/types/serializers/basic/integers/index.d.ts index 510c59be..b411a737 100644 --- a/types/serializers/basic/integers/index.d.ts +++ b/types/serializers/basic/integers/index.d.ts @@ -1,4 +1,5 @@ import IIntSerializer from './IIntSerializer'; +import Int32Serializer from './Int32'; import Int64Serializer from './Int64'; import IUIntSerializer from './IUIntSerializer'; import UInt8Serializer from './UInt8'; @@ -8,6 +9,7 @@ import UInt64Serializer from './UInt64'; import UInt256Serializer from './UInt256'; import VarInt32Serializer from './VarInt32'; +export declare const int32: Int32Serializer; export declare const int64: Int64Serializer; export declare const uint8: UInt8Serializer; export declare const uint16: UInt16Serializer; @@ -18,6 +20,7 @@ export declare const varint32: VarInt32Serializer; export { IIntSerializer, + Int32Serializer, Int64Serializer, IUIntSerializer, UInt8Serializer, diff --git a/types/serializers/chain/id/protocol.d.ts b/types/serializers/chain/id/protocol.d.ts index 518e0447..241c25ae 100644 --- a/types/serializers/chain/id/protocol.d.ts +++ b/types/serializers/chain/id/protocol.d.ts @@ -8,8 +8,8 @@ export declare const proposalId: ObjectIdSerializer; export declare const vestingBalanceId: ObjectIdSerializer; export declare const balanceId: ObjectIdSerializer; export declare const contractId: ObjectIdSerializer; -export declare const depositId: ObjectIdSerializer; -export declare const withdrawId: ObjectIdSerializer; +export declare const ethDepositId: ObjectIdSerializer; +export declare const ethWithdrawId: ObjectIdSerializer; export declare const erc20TokenId: ObjectIdSerializer; export declare const depositErc20TokenId: ObjectIdSerializer; export declare const withdrawErc20TokenId: ObjectIdSerializer; diff --git a/types/serializers/collections/Set.d.ts b/types/serializers/collections/Set.d.ts index 69b97c6e..836e7fec 100644 --- a/types/serializers/collections/Set.d.ts +++ b/types/serializers/collections/Set.d.ts @@ -5,7 +5,7 @@ import VectorSerializer from "./Vector"; type TInput = SerializerInput>[] | Set>> | undefined; -export default class SetSerializer extends VectorSerializer { +export default class SetSerializer extends VectorSerializer> { constructor(serializer: T); validate(value: TInput): void; serialize(value: TInput): Buffer; diff --git a/types/serializers/collections/Vector.d.ts b/types/serializers/collections/Vector.d.ts index f4e47cad..e7d605c2 100644 --- a/types/serializers/collections/Vector.d.ts +++ b/types/serializers/collections/Vector.d.ts @@ -4,10 +4,13 @@ import ISerializer, { SerializerInput, SerializerOutput } from "../ISerializer"; type TInput = SerializerInput[]; type TOutput = SerializerOutput[]; -export default class VectorSerializer extends ISerializer, TOutput> { +export default class VectorSerializer< + T extends ISerializer, + K extends any = TInput + > extends ISerializer> { readonly serializer: T; constructor(serializer: T); - toRaw(value: TInput): TOutput; - appendToByteBuffer(value: TInput, bytebuffer: ByteBuffer): void; + toRaw(value: K): TOutput; + appendToByteBuffer(value: K, bytebuffer: ByteBuffer): void; readFromBuffer(buffer: Buffer, offset?: number): { res: TOutput, newOffset: number }; } diff --git a/types/serializers/plugins/sidechain/config.d.ts b/types/serializers/plugins/sidechain/config.d.ts index 21a15b9c..cdbc6cb7 100644 --- a/types/serializers/plugins/sidechain/config.d.ts +++ b/types/serializers/plugins/sidechain/config.d.ts @@ -19,6 +19,7 @@ export declare const sidechainConfigSerializer: StructSerializer<{ eth_gen_address_method: typeof ethMethodSerializer, eth_withdraw_method: typeof ethMethodSerializer, eth_update_addr_method: typeof ethMethodSerializer, + eth_update_contract_address: typeof ethMethodSerializer, eth_withdraw_token_method: typeof ethMethodSerializer, eth_collect_tokens_method: typeof ethMethodSerializer, eth_committee_updated_topic: typeof ethTopicSerializer, @@ -28,14 +29,13 @@ export declare const sidechainConfigSerializer: StructSerializer<{ erc20_deposit_topic: typeof ethTopicSerializer, erc20_withdraw_topic: typeof ethTopicSerializer, ETH_asset_id: typeof assetId, - waiting_eth_blocks: typeof uint32, - fines: typeof sidechainFinesSerializer, - waiting_blocks: typeof uint32, BTC_asset_id: typeof assetId, - waiting_btc_blocks: typeof uint32, + fines: typeof sidechainFinesSerializer, + gas_price: typeof uint64, satoshis_per_byte: typeof uint32, - echo_blocks_per_aggregation: typeof uint32, - echo_blocks_per_deposit: typeof uint32, + coefficient_waiting_blocks: typeof uint32, + btc_deposit_withdrawal_min: typeof uint64, + btc_deposit_withdrawal_fee: typeof uint64, }>; export declare const sidechainERC20ConfigSerializer: StructSerializer<{ diff --git a/types/serializers/protocol/chain_parameters.d.ts b/types/serializers/protocol/chain_parameters.d.ts index ca530c4b..39624c77 100644 --- a/types/serializers/protocol/chain_parameters.d.ts +++ b/types/serializers/protocol/chain_parameters.d.ts @@ -29,7 +29,7 @@ declare const chainParametersSerializer: StructSerializer<{ block_producer_reward_ratio: typeof uint16, committee_frozen_balance_to_activate: typeof uint64, committee_maintenance_intervals_to_deposit: typeof uint64, - committee_freeze_duration_seconds: typeof uint32, + committee_balance_unfreeze_duration_seconds: typeof uint32, x86_64_maximum_contract_size: typeof uint64, extensions: typeof extensions, }>; diff --git a/types/serializers/protocol/sidechain/btc.d.ts b/types/serializers/protocol/sidechain/btc.d.ts index b1682e5f..7e08ee0d 100644 --- a/types/serializers/protocol/sidechain/btc.d.ts +++ b/types/serializers/protocol/sidechain/btc.d.ts @@ -52,6 +52,7 @@ export declare const sidechainBtcAggregateOperationPropsSerializer: StructSerial withdrawals: SetSerializer, transaction_id: typeof sha256, aggregation_out_value: typeof integers.uint64, + btc_block_number: typeof integers.uint32, sma_address: StructSerializer<{ address: StringSerializer }>, committee_member_ids_in_script: MapSerializer, previous_aggregation: typeof btcAggregatingId, diff --git a/types/serializers/protocol/sidechain/eth.d.ts b/types/serializers/protocol/sidechain/eth.d.ts index dc1e3ec5..9d0aad09 100644 --- a/types/serializers/protocol/sidechain/eth.d.ts +++ b/types/serializers/protocol/sidechain/eth.d.ts @@ -1,7 +1,8 @@ import ethAddress from "../ethAddress"; import { uint64 } from "../../basic/integers"; import { asset, extensions } from "../../chain"; -import { accountId, depositId } from "../../chain/id/protocol"; + +import {accountId, ethDepositId, ethWithdrawId} from "../../chain/id/protocol"; import { VectorSerializer, StructSerializer } from "../../collections"; export declare const sidechainEthCreateAddressOperationPropsSerializer: StructSerializer<{ @@ -31,7 +32,7 @@ export declare const sidechainEthDepositOperationPropsSerializer: StructSerializ export declare const sidechainEthSendDepositOperationPropsSerializer: StructSerializer<{ fee: typeof asset, committee_member_id: typeof accountId, - deposit_id: typeof depositId, + deposit_id: typeof ethDepositId, extensions: typeof extensions, }>; @@ -46,7 +47,7 @@ export declare const sidechainEthWithdrawOperationPropsSerializer: StructSeriali export declare const sidechainEthSendWithdrawOperationPropsSerializer: StructSerializer<{ fee: typeof asset, committee_member_id: typeof accountId, - deposit_id: typeof depositId, + withdraw_id: typeof ethWithdrawId, extensions: typeof extensions, }>; @@ -62,6 +63,3 @@ export declare const sidechainEthUpdateContractAddressOperationPropsSerializer: new_addr: typeof ethAddress, extensions: typeof extensions, }>; - - - diff --git a/types/serializers/protocol/sidechain/index.d.ts b/types/serializers/protocol/sidechain/index.d.ts index 8bd9ecb7..86634e5b 100644 --- a/types/serializers/protocol/sidechain/index.d.ts +++ b/types/serializers/protocol/sidechain/index.d.ts @@ -2,14 +2,14 @@ import * as _erc20 from './erc20'; import * as _eth from './eth'; import * as _btc from './btc'; import { asset, extensions } from '../../chain'; -import { accountId, depositId, withdrawId } from '../../chain/id/protocol'; +import { accountId, ethDepositId, ethWithdrawId } from '../../chain/id/protocol'; import { StructSerializer } from '../../collections'; export declare const sidechainIssueOperationPropsSerializer: StructSerializer<{ fee: typeof asset, value: typeof asset, account: typeof accountId, - deposit_id: typeof depositId, + deposit_id: typeof ethDepositId, extensions: typeof extensions, }>; @@ -17,7 +17,7 @@ export declare const sidechainBurnOperationPropsSerializer: StructSerializer<{ fee: typeof asset, value: typeof asset, account: typeof accountId, - withdraw_id: typeof withdrawId, + withdraw_id: typeof ethWithdrawId, extensions: typeof extensions, }>; diff --git a/types/utils/validators.d.ts b/types/utils/validators.d.ts index 4458ce11..a6cef90b 100644 --- a/types/utils/validators.d.ts +++ b/types/utils/validators.d.ts @@ -1,3 +1,5 @@ +import BigNumber from "bignumber.js"; + export declare function validateUrl(value?: any): boolean; export declare function isString(value?: any): boolean; export declare function isEmpty(value?: any): boolean; @@ -32,6 +34,7 @@ export declare function isContractResultId(value?: any): boolean; export declare function isAccountBalanceId(value?: any): boolean; export declare function isOperationId(value?: any): boolean; export declare function isVoteId(value?: any): boolean; +export declare function isERC20TokenId(value?: string): boolean; export declare function isObjectId(value?: any): boolean; export declare function isBuffer(value?: any): boolean; @@ -58,3 +61,5 @@ export declare function checkAccountName(value?: any): boolean; export declare function checkCheapName(value?: any): boolean; export declare function validateOptionsError(value?: any): boolean; export declare function isTimePointSec(value?: any): boolean; + +export declare function validateAmount(value: number|BigNumber|string): string;