diff --git a/.github/workflows/pr_test.yml b/.github/workflows/pr_test.yml index 63ffe11..b8f93e2 100644 --- a/.github/workflows/pr_test.yml +++ b/.github/workflows/pr_test.yml @@ -7,7 +7,7 @@ concurrency: group: ${{ github.event.pull_request.number }} cancel-in-progress: true jobs: - run_test_on_testnet: + run_test_on_develop_branch: if: ${{ github.base_ref == 'develop' }} runs-on: ubuntu-latest steps: @@ -17,7 +17,7 @@ jobs: id: test-pipeline with: environment: 'dev' - run_test_on_mainnet: + run_test_on_master_branch: if: ${{ github.base_ref == 'master' }} runs-on: ubuntu-latest steps: diff --git a/__tests__/__snapshots__/ain.test.ts.snap b/__tests__/__snapshots__/ain.test.ts.snap index f474019..b476fa9 100644 --- a/__tests__/__snapshots__/ain.test.ts.snap +++ b/__tests__/__snapshots__/ain.test.ts.snap @@ -141,7 +141,7 @@ Object { "#num_children": 2, "#num_children:username": 0, "#num_parents": 1, - "#num_parents:username": 2, + "#num_parents:username": 1, "#tree_bytes": 698, "#tree_bytes:username": 178, "#tree_height": 2, diff --git a/__tests__/ain.test.ts b/__tests__/ain.test.ts index 2b27696..3ddacad 100644 --- a/__tests__/ain.test.ts +++ b/__tests__/ain.test.ts @@ -1,9 +1,6 @@ // @ts-nocheck import Ain from '../src/ain'; -import Reference from '../src/ain-db/ref'; -import { TransactionBody, SetOperation, Transaction, TransactionInput, SetOperationType } from '../src/types'; -import { createSecretKey } from 'crypto'; -import { anyTypeAnnotation } from '@babel/types'; +import { TransactionBody, SetOperation, TransactionInput } from '../src/types'; import axios from 'axios'; import { fail, eraseProtoVer } from './test_util'; const { @@ -207,6 +204,16 @@ describe('ain-js', function() { expect(balance).toBeGreaterThan(0); }); + it('transfer with isDryrun = true', async function() { + const balanceBefore = await ain.wallet.getBalance(); + const response = await ain.wallet.transfer({ + to: '0xbA58D93edD8343C001eC5f43E620712Ba8C10813', value: 100, nonce: -1 + }, true); // isDryrun = true + expect(response.result.is_dryrun).toBe(true); + const balanceAfter = await ain.wallet.getBalance(); + expect(balanceAfter).toBe(balanceBefore); // NOT changed! + }); + it('transfer', async function() { const balanceBefore = await ain.wallet.getBalance(); const response = await ain.wallet.transfer({ @@ -376,6 +383,25 @@ describe('ain-js', function() { expect(thrownError.message).toEqual('Invalid app name for state label: app/path'); }); + it('sendTransaction with isDryrun = true', async function() { + await ain.sendTransaction({ operation: targetTx }, true) + .then(res => { + expect(res.result.code).toBe(0); + expect(res.tx_hash).toEqual(expect.stringMatching(TX_PATTERN)); + expect(res.result.is_dryrun).toBe(true); + targetTxHash = res.tx_hash; + }) + .catch(e => { + console.log("ERROR:", e) + fail('should not happen'); + }) + }); + + it('getTransaction for sendTransaction with isDryrun = true', async function () { + const tx = await ain.getTransaction(targetTxHash); + expect(tx).toStrictEqual(null); // The tx is NOT in the blockchain. + }); + it('sendTransaction', async function() { await ain.sendTransaction({ operation: targetTx }) .then(res => { @@ -389,11 +415,47 @@ describe('ain-js', function() { }) }); - it('getTransaction', async function () { + it('getTransaction for sendTransaction', async function () { const tx = await ain.getTransaction(targetTxHash); expect(tx.transaction.tx_body.operation).toStrictEqual(targetTx); }); + it('sendSignedTransaction with isDryrun = true', async function() { + const tx: TransactionBody = { + nonce: -1, + gas_price: 500, + timestamp: Date.now(), + operation: { + type: "SET_OWNER", + ref: "/apps/bfan", + value: { + ".owner": { + "owners": { + "*": { + write_owner: true, + write_rule: true, + branch_owner: true, + write_function: true, + } + } + } + } + } + } + const sig = ain.wallet.signTransaction(tx); + + await ain.sendSignedTransaction(sig, tx, true) + .then(res => { + expect(res.code).toBe(undefined); + expect(res.tx_hash).toEqual(expect.stringMatching(TX_PATTERN)); + expect(res.result.code).toBe(0); + expect(res.result.is_dryrun).toBe(true); + }) + .catch(e => { + console.log("ERROR:", e); + fail('should not happen'); + }) + }); it('sendSignedTransaction', async function() { const tx: TransactionBody = { @@ -597,6 +659,31 @@ describe('ain-js', function() { expect(ain.db.ref(test_path).path).toBe('/' + test_path); }); + it('setOwner with isDryrun = true', async function() { + await ain.db.ref(allowed_path).setOwner({ + value: { + ".owner": { + "owners": { + "*": { + write_owner: true, + write_rule: true, + write_function: true, + branch_owner: true + } + } + } + } + }, true) // isDryrun = true + .then(res => { + expect(res.result.code).toBe(0); + expect(res.result.is_dryrun).toBe(true); + }) + .catch((error) => { + console.log("setOwner error:", error); + fail('should not happen'); + }); + }); + it('setOwner', async function() { await ain.db.ref(allowed_path).setOwner({ value: { @@ -645,6 +732,20 @@ describe('ain-js', function() { }); }); + it('setRule with isDryrun = true', async function() { + await ain.db.ref(allowed_path).setRule({ + value: { '.rule': { 'write': "true" } } + }, true) // isDryrun = true + .then(res => { + expect(res.result.code).toBe(0); + expect(res.result.is_dryrun).toBe(true); + }) + .catch((error) => { + console.log("setRule error:", error); + fail('should not happen'); + }); + }); + it('setRule', async function() { await ain.db.ref(allowed_path).setRule({ value: { '.rule': { 'write': "true" } } @@ -658,6 +759,20 @@ describe('ain-js', function() { }); }); + it('setValue with isDryrun = true', async function() { + await ain.db.ref(allowed_path + '/username').setValue({ + value: "test_user" + }, true) // isDryrun = true + .then(res => { + expect(res.result.code).toBe(0); + expect(res.result.is_dryrun).toBe(true); + }) + .catch((error) => { + console.log("setValue error:", error); + fail('should not happen'); + }); + }); + it('setValue', async function() { await ain.db.ref(allowed_path + '/username').setValue({ value: "test_user" @@ -671,6 +786,28 @@ describe('ain-js', function() { }); }); + it('setFunction with isDryrun = true', async function() { + await ain.db.ref(allowed_path).setFunction({ + value: { + ".function": { + '0xFUNCTION_HASH': { + function_url: "https://events.ainetwork.ai/trigger", + function_id: '0xFUNCTION_HASH', + function_type: "REST" + } + } + } + }, true) // isDryrun = true + .then(res => { + expect(res.result.code).toBe(0); + expect(res.result.is_dryrun).toBe(true); + }) + .catch((error) => { + console.log("setFunction error:", error); + fail('should not happen'); + }) + }); + it('setFunction', async function() { await ain.db.ref(allowed_path).setFunction({ value: { @@ -692,6 +829,42 @@ describe('ain-js', function() { }) }); + it('set with isDryrun = true', async function() { + await ain.db.ref(allowed_path).set({ + op_list: [ + { + type: 'SET_RULE', + ref: 'can/write/', + value: { '.rule': { 'write': "true" } } + }, + { + type: 'SET_RULE', + ref: 'cannot/write', + value: { '.rule': { 'write': "false" } } + }, + { + type: 'INC_VALUE', + ref: 'can/write/', + value: 5 + }, + { + type: 'DEC_VALUE', + ref: 'can/write', + value: 10, + } + ], + nonce: -1 + }, true) // isDryrun = true + .then(res => { + expect(Object.keys(res.result).length).toBe(5); + expect(res.result.is_dryrun).toBe(true); + }) + .catch((error) => { + console.log("set error:",error); + fail('should not happen'); + }); + }); + it('set', async function() { await ain.db.ref(allowed_path).set({ op_list: [ @@ -857,6 +1030,18 @@ describe('ain-js', function() { expect(getWithVersion['#version']).not.toBeNull(); }); + it('deleteValue with isDryrun = true', async function() { + await ain.db.ref(`${allowed_path}/can/write`).deleteValue({}, true) + .then(res => { + expect(res.result.code).toBe(0); + expect(res.result.is_dryrun).toBe(true); + }) // isDryrun = true + .catch((error) => { + console.log("deleteValue error:",error); + fail('should not happen'); + }); + }); + it('deleteValue', async function() { await ain.db.ref(`${allowed_path}/can/write`).deleteValue() .then(res => { diff --git a/package.json b/package.json index 93158b4..85ae99b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@ainblockchain/ain-js", - "version": "1.4.0", + "version": "1.5.0", "description": "", "main": "lib/ain.js", "scripts": { diff --git a/src/ain-db/ref.ts b/src/ain-db/ref.ts index e271113..85b3cc4 100755 --- a/src/ain-db/ref.ts +++ b/src/ain-db/ref.ts @@ -127,9 +127,10 @@ export default class Reference { * Deletes a value. * @param {ValueOnlyTransactionInput} transactionInput - A transaction input object. * Any value given will be overwritten with null. + * @param {boolean} isDryrun - dryrun option. * @return {Promise} */ - deleteValue(transactionInput?: ValueOnlyTransactionInput): Promise { + deleteValue(transactionInput?: ValueOnlyTransactionInput, isDryrun: boolean = false): Promise { let txInput: ValueOnlyTransactionInput = transactionInput || {}; txInput['value'] = null; return this._ain.sendTransaction( @@ -138,113 +139,127 @@ export default class Reference { Reference.extendPath(this.path, txInput.ref), "SET_VALUE", this._isGlobal - ) + ), + isDryrun ); } /** * Sets a function config. * @param transactionInput + * @param {boolean} isDryrun - dryrun option. */ - setFunction(transactionInput: ValueOnlyTransactionInput): Promise { + setFunction(transactionInput: ValueOnlyTransactionInput, isDryrun: boolean = false): Promise { return this._ain.sendTransaction( Reference.extendSetTransactionInput( transactionInput, Reference.extendPath(this.path, transactionInput.ref), "SET_FUNCTION", this._isGlobal - ) + ), + isDryrun ); } /** * Sets the owner rule. * @param {ValueOnlyTransactionInput} transactionInput - A transaction input object. + * @param {boolean} isDryrun - dryrun option. * @return {Promise} */ - setOwner(transactionInput: ValueOnlyTransactionInput): Promise { + setOwner(transactionInput: ValueOnlyTransactionInput, isDryrun: boolean = false): Promise { return this._ain.sendTransaction( Reference.extendSetTransactionInput( transactionInput, Reference.extendPath(this.path, transactionInput.ref), "SET_OWNER", this._isGlobal - ) + ), + isDryrun ); } /** * Sets the write rule. * @param {ValueOnlyTransactionInput} transactionInput - A transaction input object. + * @param {boolean} isDryrun - dryrun option. * @return {Promise} */ - setRule(transactionInput: ValueOnlyTransactionInput): Promise { + setRule(transactionInput: ValueOnlyTransactionInput, isDryrun: boolean = false): Promise { return this._ain.sendTransaction( Reference.extendSetTransactionInput( transactionInput, Reference.extendPath(this.path, transactionInput.ref), "SET_RULE", this._isGlobal - ) + ), + isDryrun ); } /** * Sets a value. * @param {ValueOnlyTransactionInput} transactionInput - A transaction input object. + * @param {boolean} isDryrun - dryrun option. * @return {Promise} */ - setValue(transactionInput: ValueOnlyTransactionInput): Promise { + setValue(transactionInput: ValueOnlyTransactionInput, isDryrun: boolean = false): Promise { return this._ain.sendTransaction( Reference.extendSetTransactionInput( transactionInput, Reference.extendPath(this.path, transactionInput.ref), "SET_VALUE", this._isGlobal - ) + ), + isDryrun ); } /** * Increments the value. * @param {ValueOnlyTransactionInput} transactionInput - A transaction input object. + * @param {boolean} isDryrun - dryrun option. * @return {Promise} */ - incrementValue(transactionInput: ValueOnlyTransactionInput): Promise { + incrementValue(transactionInput: ValueOnlyTransactionInput, isDryrun: boolean = false): Promise { return this._ain.sendTransaction( Reference.extendSetTransactionInput( transactionInput, Reference.extendPath(this.path, transactionInput.ref), "INC_VALUE", this._isGlobal - ) + ), + isDryrun ); } /** * Decrements the value. * @param {ValueOnlyTransactionInput} transactionInput - A transaction input object. + * @param {boolean} isDryrun - dryrun option. * @return {Promise} */ - decrementValue(transactionInput: ValueOnlyTransactionInput): Promise { + decrementValue(transactionInput: ValueOnlyTransactionInput, isDryrun: boolean = false): Promise { return this._ain.sendTransaction( Reference.extendSetTransactionInput( transactionInput, Reference.extendPath(this.path, transactionInput.ref), "DEC_VALUE", this._isGlobal - ) + ), + isDryrun ); } /** * Processes multiple set operations. * @param {SetMultiTransactionInput} transactionInput - A transaction input object. + * @param {boolean} isDryrun - dryrun option. * @return {Promise} */ - set(transactionInput: SetMultiTransactionInput): Promise { + set(transactionInput: SetMultiTransactionInput, isDryrun: boolean = false): Promise { return this._ain.sendTransaction( - Reference.extendSetMultiTransactionInput(transactionInput, this.path)); + Reference.extendSetMultiTransactionInput(transactionInput, this.path), isDryrun); } /** diff --git a/src/ain.ts b/src/ain.ts index d2f9217..85d5e70 100755 --- a/src/ain.ts +++ b/src/ain.ts @@ -135,28 +135,25 @@ export default class Ain { /** * Signs and sends a transaction to the network * @param {TransactionInput} transactionObject + * @param {boolean} isDryrun - dryrun option. * @return {Promise} */ - async sendTransaction(transactionObject: TransactionInput): Promise { + async sendTransaction(transactionObject: TransactionInput, isDryrun: boolean = false): Promise { const txBody = await this.buildTransactionBody(transactionObject); const signature = this.wallet.signTransaction(txBody, transactionObject.address); - let result = await this.provider.send('ain_sendSignedTransaction', - { signature, tx_body: txBody }); - if (!result || typeof result !== 'object') { - result = { result }; - } - return result; + return await this.sendSignedTransaction(signature, txBody, isDryrun); } /** * Sends a signed transaction to the network * @param {string} signature * @param {TransactionBody} txBody + * @param {boolean} isDryrun - dryrun option. * @return {Promise} */ - async sendSignedTransaction(signature: string, txBody: TransactionBody): Promise { - let result = await this.provider.send('ain_sendSignedTransaction', - { signature, tx_body: txBody }); + async sendSignedTransaction(signature: string, txBody: TransactionBody, isDryrun: boolean = false): Promise { + const method = isDryrun ? 'ain_sendSignedTransactionDryrun' : 'ain_sendSignedTransaction'; + let result = await this.provider.send(method, { signature, tx_body: txBody }); if (!result || typeof result !== 'object') { result = { result }; } @@ -290,9 +287,10 @@ export default class Ain { * deposit/withdraw transaction and sends the transaction by calling sendTransaction(). * @param {string} path * @param {ValueOnlyTransactionInput} transactionObject + * @param {boolean} isDryrun - dryrun option. * @return {Promise} */ - private stakeFunction(path: string, transactionObject: ValueOnlyTransactionInput): Promise { + private stakeFunction(path: string, transactionObject: ValueOnlyTransactionInput, isDryrun: boolean = false): Promise { const type: SetOperationType = "SET_VALUE"; if (!transactionObject.value) { throw new Error('[ain-js.stakeFunction] a value should be specified.'); @@ -310,7 +308,7 @@ export default class Ain { } delete transactionObject.value; const txInput = Object.assign({ operation }, { transactionObject }); - return this.sendTransaction(txInput); + return this.sendTransaction(txInput, isDryrun); } else { throw new Error('[ain-js.stakeFunction] Error in Reference push.'); } diff --git a/src/wallet.ts b/src/wallet.ts index 9d3fae0..1ebbc3d 100755 --- a/src/wallet.ts +++ b/src/wallet.ts @@ -220,13 +220,14 @@ export default class Wallet { /** * Sends a transfer transaction to the network. * @param input + * @param {boolean} isDryrun - dryrun option. */ - transfer(input: {to: string, value: number, from?: string, nonce?: number, gas_price?: number}): Promise { + transfer(input: {to: string, value: number, from?: string, nonce?: number, gas_price?: number}, isDryrun: boolean = false): Promise { const address = this.getImpliedAddress(input.from); const toAddress = Ain.utils.toChecksumAddress(input.to); const transferRef = this.ain.db.ref(`/transfer/${address}/${toAddress}`).push() as Reference; return transferRef.setValue({ - ref: '/value', address, value: input.value, nonce: input.nonce, gas_price: input.gas_price }); + ref: '/value', address, value: input.value, nonce: input.nonce, gas_price: input.gas_price }, isDryrun); } /**