From c675c67c26bbc6cd1312444687a390a45f88badf Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Sat, 6 Feb 2021 10:59:00 +0800 Subject: [PATCH] chore: replace jest with mocha --- web3.js/.eslintignore | 2 +- web3.js/.mocharc.js | 6 + web3.js/.prettierignore | 1 + web3.js/.travis/script.sh | 1 + web3.js/CONTRIBUTING.md | 2 +- web3.js/babel.config.json | 8 +- web3.js/flow-typed/npm/jest_v26.x.x.js | 1218 ------ web3.js/jest-environment.js | 27 - web3.js/jest.json | 5 - web3.js/mocha.html | 21 + web3.js/package.json | 38 +- web3.js/rollup.config.js | 6 +- web3.js/test/.eslintrc.js | 4 - web3.js/test/.gitignore | 1 + web3.js/test/__mocks__/node-fetch.js | 86 - web3.js/test/__mocks__/rpc-websockets.js | 77 - web3.js/test/account.test.js | 154 +- web3.js/test/agent-manager.test.js | 49 +- web3.js/test/bpf-loader.test.js | 513 +-- web3.js/test/cluster.test.js | 25 +- web3.js/test/connection.test.js | 3768 +++++++----------- web3.js/test/fixtures/noop-c/build.sh | 7 - web3.js/test/fixtures/noop-rust/build.sh | 7 - web3.js/test/get-confirmed-block.test.js | 83 - web3.js/test/mockrpc/confirm-transaction.js | 19 - web3.js/test/mockrpc/get-recent-blockhash.js | 36 - web3.js/test/mocks/rpc-http.js | 170 + web3.js/test/mocks/rpc-websockets.js | 106 + web3.js/test/new-account-with-lamports.js | 36 - web3.js/test/nonce.test.js | 373 +- web3.js/test/publickey.test.js | 548 +-- web3.js/test/rollup.config.js | 76 + web3.js/test/secp256k1-program.test.js | 144 +- web3.js/test/shortvec-encoding.test.js | 95 +- web3.js/test/stake-program.test.js | 1009 ++--- web3.js/test/system-program.test.js | 1109 +++--- web3.js/test/transaction-payer.test.js | 309 +- web3.js/test/transaction.test.js | 994 ++--- web3.js/test/url.js | 5 +- web3.js/test/validator-info.test.js | 56 +- web3.js/test/websocket.test.js | 106 +- 41 files changed, 4635 insertions(+), 6665 deletions(-) create mode 100644 web3.js/.mocharc.js create mode 100644 web3.js/.prettierignore delete mode 100644 web3.js/flow-typed/npm/jest_v26.x.x.js delete mode 100644 web3.js/jest-environment.js delete mode 100644 web3.js/jest.json create mode 100644 web3.js/mocha.html delete mode 100644 web3.js/test/.eslintrc.js create mode 100644 web3.js/test/.gitignore delete mode 100644 web3.js/test/__mocks__/node-fetch.js delete mode 100644 web3.js/test/__mocks__/rpc-websockets.js delete mode 100755 web3.js/test/fixtures/noop-c/build.sh delete mode 100755 web3.js/test/fixtures/noop-rust/build.sh delete mode 100644 web3.js/test/get-confirmed-block.test.js delete mode 100644 web3.js/test/mockrpc/confirm-transaction.js delete mode 100644 web3.js/test/mockrpc/get-recent-blockhash.js create mode 100644 web3.js/test/mocks/rpc-http.js create mode 100644 web3.js/test/mocks/rpc-websockets.js delete mode 100644 web3.js/test/new-account-with-lamports.js create mode 100644 web3.js/test/rollup.config.js diff --git a/web3.js/.eslintignore b/web3.js/.eslintignore index 5c6a020fc7..3444b0317c 100644 --- a/web3.js/.eslintignore +++ b/web3.js/.eslintignore @@ -7,4 +7,4 @@ /module.flow.js /.eslintrc.js /test/.eslintrc.js -/jest-environment.js +/test/dist diff --git a/web3.js/.mocharc.js b/web3.js/.mocharc.js new file mode 100644 index 0000000000..e73c21c26b --- /dev/null +++ b/web3.js/.mocharc.js @@ -0,0 +1,6 @@ +'use strict'; + +// Configure Node.js tests +module.exports = { + require: ['@babel/register', 'esm'], +}; diff --git a/web3.js/.prettierignore b/web3.js/.prettierignore new file mode 100644 index 0000000000..94fde9b998 --- /dev/null +++ b/web3.js/.prettierignore @@ -0,0 +1 @@ +test/dist diff --git a/web3.js/.travis/script.sh b/web3.js/.travis/script.sh index ca3c064374..2d1cee2425 100644 --- a/web3.js/.travis/script.sh +++ b/web3.js/.travis/script.sh @@ -15,3 +15,4 @@ npm run codecov make -C examples/bpf-c-noop/ cargo build-bpf --manifest-path examples/bpf-rust-noop/Cargo.toml npm run test:live-with-test-validator +npm run test:browser-with-test-validator diff --git a/web3.js/CONTRIBUTING.md b/web3.js/CONTRIBUTING.md index f703fb7e8a..a8f215dc51 100644 --- a/web3.js/CONTRIBUTING.md +++ b/web3.js/CONTRIBUTING.md @@ -24,7 +24,7 @@ eslint and flow-type are used. Helpful link: https://www.saltycrane.com/flow-type-cheat-sheet/latest/ ### Testing Framework -https://jestjs.io/ +https://mochajs.org/ ### API Documentation ESDoc is used to document the public API. See diff --git a/web3.js/babel.config.json b/web3.js/babel.config.json index ec543409bb..3992889d16 100644 --- a/web3.js/babel.config.json +++ b/web3.js/babel.config.json @@ -5,11 +5,5 @@ ], "plugins": [ "@babel/plugin-proposal-class-properties" - ], - "env": { - "test": { - "plugins": ["@babel/plugin-transform-runtime"], - "presets": ["@babel/preset-flow"] - } - } + ] } diff --git a/web3.js/flow-typed/npm/jest_v26.x.x.js b/web3.js/flow-typed/npm/jest_v26.x.x.js deleted file mode 100644 index 99d5363b03..0000000000 --- a/web3.js/flow-typed/npm/jest_v26.x.x.js +++ /dev/null @@ -1,1218 +0,0 @@ -// flow-typed signature: 0ed73a588759fdbd9b6eb09d5ac062b3 -// flow-typed version: 479edd3152/jest_v26.x.x/flow_>=v0.104.x - -type JestMockFn, TReturn> = { - (...args: TArguments): TReturn, - /** - * An object for introspecting mock calls - */ - mock: { - /** - * An array that represents all calls that have been made into this mock - * function. Each call is represented by an array of arguments that were - * passed during the call. - */ - calls: Array, - /** - * An array that contains all the object instances that have been - * instantiated from this mock function. - */ - instances: Array, - /** - * An array that contains all the object results that have been - * returned by this mock function call - */ - results: Array<{ - isThrow: boolean, - value: TReturn, - ... - }>, - ... - }, - /** - * Resets all information stored in the mockFn.mock.calls and - * mockFn.mock.instances arrays. Often this is useful when you want to clean - * up a mock's usage data between two assertions. - */ - mockClear(): void, - /** - * Resets all information stored in the mock. This is useful when you want to - * completely restore a mock back to its initial state. - */ - mockReset(): void, - /** - * Removes the mock and restores the initial implementation. This is useful - * when you want to mock functions in certain test cases and restore the - * original implementation in others. Beware that mockFn.mockRestore only - * works when mock was created with jest.spyOn. Thus you have to take care of - * restoration yourself when manually assigning jest.fn(). - */ - mockRestore(): void, - /** - * Accepts a function that should be used as the implementation of the mock. - * The mock itself will still record all calls that go into and instances - * that come from itself -- the only difference is that the implementation - * will also be executed when the mock is called. - */ - mockImplementation( - fn: (...args: TArguments) => TReturn - ): JestMockFn, - /** - * Accepts a function that will be used as an implementation of the mock for - * one call to the mocked function. Can be chained so that multiple function - * calls produce different results. - */ - mockImplementationOnce( - fn: (...args: TArguments) => TReturn - ): JestMockFn, - /** - * Accepts a string to use in test result output in place of "jest.fn()" to - * indicate which mock function is being referenced. - */ - mockName(name: string): JestMockFn, - /** - * Just a simple sugar function for returning `this` - */ - mockReturnThis(): void, - /** - * Accepts a value that will be returned whenever the mock function is called. - */ - mockReturnValue(value: TReturn): JestMockFn, - /** - * Sugar for only returning a value once inside your mock - */ - mockReturnValueOnce(value: TReturn): JestMockFn, - /** - * Sugar for jest.fn().mockImplementation(() => Promise.resolve(value)) - */ - mockResolvedValue(value: TReturn): JestMockFn>, - /** - * Sugar for jest.fn().mockImplementationOnce(() => Promise.resolve(value)) - */ - mockResolvedValueOnce( - value: TReturn - ): JestMockFn>, - /** - * Sugar for jest.fn().mockImplementation(() => Promise.reject(value)) - */ - mockRejectedValue(value: TReturn): JestMockFn>, - /** - * Sugar for jest.fn().mockImplementationOnce(() => Promise.reject(value)) - */ - mockRejectedValueOnce(value: TReturn): JestMockFn>, - ... -}; - -type JestAsymmetricEqualityType = { - /** - * A custom Jasmine equality tester - */ - asymmetricMatch(value: mixed): boolean, - ... -}; - -type JestCallsType = { - allArgs(): mixed, - all(): mixed, - any(): boolean, - count(): number, - first(): mixed, - mostRecent(): mixed, - reset(): void, - ... -}; - -type JestClockType = { - install(): void, - mockDate(date: Date): void, - tick(milliseconds?: number): void, - uninstall(): void, - ... -}; - -type JestMatcherResult = { - message?: string | (() => string), - pass: boolean, - ... -}; - -type JestMatcher = ( - received: any, - ...actual: Array -) => JestMatcherResult | Promise; - -type JestPromiseType = { - /** - * Use rejects to unwrap the reason of a rejected promise so any other - * matcher can be chained. If the promise is fulfilled the assertion fails. - */ - rejects: JestExpectType, - /** - * Use resolves to unwrap the value of a fulfilled promise so any other - * matcher can be chained. If the promise is rejected the assertion fails. - */ - resolves: JestExpectType, - ... -}; - -/** - * Jest allows functions and classes to be used as test names in test() and - * describe() - */ -type JestTestName = string | Function; - -/** - * Plugin: jest-styled-components - */ - -type JestStyledComponentsMatcherValue = - | string - | JestAsymmetricEqualityType - | RegExp - | typeof undefined; - -type JestStyledComponentsMatcherOptions = { - media?: string, - modifier?: string, - supports?: string, - ... -}; - -type JestStyledComponentsMatchersType = { - toHaveStyleRule( - property: string, - value: JestStyledComponentsMatcherValue, - options?: JestStyledComponentsMatcherOptions - ): void, - ... -}; - -/** - * Plugin: jest-enzyme - */ -type EnzymeMatchersType = { - // 5.x - toBeEmpty(): void, - toBePresent(): void, - // 6.x - toBeChecked(): void, - toBeDisabled(): void, - toBeEmptyRender(): void, - toContainMatchingElement(selector: string): void, - toContainMatchingElements(n: number, selector: string): void, - toContainExactlyOneMatchingElement(selector: string): void, - toContainReact(element: React$Element): void, - toExist(): void, - toHaveClassName(className: string): void, - toHaveHTML(html: string): void, - toHaveProp: ((propKey: string, propValue?: any) => void) & - ((props: { ... }) => void), - toHaveRef(refName: string): void, - toHaveState: ((stateKey: string, stateValue?: any) => void) & - ((state: { ... }) => void), - toHaveStyle: ((styleKey: string, styleValue?: any) => void) & - ((style: { ... }) => void), - toHaveTagName(tagName: string): void, - toHaveText(text: string): void, - toHaveValue(value: any): void, - toIncludeText(text: string): void, - toMatchElement( - element: React$Element, - options?: {| ignoreProps?: boolean, verbose?: boolean |} - ): void, - toMatchSelector(selector: string): void, - // 7.x - toHaveDisplayName(name: string): void, - ... -}; - -// DOM testing library extensions (jest-dom) -// https://github.com/testing-library/jest-dom -type DomTestingLibraryType = { - /** - * @deprecated - */ - toBeInTheDOM(container?: HTMLElement): void, - - // 4.x - toBeInTheDocument(): void, - toBeVisible(): void, - toBeEmpty(): void, - toBeDisabled(): void, - toBeEnabled(): void, - toBeInvalid(): void, - toBeRequired(): void, - toBeValid(): void, - toContainElement(element: HTMLElement | null): void, - toContainHTML(htmlText: string): void, - toHaveAttribute(attr: string, value?: any): void, - toHaveClass(...classNames: string[]): void, - toHaveFocus(): void, - toHaveFormValues(expectedValues: { [name: string]: any, ... }): void, - toHaveStyle(css: string | { [name: string]: any, ... }): void, - toHaveTextContent( - text: string | RegExp, - options?: {| normalizeWhitespace: boolean |} - ): void, - toHaveValue(value?: string | string[] | number): void, - - // 5.x - toHaveDisplayValue(value: string | string[]): void, - toBeChecked(): void, - ... -}; - -// Jest JQuery Matchers: https://github.com/unindented/custom-jquery-matchers -type JestJQueryMatchersType = { - toExist(): void, - toHaveLength(len: number): void, - toHaveId(id: string): void, - toHaveClass(className: string): void, - toHaveTag(tag: string): void, - toHaveAttr(key: string, val?: any): void, - toHaveProp(key: string, val?: any): void, - toHaveText(text: string | RegExp): void, - toHaveData(key: string, val?: any): void, - toHaveValue(val: any): void, - toHaveCss(css: { [key: string]: any, ... }): void, - toBeChecked(): void, - toBeDisabled(): void, - toBeEmpty(): void, - toBeHidden(): void, - toBeSelected(): void, - toBeVisible(): void, - toBeFocused(): void, - toBeInDom(): void, - toBeMatchedBy(sel: string): void, - toHaveDescendant(sel: string): void, - toHaveDescendantWithText(sel: string, text: string | RegExp): void, - ... -}; - -// Jest Extended Matchers: https://github.com/jest-community/jest-extended -type JestExtendedMatchersType = { - /** - * Note: Currently unimplemented - * Passing assertion - * - * @param {String} message - */ - // pass(message: string): void; - - /** - * Note: Currently unimplemented - * Failing assertion - * - * @param {String} message - */ - // fail(message: string): void; - - /** - * Use .toBeEmpty when checking if a String '', Array [] or Object {} is empty. - */ - toBeEmpty(): void, - /** - * Use .toBeOneOf when checking if a value is a member of a given Array. - * @param {Array.<*>} members - */ - toBeOneOf(members: any[]): void, - /** - * Use `.toBeNil` when checking a value is `null` or `undefined`. - */ - toBeNil(): void, - /** - * Use `.toSatisfy` when you want to use a custom matcher by supplying a predicate function that returns a `Boolean`. - * @param {Function} predicate - */ - toSatisfy(predicate: (n: any) => boolean): void, - /** - * Use `.toBeArray` when checking if a value is an `Array`. - */ - toBeArray(): void, - /** - * Use `.toBeArrayOfSize` when checking if a value is an `Array` of size x. - * @param {Number} x - */ - toBeArrayOfSize(x: number): void, - /** - * Use `.toIncludeAllMembers` when checking if an `Array` contains all of the same members of a given set. - * @param {Array.<*>} members - */ - toIncludeAllMembers(members: any[]): void, - /** - * Use `.toIncludeAnyMembers` when checking if an `Array` contains any of the members of a given set. - * @param {Array.<*>} members - */ - toIncludeAnyMembers(members: any[]): void, - /** - * Use `.toSatisfyAll` when you want to use a custom matcher by supplying a predicate function that returns a `Boolean` for all values in an array. - * @param {Function} predicate - */ - toSatisfyAll(predicate: (n: any) => boolean): void, - /** - * Use `.toBeBoolean` when checking if a value is a `Boolean`. - */ - toBeBoolean(): void, - /** - * Use `.toBeTrue` when checking a value is equal (===) to `true`. - */ - toBeTrue(): void, - /** - * Use `.toBeFalse` when checking a value is equal (===) to `false`. - */ - toBeFalse(): void, - /** - * Use .toBeDate when checking if a value is a Date. - */ - toBeDate(): void, - /** - * Use `.toBeFunction` when checking if a value is a `Function`. - */ - toBeFunction(): void, - /** - * Use `.toHaveBeenCalledBefore` when checking if a `Mock` was called before another `Mock`. - * - * Note: Required Jest version >22 - * Note: Your mock functions will have to be asynchronous to cause the timestamps inside of Jest to occur in a differentJS event loop, otherwise the mock timestamps will all be the same - * - * @param {Mock} mock - */ - toHaveBeenCalledBefore(mock: JestMockFn): void, - /** - * Use `.toBeNumber` when checking if a value is a `Number`. - */ - toBeNumber(): void, - /** - * Use `.toBeNaN` when checking a value is `NaN`. - */ - toBeNaN(): void, - /** - * Use `.toBeFinite` when checking if a value is a `Number`, not `NaN` or `Infinity`. - */ - toBeFinite(): void, - /** - * Use `.toBePositive` when checking if a value is a positive `Number`. - */ - toBePositive(): void, - /** - * Use `.toBeNegative` when checking if a value is a negative `Number`. - */ - toBeNegative(): void, - /** - * Use `.toBeEven` when checking if a value is an even `Number`. - */ - toBeEven(): void, - /** - * Use `.toBeOdd` when checking if a value is an odd `Number`. - */ - toBeOdd(): void, - /** - * Use `.toBeWithin` when checking if a number is in between the given bounds of: start (inclusive) and end (exclusive). - * - * @param {Number} start - * @param {Number} end - */ - toBeWithin(start: number, end: number): void, - /** - * Use `.toBeObject` when checking if a value is an `Object`. - */ - toBeObject(): void, - /** - * Use `.toContainKey` when checking if an object contains the provided key. - * - * @param {String} key - */ - toContainKey(key: string): void, - /** - * Use `.toContainKeys` when checking if an object has all of the provided keys. - * - * @param {Array.} keys - */ - toContainKeys(keys: string[]): void, - /** - * Use `.toContainAllKeys` when checking if an object only contains all of the provided keys. - * - * @param {Array.} keys - */ - toContainAllKeys(keys: string[]): void, - /** - * Use `.toContainAnyKeys` when checking if an object contains at least one of the provided keys. - * - * @param {Array.} keys - */ - toContainAnyKeys(keys: string[]): void, - /** - * Use `.toContainValue` when checking if an object contains the provided value. - * - * @param {*} value - */ - toContainValue(value: any): void, - /** - * Use `.toContainValues` when checking if an object contains all of the provided values. - * - * @param {Array.<*>} values - */ - toContainValues(values: any[]): void, - /** - * Use `.toContainAllValues` when checking if an object only contains all of the provided values. - * - * @param {Array.<*>} values - */ - toContainAllValues(values: any[]): void, - /** - * Use `.toContainAnyValues` when checking if an object contains at least one of the provided values. - * - * @param {Array.<*>} values - */ - toContainAnyValues(values: any[]): void, - /** - * Use `.toContainEntry` when checking if an object contains the provided entry. - * - * @param {Array.} entry - */ - toContainEntry(entry: [string, string]): void, - /** - * Use `.toContainEntries` when checking if an object contains all of the provided entries. - * - * @param {Array.>} entries - */ - toContainEntries(entries: [string, string][]): void, - /** - * Use `.toContainAllEntries` when checking if an object only contains all of the provided entries. - * - * @param {Array.>} entries - */ - toContainAllEntries(entries: [string, string][]): void, - /** - * Use `.toContainAnyEntries` when checking if an object contains at least one of the provided entries. - * - * @param {Array.>} entries - */ - toContainAnyEntries(entries: [string, string][]): void, - /** - * Use `.toBeExtensible` when checking if an object is extensible. - */ - toBeExtensible(): void, - /** - * Use `.toBeFrozen` when checking if an object is frozen. - */ - toBeFrozen(): void, - /** - * Use `.toBeSealed` when checking if an object is sealed. - */ - toBeSealed(): void, - /** - * Use `.toBeString` when checking if a value is a `String`. - */ - toBeString(): void, - /** - * Use `.toEqualCaseInsensitive` when checking if a string is equal (===) to another ignoring the casing of both strings. - * - * @param {String} string - */ - toEqualCaseInsensitive(string: string): void, - /** - * Use `.toStartWith` when checking if a `String` starts with a given `String` prefix. - * - * @param {String} prefix - */ - toStartWith(prefix: string): void, - /** - * Use `.toEndWith` when checking if a `String` ends with a given `String` suffix. - * - * @param {String} suffix - */ - toEndWith(suffix: string): void, - /** - * Use `.toInclude` when checking if a `String` includes the given `String` substring. - * - * @param {String} substring - */ - toInclude(substring: string): void, - /** - * Use `.toIncludeRepeated` when checking if a `String` includes the given `String` substring the correct number of times. - * - * @param {String} substring - * @param {Number} times - */ - toIncludeRepeated(substring: string, times: number): void, - /** - * Use `.toIncludeMultiple` when checking if a `String` includes all of the given substrings. - * - * @param {Array.} substring - */ - toIncludeMultiple(substring: string[]): void, - ... -}; - -// Diffing snapshot utility for Jest (snapshot-diff) -// https://github.com/jest-community/snapshot-diff -type SnapshotDiffType = { - /** - * Compare the difference between the actual in the `expect()` - * vs the object inside `valueB` with some extra options. - */ - toMatchDiffSnapshot( - valueB: any, - options?: {| - expand?: boolean, - colors?: boolean, - contextLines?: number, - stablePatchmarks?: boolean, - aAnnotation?: string, - bAnnotation?: string, - |}, - testName?: string - ): void, - ... -}; - -interface JestExpectType { - not: JestExpectType & - EnzymeMatchersType & - DomTestingLibraryType & - JestJQueryMatchersType & - JestStyledComponentsMatchersType & - JestExtendedMatchersType & - SnapshotDiffType; - /** - * If you have a mock function, you can use .lastCalledWith to test what - * arguments it was last called with. - */ - lastCalledWith(...args: Array): void; - /** - * toBe just checks that a value is what you expect. It uses === to check - * strict equality. - */ - toBe(value: any): void; - /** - * Use .toBeCalledWith to ensure that a mock function was called with - * specific arguments. - */ - toBeCalledWith(...args: Array): void; - /** - * Using exact equality with floating point numbers is a bad idea. Rounding - * means that intuitive things fail. - */ - toBeCloseTo(num: number, delta: any): void; - /** - * Use .toBeDefined to check that a variable is not undefined. - */ - toBeDefined(): void; - /** - * Use .toBeFalsy when you don't care what a value is, you just want to - * ensure a value is false in a boolean context. - */ - toBeFalsy(): void; - /** - * To compare floating point numbers, you can use toBeGreaterThan. - */ - toBeGreaterThan(number: number): void; - /** - * To compare floating point numbers, you can use toBeGreaterThanOrEqual. - */ - toBeGreaterThanOrEqual(number: number): void; - /** - * To compare floating point numbers, you can use toBeLessThan. - */ - toBeLessThan(number: number): void; - /** - * To compare floating point numbers, you can use toBeLessThanOrEqual. - */ - toBeLessThanOrEqual(number: number): void; - /** - * Use .toBeInstanceOf(Class) to check that an object is an instance of a - * class. - */ - toBeInstanceOf(cls: Class<*>): void; - /** - * .toBeNull() is the same as .toBe(null) but the error messages are a bit - * nicer. - */ - toBeNull(): void; - /** - * Use .toBeTruthy when you don't care what a value is, you just want to - * ensure a value is true in a boolean context. - */ - toBeTruthy(): void; - /** - * Use .toBeUndefined to check that a variable is undefined. - */ - toBeUndefined(): void; - /** - * Use .toContain when you want to check that an item is in a list. For - * testing the items in the list, this uses ===, a strict equality check. - */ - toContain(item: any): void; - /** - * Use .toContainEqual when you want to check that an item is in a list. For - * testing the items in the list, this matcher recursively checks the - * equality of all fields, rather than checking for object identity. - */ - toContainEqual(item: any): void; - /** - * Use .toEqual when you want to check that two objects have the same value. - * This matcher recursively checks the equality of all fields, rather than - * checking for object identity. - */ - toEqual(value: any): void; - /** - * Use .toHaveBeenCalled to ensure that a mock function got called. - */ - toHaveBeenCalled(): void; - toBeCalled(): void; - /** - * Use .toHaveBeenCalledTimes to ensure that a mock function got called exact - * number of times. - */ - toHaveBeenCalledTimes(number: number): void; - toBeCalledTimes(number: number): void; - /** - * - */ - toHaveBeenNthCalledWith(nthCall: number, ...args: Array): void; - nthCalledWith(nthCall: number, ...args: Array): void; - /** - * - */ - toHaveReturned(): void; - toReturn(): void; - /** - * - */ - toHaveReturnedTimes(number: number): void; - toReturnTimes(number: number): void; - /** - * - */ - toHaveReturnedWith(value: any): void; - toReturnWith(value: any): void; - /** - * - */ - toHaveLastReturnedWith(value: any): void; - lastReturnedWith(value: any): void; - /** - * - */ - toHaveNthReturnedWith(nthCall: number, value: any): void; - nthReturnedWith(nthCall: number, value: any): void; - /** - * Use .toHaveBeenCalledWith to ensure that a mock function was called with - * specific arguments. - */ - toHaveBeenCalledWith(...args: Array): void; - toBeCalledWith(...args: Array): void; - /** - * Use .toHaveBeenLastCalledWith to ensure that a mock function was last called - * with specific arguments. - */ - toHaveBeenLastCalledWith(...args: Array): void; - lastCalledWith(...args: Array): void; - /** - * Check that an object has a .length property and it is set to a certain - * numeric value. - */ - toHaveLength(number: number): void; - /** - * - */ - toHaveProperty(propPath: string | $ReadOnlyArray, value?: any): void; - /** - * Use .toMatch to check that a string matches a regular expression or string. - */ - toMatch(regexpOrString: RegExp | string): void; - /** - * Use .toMatchObject to check that a javascript object matches a subset of the properties of an object. - */ - toMatchObject(object: Object | Array): void; - /** - * Use .toStrictEqual to check that a javascript object matches a subset of the properties of an object. - */ - toStrictEqual(value: any): void; - /** - * This ensures that an Object matches the most recent snapshot. - */ - toMatchSnapshot(propertyMatchers?: any, name?: string): void; - /** - * This ensures that an Object matches the most recent snapshot. - */ - toMatchSnapshot(name: string): void; - - toMatchInlineSnapshot(snapshot?: string): void; - toMatchInlineSnapshot(propertyMatchers?: any, snapshot?: string): void; - /** - * Use .toThrow to test that a function throws when it is called. - * If you want to test that a specific error gets thrown, you can provide an - * argument to toThrow. The argument can be a string for the error message, - * a class for the error, or a regex that should match the error. - * - * Alias: .toThrowError - */ - toThrow(message?: string | Error | Class | RegExp): void; - toThrowError(message?: string | Error | Class | RegExp): void; - /** - * Use .toThrowErrorMatchingSnapshot to test that a function throws a error - * matching the most recent snapshot when it is called. - */ - toThrowErrorMatchingSnapshot(): void; - toThrowErrorMatchingInlineSnapshot(snapshot?: string): void; -} - -type JestObjectType = { - /** - * Disables automatic mocking in the module loader. - * - * After this method is called, all `require()`s will return the real - * versions of each module (rather than a mocked version). - */ - disableAutomock(): JestObjectType, - /** - * An un-hoisted version of disableAutomock - */ - autoMockOff(): JestObjectType, - /** - * Enables automatic mocking in the module loader. - */ - enableAutomock(): JestObjectType, - /** - * An un-hoisted version of enableAutomock - */ - autoMockOn(): JestObjectType, - /** - * Clears the mock.calls and mock.instances properties of all mocks. - * Equivalent to calling .mockClear() on every mocked function. - */ - clearAllMocks(): JestObjectType, - /** - * Resets the state of all mocks. Equivalent to calling .mockReset() on every - * mocked function. - */ - resetAllMocks(): JestObjectType, - /** - * Restores all mocks back to their original value. - */ - restoreAllMocks(): JestObjectType, - /** - * Removes any pending timers from the timer system. - */ - clearAllTimers(): void, - /** - * Returns the number of fake timers still left to run. - */ - getTimerCount(): number, - /** - * The same as `mock` but not moved to the top of the expectation by - * babel-jest. - */ - doMock(moduleName: string, moduleFactory?: any): JestObjectType, - /** - * The same as `unmock` but not moved to the top of the expectation by - * babel-jest. - */ - dontMock(moduleName: string): JestObjectType, - /** - * Returns a new, unused mock function. Optionally takes a mock - * implementation. - */ - fn, TReturn>( - implementation?: (...args: TArguments) => TReturn - ): JestMockFn, - /** - * Determines if the given function is a mocked function. - */ - isMockFunction(fn: Function): boolean, - /** - * Given the name of a module, use the automatic mocking system to generate a - * mocked version of the module for you. - */ - genMockFromModule(moduleName: string): any, - /** - * Mocks a module with an auto-mocked version when it is being required. - * - * The second argument can be used to specify an explicit module factory that - * is being run instead of using Jest's automocking feature. - * - * The third argument can be used to create virtual mocks -- mocks of modules - * that don't exist anywhere in the system. - */ - mock( - moduleName: string, - moduleFactory?: any, - options?: Object - ): JestObjectType, - /** - * Returns the actual module instead of a mock, bypassing all checks on - * whether the module should receive a mock implementation or not. - */ - requireActual(moduleName: string): any, - /** - * Returns a mock module instead of the actual module, bypassing all checks - * on whether the module should be required normally or not. - */ - requireMock(moduleName: string): any, - /** - * Resets the module registry - the cache of all required modules. This is - * useful to isolate modules where local state might conflict between tests. - */ - resetModules(): JestObjectType, - /** - * Creates a sandbox registry for the modules that are loaded inside the - * callback function. This is useful to isolate specific modules for every - * test so that local module state doesn't conflict between tests. - */ - isolateModules(fn: () => void): JestObjectType, - /** - * Exhausts the micro-task queue (usually interfaced in node via - * process.nextTick). - */ - runAllTicks(): void, - /** - * Exhausts the macro-task queue (i.e., all tasks queued by setTimeout(), - * setInterval(), and setImmediate()). - */ - runAllTimers(): void, - /** - * Exhausts all tasks queued by setImmediate(). - */ - runAllImmediates(): void, - /** - * Executes only the macro task queue (i.e. all tasks queued by setTimeout() - * or setInterval() and setImmediate()). - */ - advanceTimersByTime(msToRun: number): void, - /** - * Executes only the macro task queue (i.e. all tasks queued by setTimeout() - * or setInterval() and setImmediate()). - * - * Renamed to `advanceTimersByTime`. - */ - runTimersToTime(msToRun: number): void, - /** - * Executes only the macro-tasks that are currently pending (i.e., only the - * tasks that have been queued by setTimeout() or setInterval() up to this - * point) - */ - runOnlyPendingTimers(): void, - /** - * Explicitly supplies the mock object that the module system should return - * for the specified module. Note: It is recommended to use jest.mock() - * instead. - */ - setMock(moduleName: string, moduleExports: any): JestObjectType, - /** - * Indicates that the module system should never return a mocked version of - * the specified module from require() (e.g. that it should always return the - * real module). - */ - unmock(moduleName: string): JestObjectType, - /** - * Instructs Jest to use fake versions of the standard timer functions - * (setTimeout, setInterval, clearTimeout, clearInterval, nextTick, - * setImmediate and clearImmediate). - */ - useFakeTimers(mode?: 'modern' | 'legacy'): JestObjectType, - /** - * Instructs Jest to use the real versions of the standard timer functions. - */ - useRealTimers(): JestObjectType, - /** - * Creates a mock function similar to jest.fn but also tracks calls to - * object[methodName]. - */ - spyOn( - object: Object, - methodName: string, - accessType?: 'get' | 'set' - ): JestMockFn, - /** - * Set the default timeout interval for tests and before/after hooks in milliseconds. - * Note: The default timeout interval is 5 seconds if this method is not called. - */ - setTimeout(timeout: number): JestObjectType, - ... -}; - -type JestSpyType = { calls: JestCallsType, ... }; - -type JestDoneFn = {| - (): void, - fail: (error: Error) => void, -|}; - -/** Runs this function after every test inside this context */ -declare function afterEach( - fn: (done: JestDoneFn) => ?Promise, - timeout?: number -): void; -/** Runs this function before every test inside this context */ -declare function beforeEach( - fn: (done: JestDoneFn) => ?Promise, - timeout?: number -): void; -/** Runs this function after all tests have finished inside this context */ -declare function afterAll( - fn: (done: JestDoneFn) => ?Promise, - timeout?: number -): void; -/** Runs this function before any tests have started inside this context */ -declare function beforeAll( - fn: (done: JestDoneFn) => ?Promise, - timeout?: number -): void; - -/** A context for grouping tests together */ -declare var describe: { - /** - * Creates a block that groups together several related tests in one "test suite" - */ - (name: JestTestName, fn: () => void): void, - /** - * Only run this describe block - */ - only(name: JestTestName, fn: () => void): void, - /** - * Skip running this describe block - */ - skip(name: JestTestName, fn: () => void): void, - /** - * each runs this test against array of argument arrays per each run - * - * @param {table} table of Test - */ - each( - ...table: Array | mixed> | [Array, string] - ): ( - name: JestTestName, - fn?: (...args: Array) => ?Promise, - timeout?: number - ) => void, - ... -}; - -/** An individual test unit */ -declare var it: { - /** - * An individual test unit - * - * @param {JestTestName} Name of Test - * @param {Function} Test - * @param {number} Timeout for the test, in milliseconds. - */ - ( - name: JestTestName, - fn?: (done: JestDoneFn) => ?Promise, - timeout?: number - ): void, - /** - * Only run this test - * - * @param {JestTestName} Name of Test - * @param {Function} Test - * @param {number} Timeout for the test, in milliseconds. - */ - only: {| - ( - name: JestTestName, - fn?: (done: JestDoneFn) => ?Promise, - timeout?: number - ): void, - each( - ...table: Array | mixed> | [Array, string] - ): ( - name: JestTestName, - fn?: (...args: Array) => ?Promise, - timeout?: number - ) => void, - |}, - /** - * Skip running this test - * - * @param {JestTestName} Name of Test - * @param {Function} Test - * @param {number} Timeout for the test, in milliseconds. - */ - skip( - name: JestTestName, - fn?: (done: JestDoneFn) => ?Promise, - timeout?: number - ): void, - /** - * Highlight planned tests in the summary output - * - * @param {String} Name of Test to do - */ - todo(name: string): void, - /** - * Run the test concurrently - * - * @param {JestTestName} Name of Test - * @param {Function} Test - * @param {number} Timeout for the test, in milliseconds. - */ - concurrent( - name: JestTestName, - fn?: (done: JestDoneFn) => ?Promise, - timeout?: number - ): void, - /** - * each runs this test against array of argument arrays per each run - * - * @param {table} table of Test - */ - each( - ...table: Array | mixed> | [Array, string] - ): ( - name: JestTestName, - fn?: (...args: Array) => ?Promise, - timeout?: number - ) => void, - ... -}; - -declare function fit( - name: JestTestName, - fn: (done: JestDoneFn) => ?Promise, - timeout?: number -): void; -/** An individual test unit */ -declare var test: typeof it; -/** A disabled group of tests */ -declare var xdescribe: typeof describe; -/** A focused group of tests */ -declare var fdescribe: typeof describe; -/** A disabled individual test */ -declare var xit: typeof it; -/** A disabled individual test */ -declare var xtest: typeof it; - -type JestPrettyFormatColors = { - comment: { - close: string, - open: string, - ... - }, - content: { - close: string, - open: string, - ... - }, - prop: { - close: string, - open: string, - ... - }, - tag: { - close: string, - open: string, - ... - }, - value: { - close: string, - open: string, - ... - }, - ... -}; - -type JestPrettyFormatIndent = (string) => string; -type JestPrettyFormatRefs = Array; -type JestPrettyFormatPrint = (any) => string; -type JestPrettyFormatStringOrNull = string | null; - -type JestPrettyFormatOptions = {| - callToJSON: boolean, - edgeSpacing: string, - escapeRegex: boolean, - highlight: boolean, - indent: number, - maxDepth: number, - min: boolean, - plugins: JestPrettyFormatPlugins, - printFunctionName: boolean, - spacing: string, - theme: {| - comment: string, - content: string, - prop: string, - tag: string, - value: string, - |}, -|}; - -type JestPrettyFormatPlugin = { - print: ( - val: any, - serialize: JestPrettyFormatPrint, - indent: JestPrettyFormatIndent, - opts: JestPrettyFormatOptions, - colors: JestPrettyFormatColors - ) => string, - test: (any) => boolean, - ... -}; - -type JestPrettyFormatPlugins = Array; - -/** The expect function is used every time you want to test a value */ -declare var expect: { - /** The object that you want to make assertions against */ - ( - value: any - ): JestExpectType & - JestPromiseType & - EnzymeMatchersType & - DomTestingLibraryType & - JestJQueryMatchersType & - JestStyledComponentsMatchersType & - JestExtendedMatchersType & - SnapshotDiffType, - /** Add additional Jasmine matchers to Jest's roster */ - extend(matchers: { [name: string]: JestMatcher, ... }): void, - /** Add a module that formats application-specific data structures. */ - addSnapshotSerializer(pluginModule: JestPrettyFormatPlugin): void, - assertions(expectedAssertions: number): void, - hasAssertions(): void, - any(value: mixed): JestAsymmetricEqualityType, - anything(): any, - arrayContaining(value: Array): Array, - objectContaining(value: Object): Object, - /** Matches any received string that contains the exact expected string. */ - stringContaining(value: string): string, - stringMatching(value: string | RegExp): string, - not: { - arrayContaining: (value: $ReadOnlyArray) => Array, - objectContaining: (value: { ... }) => Object, - stringContaining: (value: string) => string, - stringMatching: (value: string | RegExp) => string, - ... - }, - ... -}; - -// TODO handle return type -// http://jasmine.github.io/2.4/introduction.html#section-Spies -declare function spyOn(value: mixed, method: string): Object; - -/** Holds all functions related to manipulating test runner */ -declare var jest: JestObjectType; - -/** - * The global Jasmine object, this is generally not exposed as the public API, - * using features inside here could break in later versions of Jest. - */ -declare var jasmine: { - DEFAULT_TIMEOUT_INTERVAL: number, - any(value: mixed): JestAsymmetricEqualityType, - anything(): any, - arrayContaining(value: Array): Array, - clock(): JestClockType, - createSpy(name: string): JestSpyType, - createSpyObj( - baseName: string, - methodNames: Array - ): { [methodName: string]: JestSpyType, ... }, - objectContaining(value: Object): Object, - stringMatching(value: string): string, - ... -}; diff --git a/web3.js/jest-environment.js b/web3.js/jest-environment.js deleted file mode 100644 index a8067ad45a..0000000000 --- a/web3.js/jest-environment.js +++ /dev/null @@ -1,27 +0,0 @@ -const NodeEnvironment = require('jest-environment-node'); - -class CustomEnvironment extends NodeEnvironment { - constructor(config, context) { - Object.assign(config.globals, { - Uint8Array, - ArrayBuffer, - }); - super(config, context); - this.testPath = context.testPath; - this.docblockPragmas = context.docblockPragmas; - } - - async setup() { - await super.setup(); - } - - async teardown() { - await super.teardown(); - } - - runScript(script) { - return super.runScript(script); - } -} - -module.exports = CustomEnvironment; diff --git a/web3.js/jest.json b/web3.js/jest.json deleted file mode 100644 index 3a8c161a03..0000000000 --- a/web3.js/jest.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "collectCoverage": true, - "collectCoverageFrom": ["src/**"], - "coverageReporters": ["json", "lcov", "text-summary", "html"] -} \ No newline at end of file diff --git a/web3.js/mocha.html b/web3.js/mocha.html new file mode 100644 index 0000000000..ba553b99fa --- /dev/null +++ b/web3.js/mocha.html @@ -0,0 +1,21 @@ + + + + + Mocha Tests + + + + +
+ + + + + + \ No newline at end of file diff --git a/web3.js/package.json b/web3.js/package.json index a4e2d5b993..3bc8895106 100644 --- a/web3.js/package.json +++ b/web3.js/package.json @@ -46,6 +46,7 @@ "bpf-sdk:install": "npm run clean:fixtures; bin/bpf-sdk-install.sh .", "bpf-sdk:remove-symlinks": "find bpf-sdk -type l -print -exec cp {} {}.tmp \\; -exec mv {}.tmp {} \\;", "build": "cross-env NODE_ENV=production rollup -c", + "build:browser-test": "rollup -c test/rollup.config.js", "build:fixtures": "set -ex; ./test/fixtures/noop-c/build.sh; ./test/fixtures/noop-rust/build.sh", "clean:fixtures": "make -C examples/bpf-c-noop clean ", "clean": "rimraf ./coverage ./lib", @@ -70,14 +71,13 @@ "pretty": "prettier --check '{,{examples,src,test}/**/}*.{j,t}s'", "pretty:fix": "prettier --write '{,{examples,src,test}/**/}*.{j,t}s'", "re": "semantic-release --repository-url git@github.com:solana-labs/solana-web3.js.git", - "test": "npm run build:fixtures && cross-env NODE_ENV=test jest --useStderr", - "test:cover": "npm run build:fixtures && cross-env NODE_ENV=test jest --coverage --useStderr", - "test:live": "npm run build:fixtures && cross-env NODE_ENV=test TEST_LIVE=1 jest --useStderr", + "test": "npm run build:fixtures && mocha './test/**/*.test.js'", + "test:browser": "TEST_LIVE=1 npm run build:fixtures && npm run build:browser-test && mocha-headless-chrome -f http://localhost:8080/mocha.html --timeout 180000", + "test:browser-with-server": "start-server-and-test 'http-server -p 8080' 8080 test:browser", + "test:browser-with-test-validator": "start-server-and-test 'solana-test-validator --reset --quiet' http://localhost:8899/health test:browser-with-server", + "test:live": "TEST_LIVE=1 npm run test", "test:live-with-test-validator": "start-server-and-test 'solana-test-validator --reset --quiet' http://localhost:8899/health test:live", - "test:watch": "npm run build:fixtures && cross-env NODE_ENV=test jest --watch --useStderr" - }, - "jest": { - "testEnvironment": "./jest-environment" + "test:watch": "npm run build:fixtures && npm run test" }, "dependencies": { "@babel/runtime": "^7.12.5", @@ -95,15 +95,19 @@ "tweetnacl": "^1.0.0" }, "devDependencies": { + "@babel/core": "^7.12.13", "@babel/plugin-proposal-class-properties": "^7.12.1", "@babel/plugin-transform-runtime": "^7.12.10", "@babel/preset-env": "^7.12.11", "@babel/preset-flow": "^7.12.1", + "@babel/register": "^7.12.13", "@commitlint/config-conventional": "^11.0.0", "@commitlint/travis-cli": "^11.0.0", + "@rollup/plugin-alias": "^3.1.2", "@rollup/plugin-babel": "^5.2.3", "@rollup/plugin-commonjs": "^17.1.0", "@rollup/plugin-json": "^4.1.0", + "@rollup/plugin-multi-entry": "^4.0.0", "@rollup/plugin-node-resolve": "^11.1.1", "@rollup/plugin-replace": "^2.3.4", "@solana/spl-token": "^0.0.13", @@ -111,6 +115,8 @@ "@typescript-eslint/parser": "^4.14.2", "acorn": "^8.0.1", "babel-eslint": "^11.0.0-beta.2", + "chai": "^4.3.0", + "chai-as-promised": "^7.1.1", "codecov": "^3.0.4", "cross-env": "7.0.3", "elfy": "^1.0.0", @@ -121,22 +127,21 @@ "esdoc-importpath-plugin": "^1.0.2", "esdoc-inject-style-plugin": "^1.0.0", "esdoc-standard-plugin": "^1.0.0", -<<<<<<< HEAD - "eslint": "7.19.0", -======= "eslint": "^7.19.0", ->>>>>>> 37215dbc2... feat: add support for browser es modules "eslint-config-prettier": "^7.0.0", "eslint-plugin-flowtype": "^5.2.0", "eslint-plugin-import": "2.22.1", - "eslint-plugin-jest": "22.19.0", "eslint-plugin-prettier": "^3.0.0", + "esm": "^3.2.25", "flow-bin": "0.130.0", "flow-remove-types": "^2.143.1", "flow-typed": "3.2.1", "fs-file-tree": "1.1.1", - "jest": "26.6.3", + "http-server": "^0.12.3", "marked": "^1.1.0", + "mocha": "^8.2.1", + "mocha-headless-chrome": "^3.1.0", + "mockttp": "^1.1.0", "mz": "^2.7.0", "npm-run-all": "^4.1.5", "prettier": "^2.0.0", @@ -146,8 +151,11 @@ "rollup-plugin-node-polyfills": "^0.2.1", "rollup-plugin-terser": "^7.0.2", "semantic-release": "^17.0.2", - "start-server-and-test": "^1.11.6", + "sinon": "^9.2.4", + "start-server-and-test": "^1.12.0", "typescript": "^4.1.3", - "watch": "^1.0.2" + "watch": "^1.0.2", + "webpack": "^5.21.0", + "webpack-cli": "^4.5.0" } } diff --git a/web3.js/rollup.config.js b/web3.js/rollup.config.js index 862c160cc2..a0af287f11 100644 --- a/web3.js/rollup.config.js +++ b/web3.js/rollup.config.js @@ -19,7 +19,11 @@ function generateConfig(configType, format) { plugins: [ flow(), commonjs(), - nodeResolve({browser, preferBuiltins: !browser, dedupe: ['bn.js']}), + nodeResolve({ + browser, + preferBuiltins: !browser, + dedupe: ['bn.js', 'buffer'], + }), babel({ exclude: '**/node_modules/**', babelHelpers: bundle ? 'bundled' : 'runtime', diff --git a/web3.js/test/.eslintrc.js b/web3.js/test/.eslintrc.js deleted file mode 100644 index ad7f5aa1dc..0000000000 --- a/web3.js/test/.eslintrc.js +++ /dev/null @@ -1,4 +0,0 @@ -module.exports = { - // eslint-disable-line import/no-commonjs - extends: ['plugin:jest/recommended', '../.eslintrc.js'], -}; diff --git a/web3.js/test/.gitignore b/web3.js/test/.gitignore new file mode 100644 index 0000000000..1521c8b765 --- /dev/null +++ b/web3.js/test/.gitignore @@ -0,0 +1 @@ +dist diff --git a/web3.js/test/__mocks__/node-fetch.js b/web3.js/test/__mocks__/node-fetch.js deleted file mode 100644 index dbc0866768..0000000000 --- a/web3.js/test/__mocks__/node-fetch.js +++ /dev/null @@ -1,86 +0,0 @@ -// @flow - -import fetch from 'node-fetch'; - -type RpcRequest = { - method: string, - params?: Array, -}; - -type RpcResponseError = { - message: string, -}; -type RpcResponseResult = any; -type RpcResponse = { - error: ?RpcResponseError, - result: ?RpcResponseResult, -}; - -export const mockRpc: Array<[string, RpcRequest, RpcResponse]> = []; - -// Define TEST_LIVE in the environment to test against the real full node -// identified by `url` instead of using the mock -export const mockRpcEnabled = !process.env.TEST_LIVE; - -let mockNotice = true; - -// Suppress lint: 'JestMockFn' is not defined -// eslint-disable-next-line no-undef -const mock: JestMockFn = jest.fn((fetchUrl, fetchOptions) => { - if (!mockRpcEnabled) { - if (mockNotice) { - console.log( - `Note: node-fetch mock is disabled, testing live against ${fetchUrl}`, - ); - mockNotice = false; - } - return fetch(fetchUrl, fetchOptions); - } - - expect(mockRpc.length).toBeGreaterThanOrEqual(1); - const [mockUrl, mockRequest, mockResponse] = mockRpc.shift(); - - expect(fetchUrl).toBe(mockUrl); - expect(fetchOptions).toMatchObject({ - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - }); - expect(fetchOptions.body).toBeDefined(); - - const body = JSON.parse(fetchOptions.body); - expect(body).toMatchObject( - Object.assign( - {}, - { - jsonrpc: '2.0', - method: 'invalid', - }, - mockRequest, - ), - ); - - const response = Object.assign( - {}, - { - jsonrpc: '2.0', - id: body.id, - error: { - message: 'invalid error message', - }, - result: 'invalid response', - }, - mockResponse, - ); - return { - ok: true, - status: 200, - statusText: 'OK', - text: () => { - return Promise.resolve(JSON.stringify(response)); - }, - }; -}); - -export default mock; diff --git a/web3.js/test/__mocks__/rpc-websockets.js b/web3.js/test/__mocks__/rpc-websockets.js deleted file mode 100644 index fa2d9d1a7e..0000000000 --- a/web3.js/test/__mocks__/rpc-websockets.js +++ /dev/null @@ -1,77 +0,0 @@ -// @flow - -import {Client as LiveClient} from 'rpc-websockets'; -import EventEmitter from 'events'; - -type RpcRequest = { - method: string, - params?: Array, -}; - -type RpcResponse = { - context: { - slot: number, - }, - value: any, -}; - -// Define TEST_LIVE in the environment to test against the real full node -// identified by `url` instead of using the mock -export const mockRpcEnabled = !process.env.TEST_LIVE; - -export const mockRpcSocket: Array<[RpcRequest, RpcResponse]> = []; - -class MockClient extends EventEmitter { - mockOpen = false; - subscriptionCounter = 0; - - constructor() { - super(); - } - - connect() { - if (!this.mockOpen) { - this.mockOpen = true; - this.emit('open'); - } - } - - close() { - if (this.mockOpen) { - this.mockOpen = false; - this.emit('close'); - } - } - - notify(): Promise { - return Promise.resolve(); - } - - on(event: string, callback: Function): this { - return super.on(event, callback); - } - - call(method: string, params: Array): Promise { - expect(mockRpcSocket.length).toBeGreaterThanOrEqual(1); - const [mockRequest, mockResponse] = mockRpcSocket.shift(); - - expect(method).toBe(mockRequest.method); - expect(params).toMatchObject(mockRequest.params); - - let id = this.subscriptionCounter++; - const response = { - subscription: id, - result: mockResponse, - }; - - setImmediate(() => { - const eventName = method.replace('Subscribe', 'Notification'); - this.emit(eventName, response); - }); - - return Promise.resolve(id); - } -} - -const Client = mockRpcEnabled ? MockClient : LiveClient; -export {Client}; diff --git a/web3.js/test/account.test.js b/web3.js/test/account.test.js index 89c37f6ba8..97ead73642 100644 --- a/web3.js/test/account.test.js +++ b/web3.js/test/account.test.js @@ -1,80 +1,84 @@ // @flow import {Account} from '../src/account'; +import {expect} from 'chai'; +import {Buffer} from 'buffer'; -test('generate new account', () => { - const account = new Account(); - expect(account.secretKey).toHaveLength(64); -}); +describe('Account', () => { + it('generate new account', () => { + const account = new Account(); + expect(account.secretKey).to.have.length(64); + }); -test('account from secret key', () => { - const secretKey = Buffer.from([ - 153, - 218, - 149, - 89, - 225, - 94, - 145, - 62, - 233, - 171, - 46, - 83, - 227, - 223, - 173, - 87, - 93, - 163, - 59, - 73, - 190, - 17, - 37, - 187, - 146, - 46, - 51, - 73, - 79, - 73, - 136, - 40, - 27, - 47, - 73, - 9, - 110, - 62, - 93, - 189, - 15, - 207, - 169, - 192, - 192, - 205, - 146, - 217, - 171, - 59, - 33, - 84, - 75, - 52, - 213, - 221, - 74, - 101, - 217, - 139, - 135, - 139, - 153, - 34, - ]); - const account = new Account(secretKey); - expect(account.publicKey.toBase58()).toBe( - '2q7pyhPwAwZ3QMfZrnAbDhnh9mDUqycszcpf86VgQxhF', - ); + it('account from secret key', () => { + const secretKey = Buffer.from([ + 153, + 218, + 149, + 89, + 225, + 94, + 145, + 62, + 233, + 171, + 46, + 83, + 227, + 223, + 173, + 87, + 93, + 163, + 59, + 73, + 190, + 17, + 37, + 187, + 146, + 46, + 51, + 73, + 79, + 73, + 136, + 40, + 27, + 47, + 73, + 9, + 110, + 62, + 93, + 189, + 15, + 207, + 169, + 192, + 192, + 205, + 146, + 217, + 171, + 59, + 33, + 84, + 75, + 52, + 213, + 221, + 74, + 101, + 217, + 139, + 135, + 139, + 153, + 34, + ]); + const account = new Account(secretKey); + expect(account.publicKey.toBase58()).to.eq( + '2q7pyhPwAwZ3QMfZrnAbDhnh9mDUqycszcpf86VgQxhF', + ); + }); }); diff --git a/web3.js/test/agent-manager.test.js b/web3.js/test/agent-manager.test.js index 244a27eb81..c731fdd2dc 100644 --- a/web3.js/test/agent-manager.test.js +++ b/web3.js/test/agent-manager.test.js @@ -1,40 +1,41 @@ // @flow import {AgentManager, DESTROY_TIMEOUT_MS} from '../src/agent-manager'; +import {expect} from 'chai'; import {sleep} from '../src/util/sleep'; -jest.setTimeout(10 * 1000); +describe('AgentManager', () => { + it('works', async () => { + const manager = new AgentManager(); + const agent = manager._agent; + expect(manager._activeRequests).to.eq(0); + expect(manager._destroyTimeout).to.be.null; -test('agent manager', async () => { - const manager = new AgentManager(); - const agent = manager._agent; - expect(manager._activeRequests).toBe(0); - expect(manager._destroyTimeout).toBeNull(); + manager.requestStart(); - manager.requestStart(); + expect(manager._activeRequests).to.eq(1); + expect(manager._destroyTimeout).to.be.null; - expect(manager._activeRequests).toBe(1); - expect(manager._destroyTimeout).toBeNull(); + manager.requestEnd(); - manager.requestEnd(); + expect(manager._activeRequests).to.eq(0); + expect(manager._destroyTimeout).not.to.be.null; - expect(manager._activeRequests).toBe(0); - expect(manager._destroyTimeout).not.toBeNull(); + manager.requestStart(); + manager.requestStart(); - manager.requestStart(); - manager.requestStart(); + expect(manager._activeRequests).to.eq(2); + expect(manager._destroyTimeout).to.be.null; - expect(manager._activeRequests).toBe(2); - expect(manager._destroyTimeout).toBeNull(); + manager.requestEnd(); + manager.requestEnd(); - manager.requestEnd(); - manager.requestEnd(); + expect(manager._activeRequests).to.eq(0); + expect(manager._destroyTimeout).not.to.be.null; + expect(manager._agent).to.eq(agent); - expect(manager._activeRequests).toBe(0); - expect(manager._destroyTimeout).not.toBeNull(); - expect(manager._agent).toBe(agent); + await sleep(DESTROY_TIMEOUT_MS); - await sleep(DESTROY_TIMEOUT_MS); - - expect(manager._agent).not.toBe(agent); + expect(manager._agent).not.to.eq(agent); + }).timeout(2 * DESTROY_TIMEOUT_MS); }); diff --git a/web3.js/test/bpf-loader.test.js b/web3.js/test/bpf-loader.test.js index 8919c693e4..d6f4aa1f84 100644 --- a/web3.js/test/bpf-loader.test.js +++ b/web3.js/test/bpf-loader.test.js @@ -1,6 +1,8 @@ // @flow import fs from 'mz/fs'; +import {expect, use} from 'chai'; +import chaiAsPromised from 'chai-as-promised'; import { Connection, @@ -9,263 +11,278 @@ import { sendAndConfirmTransaction, Account, } from '../src'; -import {mockRpcEnabled} from './__mocks__/node-fetch'; import {url} from './url'; -import {newAccountWithLamports} from './new-account-with-lamports'; import {BPF_LOADER_PROGRAM_ID} from '../src/bpf-loader'; +import {helpers} from './mocks/rpc-http'; -if (!mockRpcEnabled) { - // The default of 5 seconds is too slow for live testing sometimes - jest.setTimeout(240000); -} +use(chaiAsPromised); -test('load BPF C program', async () => { - if (mockRpcEnabled) { - console.log('non-live test skipped'); - return; - } +if (process.env.TEST_LIVE) { + describe('BPF Loader', () => { + it('load BPF C program', async () => { + const data = await fs.readFile('test/fixtures/noop-c/noop.so'); - const data = await fs.readFile('test/fixtures/noop-c/noop.so'); + const connection = new Connection(url, 'singleGossip'); + const {feeCalculator} = await connection.getRecentBlockhash(); + const fees = + feeCalculator.lamportsPerSignature * + BpfLoader.getMinNumSignatures(data.length); + const payerBalance = await connection.getMinimumBalanceForRentExemption( + 0, + ); + const executableBalance = await connection.getMinimumBalanceForRentExemption( + data.length, + ); - const connection = new Connection(url, 'singleGossip'); - const {feeCalculator} = await connection.getRecentBlockhash(); - const fees = - feeCalculator.lamportsPerSignature * - BpfLoader.getMinNumSignatures(data.length); - const payerBalance = await connection.getMinimumBalanceForRentExemption(0); - const executableBalance = await connection.getMinimumBalanceForRentExemption( - data.length, - ); - const from = await newAccountWithLamports( - connection, - payerBalance + fees + executableBalance, - ); + const from = new Account(); + await helpers.airdrop({ + connection, + address: from.publicKey, + amount: payerBalance + fees + executableBalance, + }); - const program = new Account(); - await BpfLoader.load(connection, from, program, data, BPF_LOADER_PROGRAM_ID); - - // Check that program loading costed exactly `fees + executableBalance` - const fromBalance = await connection.getBalance(from.publicKey); - expect(fromBalance).toEqual(payerBalance); - - const transaction = new Transaction().add({ - keys: [{pubkey: from.publicKey, isSigner: true, isWritable: true}], - programId: program.publicKey, - }); - await sendAndConfirmTransaction(connection, transaction, [from], { - commitment: 'singleGossip', - preflightCommitment: 'singleGossip', - }); -}); - -describe('load BPF Rust program', () => { - if (mockRpcEnabled) { - console.log('non-live test skipped'); - return; - } - - const connection = new Connection(url, 'singleGossip'); - - let program: Account; - let payerAccount: Account; - let programData: Buffer; - - beforeAll(async () => { - programData = await fs.readFile( - 'test/fixtures/noop-rust/solana_bpf_rust_noop.so', - ); - - const {feeCalculator} = await connection.getRecentBlockhash(); - const fees = - feeCalculator.lamportsPerSignature * - BpfLoader.getMinNumSignatures(programData.length); - const payerBalance = await connection.getMinimumBalanceForRentExemption(0); - const executableBalance = await connection.getMinimumBalanceForRentExemption( - programData.length, - ); - - payerAccount = await newAccountWithLamports( - connection, - payerBalance + fees + executableBalance, - ); - - // Create program account with low balance - program = await newAccountWithLamports(connection, executableBalance - 1); - - // First load will fail part way due to lack of funds - const insufficientPayerAccount = await newAccountWithLamports( - connection, - 2 * feeCalculator.lamportsPerSignature * 8, - ); - - const failedLoad = BpfLoader.load( - connection, - insufficientPayerAccount, - program, - programData, - BPF_LOADER_PROGRAM_ID, - ); - await expect(failedLoad).rejects.toThrow(); - - // Second load will succeed - await BpfLoader.load( - connection, - payerAccount, - program, - programData, - BPF_LOADER_PROGRAM_ID, - ); - }); - - test('get confirmed transaction', async () => { - const transaction = new Transaction().add({ - keys: [ - {pubkey: payerAccount.publicKey, isSigner: true, isWritable: true}, - ], - programId: program.publicKey, - }); - - const signature = await sendAndConfirmTransaction( - connection, - transaction, - [payerAccount], - { - commitment: 'max', // `getParsedConfirmedTransaction` requires max commitment - preflightCommitment: connection.commitment || 'max', - }, - ); - - const parsedTx = await connection.getParsedConfirmedTransaction(signature); - if (parsedTx === null) { - expect(parsedTx).not.toBeNull(); - return; - } - const {signatures, message} = parsedTx.transaction; - expect(signatures[0]).toEqual(signature); - const ix = message.instructions[0]; - if (ix.parsed) { - expect('parsed' in ix).toBe(false); - } else { - expect(ix.programId.equals(program.publicKey)).toBe(true); - expect(ix.data).toEqual(''); - } - }); - - test('simulate transaction', async () => { - const simulatedTransaction = new Transaction().add({ - keys: [ - {pubkey: payerAccount.publicKey, isSigner: true, isWritable: true}, - ], - programId: program.publicKey, - }); - - const {err, logs} = ( - await connection.simulateTransaction(simulatedTransaction, [payerAccount]) - ).value; - expect(err).toBeNull(); - - if (logs === null) { - expect(logs).not.toBeNull(); - return; - } - - expect(logs.length).toBeGreaterThanOrEqual(2); - expect(logs[0]).toEqual( - `Program ${program.publicKey.toBase58()} invoke [1]`, - ); - expect(logs[logs.length - 1]).toEqual( - `Program ${program.publicKey.toBase58()} success`, - ); - }); - - test('deprecated - simulate transaction without signature verification', async () => { - const simulatedTransaction = new Transaction().add({ - keys: [ - {pubkey: payerAccount.publicKey, isSigner: true, isWritable: true}, - ], - programId: program.publicKey, - }); - - simulatedTransaction.setSigners(payerAccount.publicKey); - const {err, logs} = ( - await connection.simulateTransaction(simulatedTransaction) - ).value; - expect(err).toBeNull(); - - if (logs === null) { - expect(logs).not.toBeNull(); - return; - } - - expect(logs.length).toBeGreaterThanOrEqual(2); - expect(logs[0]).toEqual( - `Program ${program.publicKey.toBase58()} invoke [1]`, - ); - expect(logs[logs.length - 1]).toEqual( - `Program ${program.publicKey.toBase58()} success`, - ); - }); - - test('simulate transaction without signature verification', async () => { - const simulatedTransaction = new Transaction({ - feePayer: payerAccount.publicKey, - }).add({ - keys: [ - {pubkey: payerAccount.publicKey, isSigner: true, isWritable: true}, - ], - programId: program.publicKey, - }); - - const {err, logs} = ( - await connection.simulateTransaction(simulatedTransaction) - ).value; - expect(err).toBeNull(); - - if (logs === null) { - expect(logs).not.toBeNull(); - return; - } - - expect(logs.length).toBeGreaterThanOrEqual(2); - expect(logs[0]).toEqual( - `Program ${program.publicKey.toBase58()} invoke [1]`, - ); - expect(logs[logs.length - 1]).toEqual( - `Program ${program.publicKey.toBase58()} success`, - ); - }); - - test('simulate transaction with bad programId', async () => { - const simulatedTransaction = new Transaction().add({ - keys: [ - {pubkey: payerAccount.publicKey, isSigner: true, isWritable: true}, - ], - programId: new Account().publicKey, - }); - - simulatedTransaction.setSigners(payerAccount.publicKey); - const {err, logs} = ( - await connection.simulateTransaction(simulatedTransaction) - ).value; - expect(err).toEqual('ProgramAccountNotFound'); - - if (logs === null) { - expect(logs).not.toBeNull(); - return; - } - - expect(logs.length).toEqual(0); - }); - - test('reload program', async () => { - expect( + const program = new Account(); await BpfLoader.load( connection, - payerAccount, + from, program, - programData, + data, BPF_LOADER_PROGRAM_ID, - ), - ).toBe(false); + ); + + // Check that program loading costed exactly `fees + executableBalance` + const fromBalance = await connection.getBalance(from.publicKey); + expect(fromBalance).to.eq(payerBalance); + + const transaction = new Transaction().add({ + keys: [{pubkey: from.publicKey, isSigner: true, isWritable: true}], + programId: program.publicKey, + }); + await sendAndConfirmTransaction(connection, transaction, [from], { + commitment: 'singleGossip', + preflightCommitment: 'singleGossip', + }); + }).timeout(5000); + + describe('load BPF Rust program', () => { + const connection = new Connection(url, 'singleGossip'); + + let program = new Account(); + let payerAccount = new Account(); + let programData: Buffer; + + before(async function () { + this.timeout(60_000); + programData = await fs.readFile( + 'test/fixtures/noop-rust/solana_bpf_rust_noop.so', + ); + + const {feeCalculator} = await connection.getRecentBlockhash(); + const fees = + feeCalculator.lamportsPerSignature * + BpfLoader.getMinNumSignatures(programData.length); + const payerBalance = await connection.getMinimumBalanceForRentExemption( + 0, + ); + const executableBalance = await connection.getMinimumBalanceForRentExemption( + programData.length, + ); + + await helpers.airdrop({ + connection, + address: payerAccount.publicKey, + amount: payerBalance + fees + executableBalance, + }); + + // Create program account with low balance + await helpers.airdrop({ + connection, + address: program.publicKey, + amount: executableBalance - 1, + }); + + // First load will fail part way due to lack of funds + const insufficientPayerAccount = new Account(); + await helpers.airdrop({ + connection, + address: insufficientPayerAccount.publicKey, + amount: 2 * feeCalculator.lamportsPerSignature * 8, + }); + + const failedLoad = BpfLoader.load( + connection, + insufficientPayerAccount, + program, + programData, + BPF_LOADER_PROGRAM_ID, + ); + await expect(failedLoad).to.be.rejected; + + // Second load will succeed + await BpfLoader.load( + connection, + payerAccount, + program, + programData, + BPF_LOADER_PROGRAM_ID, + ); + }); + + it('get confirmed transaction', async () => { + const transaction = new Transaction().add({ + keys: [ + {pubkey: payerAccount.publicKey, isSigner: true, isWritable: true}, + ], + programId: program.publicKey, + }); + + const signature = await sendAndConfirmTransaction( + connection, + transaction, + [payerAccount], + { + commitment: 'max', // `getParsedConfirmedTransaction` requires max commitment + preflightCommitment: connection.commitment || 'max', + }, + ); + + const parsedTx = await connection.getParsedConfirmedTransaction( + signature, + ); + if (parsedTx === null) { + expect(parsedTx).not.to.be.null; + return; + } + const {signatures, message} = parsedTx.transaction; + expect(signatures[0]).to.eq(signature); + const ix = message.instructions[0]; + if (ix.parsed) { + expect('parsed' in ix).to.eq(false); + } else { + expect(ix.programId).to.eql(program.publicKey); + expect(ix.data).to.eq(''); + } + }).timeout(30000); + + it('simulate transaction', async () => { + const simulatedTransaction = new Transaction().add({ + keys: [ + {pubkey: payerAccount.publicKey, isSigner: true, isWritable: true}, + ], + programId: program.publicKey, + }); + + const {err, logs} = ( + await connection.simulateTransaction(simulatedTransaction, [ + payerAccount, + ]) + ).value; + expect(err).to.be.null; + + if (logs === null) { + expect(logs).not.to.be.null; + return; + } + + expect(logs.length).to.be.at.least(2); + expect(logs[0]).to.eq( + `Program ${program.publicKey.toBase58()} invoke [1]`, + ); + expect(logs[logs.length - 1]).to.eq( + `Program ${program.publicKey.toBase58()} success`, + ); + }); + + it('deprecated - simulate transaction without signature verification', async () => { + const simulatedTransaction = new Transaction().add({ + keys: [ + {pubkey: payerAccount.publicKey, isSigner: true, isWritable: true}, + ], + programId: program.publicKey, + }); + + simulatedTransaction.setSigners(payerAccount.publicKey); + const {err, logs} = ( + await connection.simulateTransaction(simulatedTransaction) + ).value; + expect(err).to.be.null; + + if (logs === null) { + expect(logs).not.to.be.null; + return; + } + + expect(logs.length).to.be.at.least(2); + expect(logs[0]).to.eq( + `Program ${program.publicKey.toBase58()} invoke [1]`, + ); + expect(logs[logs.length - 1]).to.eq( + `Program ${program.publicKey.toBase58()} success`, + ); + }); + + it('simulate transaction without signature verification', async () => { + const simulatedTransaction = new Transaction({ + feePayer: payerAccount.publicKey, + }).add({ + keys: [ + {pubkey: payerAccount.publicKey, isSigner: true, isWritable: true}, + ], + programId: program.publicKey, + }); + + const {err, logs} = ( + await connection.simulateTransaction(simulatedTransaction) + ).value; + expect(err).to.be.null; + + if (logs === null) { + expect(logs).not.to.be.null; + return; + } + + expect(logs.length).to.be.at.least(2); + expect(logs[0]).to.eq( + `Program ${program.publicKey.toBase58()} invoke [1]`, + ); + expect(logs[logs.length - 1]).to.eq( + `Program ${program.publicKey.toBase58()} success`, + ); + }); + + it('simulate transaction with bad programId', async () => { + const simulatedTransaction = new Transaction().add({ + keys: [ + {pubkey: payerAccount.publicKey, isSigner: true, isWritable: true}, + ], + programId: new Account().publicKey, + }); + + simulatedTransaction.setSigners(payerAccount.publicKey); + const {err, logs} = ( + await connection.simulateTransaction(simulatedTransaction) + ).value; + expect(err).to.eq('ProgramAccountNotFound'); + + if (logs === null) { + expect(logs).not.to.be.null; + return; + } + + expect(logs).to.have.length(0); + }); + + it('reload program', async () => { + expect( + await BpfLoader.load( + connection, + payerAccount, + program, + programData, + BPF_LOADER_PROGRAM_ID, + ), + ).to.eq(false); + }); + }); }); -}); +} diff --git a/web3.js/test/cluster.test.js b/web3.js/test/cluster.test.js index 14b9d1d925..6e00f691d9 100644 --- a/web3.js/test/cluster.test.js +++ b/web3.js/test/cluster.test.js @@ -1,16 +1,19 @@ // @flow import {clusterApiUrl} from '../src/util/cluster'; +import {expect} from 'chai'; -test('invalid', () => { - expect(() => { - // $FlowExpectedError - clusterApiUrl('abc123'); - }).toThrow(); -}); +describe('Cluster Util', () => { + it('invalid', () => { + expect(() => { + // $FlowExpectedError + clusterApiUrl('abc123'); + }).to.throw(); + }); -test('devnet', () => { - expect(clusterApiUrl()).toEqual('https://devnet.solana.com'); - expect(clusterApiUrl('devnet')).toEqual('https://devnet.solana.com'); - expect(clusterApiUrl('devnet', true)).toEqual('https://devnet.solana.com'); - expect(clusterApiUrl('devnet', false)).toEqual('http://devnet.solana.com'); + it('devnet', () => { + expect(clusterApiUrl()).to.eq('https://devnet.solana.com'); + expect(clusterApiUrl('devnet')).to.eq('https://devnet.solana.com'); + expect(clusterApiUrl('devnet', true)).to.eq('https://devnet.solana.com'); + expect(clusterApiUrl('devnet', false)).to.eq('http://devnet.solana.com'); + }); }); diff --git a/web3.js/test/connection.test.js b/web3.js/test/connection.test.js index 6e00416bce..323af03b10 100644 --- a/web3.js/test/connection.test.js +++ b/web3.js/test/connection.test.js @@ -1,6 +1,9 @@ // @flow import bs58 from 'bs58'; +import {Buffer} from 'buffer'; import {Token, u64} from '@solana/spl-token'; +import {expect, use} from 'chai'; +import chaiAsPromised from 'chai-as-promised'; import { Account, @@ -8,540 +11,434 @@ import { Connection, SystemProgram, Transaction, - sendAndConfirmTransaction, LAMPORTS_PER_SOL, Lockup, PublicKey, StakeProgram, + sendAndConfirmTransaction, } from '../src'; import {DEFAULT_TICKS_PER_SLOT, NUM_TICKS_PER_SECOND} from '../src/timing'; -import {mockRpc, mockRpcEnabled} from './__mocks__/node-fetch'; -import {mockGetRecentBlockhash} from './mockrpc/get-recent-blockhash'; -import {url} from './url'; -import {sleep} from '../src/util/sleep'; +import {MOCK_PORT, url} from './url'; import {BLOCKHASH_CACHE_TIMEOUT_MS} from '../src/connection'; +import {sleep} from '../src/util/sleep'; import type {TransactionSignature} from '../src/transaction'; -import type {SignatureStatus, TransactionError} from '../src/connection'; -import {mockConfirmTransaction} from './mockrpc/confirm-transaction'; -import {mockRpcSocket} from './__mocks__/rpc-websockets'; +import type { + Commitment, + SignatureStatus, + TransactionError, + KeyedAccountInfo, + SlotInfo, +} from '../src/connection'; -// Testing tokens and blockhash cache each take around 30s to complete -jest.setTimeout(90000); +import { + helpers, + mockErrorMessage, + mockErrorResponse, + uniqueSignature, + uniqueBlockhash, + mockRpcResponse, + mockServer, +} from './mocks/rpc-http'; +import { + stubRpcWebSocket, + restoreRpcWebSocket, + mockRpcMessage, +} from './mocks/rpc-websockets'; -const errorMessage = 'Invalid'; -const errorResponse = { - error: { - code: -32602, - message: errorMessage, - }, - result: undefined, -}; +use(chaiAsPromised); const verifySignatureStatus = ( status: SignatureStatus | null, err?: TransactionError, ): SignatureStatus => { if (status === null) { - expect(status).not.toBeNull(); + expect(status).not.to.be.null; throw new Error(); // unreachable } const expectedErr = err || null; - expect(status.err).toEqual(expectedErr); - expect(status.slot).toBeGreaterThanOrEqual(0); + expect(status.err).to.eql(expectedErr); + expect(status.slot).to.be.at.least(0); if (expectedErr !== null) return status; const confirmations = status.confirmations; if (typeof confirmations === 'number') { - expect(confirmations).toBeGreaterThanOrEqual(0); + expect(confirmations).to.be.at.least(0); } else { - expect(confirmations).toBeNull(); + expect(confirmations).to.be.null; } return status; }; -test('get account info - not found', async () => { - const account = new Account(); - const connection = new Connection(url); +describe('Connection', () => { + let connection: Connection; + beforeEach(() => { + connection = new Connection(url); + }); - mockRpc.push([ - url, - { + if (!process.env.TEST_LIVE) { + beforeEach(() => { + mockServer.start(MOCK_PORT); + stubRpcWebSocket(connection); + }); + + afterEach(() => { + mockServer.stop(); + restoreRpcWebSocket(connection); + }); + } + + it('get account info - not found', async () => { + const account = new Account(); + + await mockRpcResponse({ method: 'getAccountInfo', params: [account.publicKey.toBase58(), {encoding: 'base64'}], - }, - { - error: null, - result: { - context: { - slot: 11, - }, - value: null, - }, - }, - ]); + value: null, + withContext: true, + }); - expect(await connection.getAccountInfo(account.publicKey)).toBeNull(); + expect(await connection.getAccountInfo(account.publicKey)).to.be.null; - if (!mockRpcEnabled) { - expect( - (await connection.getParsedAccountInfo(account.publicKey)).value, - ).toBeNull(); - } -}); + await mockRpcResponse({ + method: 'getAccountInfo', + params: [account.publicKey.toBase58(), {encoding: 'jsonParsed'}], + value: null, + withContext: true, + }); -test('get program accounts', async () => { - const connection = new Connection(url, 'singleGossip'); - const account0 = new Account(); - const account1 = new Account(); - const programId = new Account(); - mockRpc.push([ - url, - { - method: 'requestAirdrop', - params: [account0.publicKey.toBase58(), LAMPORTS_PER_SOL], - }, - { - error: null, - result: - '2WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk', - }, - ]); - mockConfirmTransaction( - '2WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk', - ); - let signature = await connection.requestAirdrop( - account0.publicKey, - LAMPORTS_PER_SOL, - ); - await connection.confirmTransaction(signature); - - mockRpc.push([ - url, - { - method: 'requestAirdrop', - params: [account1.publicKey.toBase58(), 0.5 * LAMPORTS_PER_SOL], - }, - { - error: null, - result: - '2WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk', - }, - ]); - mockConfirmTransaction( - '2WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk', - ); - signature = await connection.requestAirdrop( - account1.publicKey, - 0.5 * LAMPORTS_PER_SOL, - ); - await connection.confirmTransaction(signature); - - mockGetRecentBlockhash('max'); - mockRpc.push([ - url, - { - method: 'sendTransaction', - }, - { - error: null, - result: - '3WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk', - }, - ]); - - let transaction = new Transaction().add( - SystemProgram.assign({ - accountPubkey: account0.publicKey, - programId: programId.publicKey, - }), - ); - - mockConfirmTransaction( - '3WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk', - ); - await sendAndConfirmTransaction(connection, transaction, [account0], { - commitment: 'singleGossip', + expect((await connection.getParsedAccountInfo(account.publicKey)).value).to + .be.null; }); - mockRpc.push([ - url, + it('get program accounts', async () => { + const account0 = new Account(); + const account1 = new Account(); + const programId = new Account(); + { - method: 'sendTransaction', - }, - { - error: null, - result: - '3WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk', - }, - ]); + await helpers.airdrop({ + connection, + address: account0.publicKey, + amount: LAMPORTS_PER_SOL, + }); - transaction = new Transaction().add( - SystemProgram.assign({ - accountPubkey: account1.publicKey, - programId: programId.publicKey, - }), - ); - - mockConfirmTransaction( - '3WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk', - ); - await sendAndConfirmTransaction(connection, transaction, [account1], { - commitment: 'singleGossip', - }); - - mockRpc.push([ - url, - { - method: 'getFeeCalculatorForBlockhash', - params: [transaction.recentBlockhash, {commitment: 'singleGossip'}], - }, - { - error: null, - result: { - context: { - slot: 11, - }, - value: { - feeCalculator: { - lamportsPerSignature: 42, - }, - }, - }, - }, - ]); - - if (!transaction.recentBlockhash) { - expect(transaction.recentBlockhash).toBeTruthy(); - return; - } - - const feeCalculator = ( - await connection.getFeeCalculatorForBlockhash(transaction.recentBlockhash) - ).value; - - if (feeCalculator === null) { - expect(feeCalculator).not.toBeNull(); - return; - } - - mockRpc.push([ - url, - { - method: 'getProgramAccounts', - params: [ - programId.publicKey.toBase58(), - {commitment: 'singleGossip', encoding: 'base64'}, - ], - }, - { - error: null, - result: [ - { - account: { - data: ['', 'base64'], - executable: false, - lamports: LAMPORTS_PER_SOL - feeCalculator.lamportsPerSignature, - owner: programId.publicKey.toBase58(), - rentEpoch: 20, - }, - pubkey: account0.publicKey.toBase58(), - }, - { - account: { - data: ['', 'base64'], - executable: false, - lamports: - 0.5 * LAMPORTS_PER_SOL - feeCalculator.lamportsPerSignature, - owner: programId.publicKey.toBase58(), - rentEpoch: 20, - }, - pubkey: account1.publicKey.toBase58(), - }, - ], - }, - ]); - const programAccounts = await connection.getProgramAccounts( - programId.publicKey, - ); - expect(programAccounts.length).toBe(2); - - programAccounts.forEach(function (element) { - if (element.pubkey.equals(account0.publicKey)) { - expect(element.account.lamports).toBe( - LAMPORTS_PER_SOL - feeCalculator.lamportsPerSignature, + const transaction = new Transaction().add( + SystemProgram.assign({ + accountPubkey: account0.publicKey, + programId: programId.publicKey, + }), ); - } else if (element.pubkey.equals(account1.publicKey)) { - expect(element.account.lamports).toBe( - 0.5 * LAMPORTS_PER_SOL - feeCalculator.lamportsPerSignature, + + await helpers.processTransaction({ + connection, + transaction, + signers: [account0], + commitment: 'singleGossip', + }); + } + + { + await helpers.airdrop({ + connection, + address: account1.publicKey, + amount: 0.5 * LAMPORTS_PER_SOL, + }); + + const transaction = new Transaction().add( + SystemProgram.assign({ + accountPubkey: account1.publicKey, + programId: programId.publicKey, + }), ); - } else { - expect(element.pubkey.equals(account1.publicKey)).toBe(true); + + await helpers.processTransaction({ + connection, + transaction, + signers: [account1], + commitment: 'singleGossip', + }); + } + + const feeCalculator = (await helpers.recentBlockhash({connection})) + .feeCalculator; + + { + await mockRpcResponse({ + method: 'getProgramAccounts', + params: [ + programId.publicKey.toBase58(), + {commitment: 'singleGossip', encoding: 'base64'}, + ], + value: [ + { + account: { + data: ['', 'base64'], + executable: false, + lamports: LAMPORTS_PER_SOL - feeCalculator.lamportsPerSignature, + owner: programId.publicKey.toBase58(), + rentEpoch: 20, + }, + pubkey: account0.publicKey.toBase58(), + }, + { + account: { + data: ['', 'base64'], + executable: false, + lamports: + 0.5 * LAMPORTS_PER_SOL - feeCalculator.lamportsPerSignature, + owner: programId.publicKey.toBase58(), + rentEpoch: 20, + }, + pubkey: account1.publicKey.toBase58(), + }, + ], + }); + + const programAccounts = await connection.getProgramAccounts( + programId.publicKey, + 'singleGossip', + ); + expect(programAccounts).to.have.length(2); + programAccounts.forEach(function (keyedAccount) { + if (keyedAccount.pubkey.equals(account0.publicKey)) { + expect(keyedAccount.account.lamports).to.eq( + LAMPORTS_PER_SOL - feeCalculator.lamportsPerSignature, + ); + } else { + expect(keyedAccount.pubkey).to.eql(account1.publicKey); + expect(keyedAccount.account.lamports).to.eq( + 0.5 * LAMPORTS_PER_SOL - feeCalculator.lamportsPerSignature, + ); + } + }); + } + + { + await mockRpcResponse({ + method: 'getProgramAccounts', + params: [ + programId.publicKey.toBase58(), + {commitment: 'singleGossip', encoding: 'jsonParsed'}, + ], + value: [ + { + account: { + data: ['', 'base64'], + executable: false, + lamports: LAMPORTS_PER_SOL - feeCalculator.lamportsPerSignature, + owner: programId.publicKey.toBase58(), + rentEpoch: 20, + }, + pubkey: account0.publicKey.toBase58(), + }, + { + account: { + data: ['', 'base64'], + executable: false, + lamports: + 0.5 * LAMPORTS_PER_SOL - feeCalculator.lamportsPerSignature, + owner: programId.publicKey.toBase58(), + rentEpoch: 20, + }, + pubkey: account1.publicKey.toBase58(), + }, + ], + }); + + const programAccounts = await connection.getParsedProgramAccounts( + programId.publicKey, + 'singleGossip', + ); + expect(programAccounts).to.have.length(2); + + programAccounts.forEach(function (element) { + if (element.pubkey.equals(account0.publicKey)) { + expect(element.account.lamports).to.eq( + LAMPORTS_PER_SOL - feeCalculator.lamportsPerSignature, + ); + } else { + expect(element.pubkey).to.eql(account1.publicKey); + expect(element.account.lamports).to.eq( + 0.5 * LAMPORTS_PER_SOL - feeCalculator.lamportsPerSignature, + ); + } + }); } }); - if (!mockRpcEnabled) { - const programAccounts = await connection.getParsedProgramAccounts( - programId.publicKey, - ); - expect(programAccounts.length).toBe(2); + if (!process.env.TEST_LIVE) { + it('validatorExit', async () => { + await mockRpcResponse({ + method: 'validatorExit', + params: [], + value: false, + }); - programAccounts.forEach(function (element) { - if (element.pubkey.equals(account0.publicKey)) { - expect(element.account.lamports).toBe( - LAMPORTS_PER_SOL - feeCalculator.lamportsPerSignature, - ); - } else if (element.pubkey.equals(account1.publicKey)) { - expect(element.account.lamports).toBe( - 0.5 * LAMPORTS_PER_SOL - feeCalculator.lamportsPerSignature, - ); - } else { - expect(element.pubkey.equals(account1.publicKey)).toBe(true); - } + const result = await connection.validatorExit(); + expect(result).to.eq(false); }); } -}); -test('validatorExit', async () => { - if (!mockRpcEnabled) { - console.log('validatorExit skipped on live node'); - return; - } - const connection = new Connection(url); + it('get balance', async () => { + const account = new Account(); - mockRpc.push([ - url, - { - method: 'validatorExit', - }, - { - error: null, - result: false, - }, - ]); - - const result = await connection.validatorExit(); - expect(result).toBe(false); -}); - -test('get balance', async () => { - const account = new Account(); - const connection = new Connection(url); - - mockRpc.push([ - url, - { + await mockRpcResponse({ method: 'getBalance', params: [account.publicKey.toBase58()], - }, - { - error: null, - result: { + value: { context: { slot: 11, }, value: 0, }, - }, - ]); + }); - const balance = await connection.getBalance(account.publicKey); - expect(balance).toBeGreaterThanOrEqual(0); -}); + const balance = await connection.getBalance(account.publicKey); + expect(balance).to.be.at.least(0); + }); -test('get inflation', async () => { - const connection = new Connection(url); - - mockRpc.push([ - url, - { + it('get inflation', async () => { + await mockRpcResponse({ method: 'getInflationGovernor', params: [], - }, - { - error: null, - result: { + value: { foundation: 0.05, foundationTerm: 7.0, initial: 0.15, taper: 0.15, terminal: 0.015, }, - }, - ]); + }); - const inflation = await connection.getInflationGovernor(); + const inflation = await connection.getInflationGovernor(); - for (const key of [ - 'initial', - 'terminal', - 'taper', - 'foundation', - 'foundationTerm', - ]) { - expect(inflation).toHaveProperty(key); - expect(inflation[key]).toBeGreaterThan(0); - } -}); + for (const key of [ + 'initial', + 'terminal', + 'taper', + 'foundation', + 'foundationTerm', + ]) { + expect(inflation).to.have.property(key); + expect(inflation[key]).to.be.greaterThan(0); + } + }); -test('get epoch info', async () => { - const connection = new Connection(url, 'singleGossip'); - - mockRpc.push([ - url, - { + it('get epoch info', async () => { + await mockRpcResponse({ method: 'getEpochInfo', params: [{commitment: 'singleGossip'}], - }, - { - error: null, - result: { + value: { epoch: 0, slotIndex: 1, slotsInEpoch: 8192, absoluteSlot: 1, blockHeight: 1, }, - }, - ]); + }); - const epochInfo = await connection.getEpochInfo(); + const epochInfo = await connection.getEpochInfo('singleGossip'); - for (const key of [ - 'epoch', - 'slotIndex', - 'slotsInEpoch', - 'absoluteSlot', - 'blockHeight', - ]) { - expect(epochInfo).toHaveProperty(key); - expect(epochInfo[key]).toBeGreaterThanOrEqual(0); - } -}); + for (const key of [ + 'epoch', + 'slotIndex', + 'slotsInEpoch', + 'absoluteSlot', + 'blockHeight', + ]) { + expect(epochInfo).to.have.property(key); + expect(epochInfo[key]).to.be.at.least(0); + } + }); -test('get epoch schedule', async () => { - const connection = new Connection(url); - - mockRpc.push([ - url, - { + it('get epoch schedule', async () => { + await mockRpcResponse({ method: 'getEpochSchedule', params: [], - }, - { - error: null, - result: { + value: { firstNormalEpoch: 8, firstNormalSlot: 8160, leaderScheduleSlotOffset: 8192, slotsPerEpoch: 8192, warmup: true, }, - }, - ]); + }); - const epochSchedule = await connection.getEpochSchedule(); + const epochSchedule = await connection.getEpochSchedule(); - for (const key of [ - 'firstNormalEpoch', - 'firstNormalSlot', - 'leaderScheduleSlotOffset', - 'slotsPerEpoch', - ]) { - expect(epochSchedule).toHaveProperty('warmup'); - expect(epochSchedule).toHaveProperty(key); - if (epochSchedule.warmup) { - expect(epochSchedule[key]).toBeGreaterThan(0); + for (const key of [ + 'firstNormalEpoch', + 'firstNormalSlot', + 'leaderScheduleSlotOffset', + 'slotsPerEpoch', + ]) { + expect(epochSchedule).to.have.property('warmup'); + expect(epochSchedule).to.have.property(key); + if (epochSchedule.warmup) { + expect(epochSchedule[key]).to.be.greaterThan(0); + } } - } -}); + }); -test('get leader schedule', async () => { - const connection = new Connection(url); - - mockRpc.push([ - url, - { + it('get leader schedule', async () => { + await mockRpcResponse({ method: 'getLeaderSchedule', params: [], - }, - { - error: null, - result: { + value: { '123vij84ecQEKUvQ7gYMKxKwKF6PbYSzCzzURYA4xULY': [0, 1, 2, 3], '8PTjAikKoAybKXcEPnDSoy8wSNNikUBJ1iKawJKQwXnB': [4, 5, 6, 7], }, - }, - ]); + }); - const leaderSchedule = await connection.getLeaderSchedule(); - expect(Object.keys(leaderSchedule).length).toBeGreaterThanOrEqual(1); - for (const key in leaderSchedule) { - const slots = leaderSchedule[key]; - expect(Array.isArray(slots)).toBe(true); - expect(slots.length).toBeGreaterThanOrEqual(4); - } -}); + const leaderSchedule = await connection.getLeaderSchedule(); + expect(Object.keys(leaderSchedule).length).to.be.at.least(1); + for (const key in leaderSchedule) { + const slots = leaderSchedule[key]; + expect(Array.isArray(slots)).to.be.true; + expect(slots.length).to.be.at.least(4); + } + }); -test('get slot', async () => { - const connection = new Connection(url); - - mockRpc.push([ - url, - { + it('get slot', async () => { + await mockRpcResponse({ method: 'getSlot', - }, - { - error: null, - result: 123, - }, - ]); + params: [], + value: 123, + }); - const slotLeader = await connection.getSlot(); - if (mockRpcEnabled) { - expect(slotLeader).toBe(123); - } else { - // No idea what the correct slot value should be on a live cluster, so - // just check the type - expect(typeof slotLeader).toBe('number'); - } -}); + const slot = await connection.getSlot(); + if (!process.env.TEST_LIVE) { + expect(slot).to.eq(123); + } else { + // No idea what the correct slot value should be on a live cluster, so + // just check the type + expect(typeof slot).to.eq('number'); + } + }); -test('get slot leader', async () => { - const connection = new Connection(url); - - mockRpc.push([ - url, - { + it('get slot leader', async () => { + await mockRpcResponse({ method: 'getSlotLeader', - }, - { - error: null, - result: '11111111111111111111111111111111', - }, - ]); + params: [], + value: '11111111111111111111111111111111', + }); - const slotLeader = await connection.getSlotLeader(); - if (mockRpcEnabled) { - expect(slotLeader).toBe('11111111111111111111111111111111'); - } else { - // No idea what the correct slotLeader value should be on a live cluster, so - // just check the type - expect(typeof slotLeader).toBe('string'); - } -}); + const slotLeader = await connection.getSlotLeader(); + if (!process.env.TEST_LIVE) { + expect(slotLeader).to.eq('11111111111111111111111111111111'); + } else { + // No idea what the correct slotLeader value should be on a live cluster, so + // just check the type + expect(typeof slotLeader).to.eq('string'); + } + }); -test('get cluster nodes', async () => { - const connection = new Connection(url); - - mockRpc.push([ - url, - { + it('get cluster nodes', async () => { + await mockRpcResponse({ method: 'getClusterNodes', - }, - { - error: null, - result: [ + params: [], + value: [ { pubkey: '11111111111111111111111111111111', gossip: '127.0.0.0:1234', @@ -550,143 +447,98 @@ test('get cluster nodes', async () => { version: '1.1.10', }, ], - }, - ]); + }); - const clusterNodes = await connection.getClusterNodes(); - if (mockRpcEnabled) { - expect(clusterNodes).toHaveLength(1); - expect(clusterNodes[0].pubkey).toBe('11111111111111111111111111111111'); - expect(typeof clusterNodes[0].gossip).toBe('string'); - expect(typeof clusterNodes[0].tpu).toBe('string'); - expect(clusterNodes[0].rpc).toBeNull(); - } else { - // There should be at least one node (the node that we're talking to) - expect(clusterNodes.length).toBeGreaterThan(0); - } -}); + const clusterNodes = await connection.getClusterNodes(); + if (!process.env.TEST_LIVE) { + expect(clusterNodes).to.have.length(1); + expect(clusterNodes[0].pubkey).to.eq('11111111111111111111111111111111'); + expect(typeof clusterNodes[0].gossip).to.eq('string'); + expect(typeof clusterNodes[0].tpu).to.eq('string'); + expect(clusterNodes[0].rpc).to.be.null; + } else { + // There should be at least one node (the node that we're talking to) + expect(clusterNodes.length).to.be.greaterThan(0); + } + }); -test('getVoteAccounts', async () => { - if (mockRpcEnabled) { - console.log('non-live test skipped'); - return; + if (process.env.TEST_LIVE) { + it('get vote accounts', async () => { + const voteAccounts = await connection.getVoteAccounts(); + expect( + voteAccounts.current.concat(voteAccounts.delinquent).length, + ).to.be.greaterThan(0); + }); } - const connection = new Connection(url); - const voteAccounts = await connection.getVoteAccounts(); - expect( - voteAccounts.current.concat(voteAccounts.delinquent).length, - ).toBeGreaterThan(0); -}); + it('confirm transaction - error', async () => { + const badTransactionSignature = 'bad transaction signature'; -test('confirm transaction - error', async () => { - const connection = new Connection(url); + await expect( + connection.confirmTransaction(badTransactionSignature), + ).to.be.rejectedWith('signature must be base58 encoded'); - const badTransactionSignature = 'bad transaction signature'; - - await expect( - connection.confirmTransaction(badTransactionSignature), - ).rejects.toThrow('signature must be base58 encoded'); - - mockRpc.push([ - url, - { + await mockRpcResponse({ method: 'getSignatureStatuses', params: [[badTransactionSignature]], - }, - errorResponse, - ]); + error: mockErrorResponse, + }); - await expect( - connection.getSignatureStatus(badTransactionSignature), - ).rejects.toThrow(errorMessage); -}); + await expect( + connection.getSignatureStatus(badTransactionSignature), + ).to.be.rejectedWith(mockErrorMessage); + }); -test('get transaction count', async () => { - const connection = new Connection(url); - - mockRpc.push([ - url, - { + it('get transaction count', async () => { + await mockRpcResponse({ method: 'getTransactionCount', params: [], - }, - { - error: null, - result: 1000000, - }, - ]); + value: 1000000, + }); - const count = await connection.getTransactionCount(); - expect(count).toBeGreaterThanOrEqual(0); -}); + const count = await connection.getTransactionCount(); + expect(count).to.be.at.least(0); + }); -test('get total supply', async () => { - const connection = new Connection(url); - - mockRpc.push([ - url, - { + it('get total supply', async () => { + await mockRpcResponse({ method: 'getTotalSupply', params: [], - }, - { - error: null, - result: 1000000, - }, - ]); + value: 1000000, + }); - const count = await connection.getTotalSupply(); - expect(count).toBeGreaterThanOrEqual(0); -}); + const count = await connection.getTotalSupply(); + expect(count).to.be.at.least(0); + }); -test('get minimum balance for rent exemption', async () => { - const connection = new Connection(url); - - mockRpc.push([ - url, - { + it('get minimum balance for rent exemption', async () => { + await mockRpcResponse({ method: 'getMinimumBalanceForRentExemption', params: [512], - }, - { - error: null, - result: 1000000, - }, - ]); + value: 1000000, + }); - const count = await connection.getMinimumBalanceForRentExemption(512); - expect(count).toBeGreaterThanOrEqual(0); -}); + const count = await connection.getMinimumBalanceForRentExemption(512); + expect(count).to.be.at.least(0); + }); -test('get confirmed signatures for address', async () => { - const connection = new Connection(url); + it('get confirmed signatures for address', async () => { + const connection = new Connection(url); - mockRpc.push([ - url, - { + await mockRpcResponse({ method: 'getSlot', params: [], - }, - { - error: null, - result: 1, - }, - ]); + value: 1, + }); - while ((await connection.getSlot()) <= 0) { - continue; - } + while ((await connection.getSlot()) <= 0) { + continue; + } - mockRpc.push([ - url, - { + await mockRpcResponse({ method: 'getConfirmedBlock', params: [1], - }, - { - error: null, - result: { + value: { blockhash: '57zQNBZBEiHsCZFqsaY6h176ioXy5MsSLmcvHkEyaLGy', previousBlockhash: 'H5nJ91eGag3B5ZSRHZ7zG5ZwXJ6ywCt2hyR8xCsV7xMo', parentSlot: 0, @@ -731,78 +583,60 @@ test('get confirmed signatures for address', async () => { }, ], }, - }, - ]); + }); - // Find a block that has a transaction, usually Block 1 - let slot = 0; - let address: ?PublicKey; - let expectedSignature: ?string; - while (!address || !expectedSignature) { - slot++; - const block = await connection.getConfirmedBlock(slot); - if (block.transactions.length > 0) { - const { - signature, - publicKey, - } = block.transactions[0].transaction.signatures[0]; - if (signature) { - address = publicKey; - expectedSignature = bs58.encode(signature); + // Find a block that has a transaction, usually Block 1 + let slot = 0; + let address: ?PublicKey; + let expectedSignature: ?string; + while (!address || !expectedSignature) { + slot++; + const block = await connection.getConfirmedBlock(slot); + if (block.transactions.length > 0) { + const { + signature, + publicKey, + } = block.transactions[0].transaction.signatures[0]; + if (signature) { + address = publicKey; + expectedSignature = bs58.encode(signature); + } } } - } - // getConfirmedSignaturesForAddress tests... - mockRpc.push([ - url, - { + // getConfirmedSignaturesForAddress tests... + await mockRpcResponse({ method: 'getConfirmedSignaturesForAddress', params: [address.toBase58(), slot, slot + 1], - }, - { - error: null, - result: [expectedSignature], - }, - ]); + value: [expectedSignature], + }); - const confirmedSignatures = await connection.getConfirmedSignaturesForAddress( - address, - slot, - slot + 1, - ); - expect(confirmedSignatures.includes(expectedSignature)).toBe(true); + const confirmedSignatures = await connection.getConfirmedSignaturesForAddress( + address, + slot, + slot + 1, + ); + expect(confirmedSignatures.includes(expectedSignature)).to.be.true; - const badSlot = Number.MAX_SAFE_INTEGER - 1; - mockRpc.push([ - url, - { + const badSlot = Number.MAX_SAFE_INTEGER - 1; + await mockRpcResponse({ method: 'getConfirmedSignaturesForAddress', params: [address.toBase58(), badSlot, badSlot + 1], - }, - { - error: null, - result: [], - }, - ]); + value: [], + }); - const emptySignatures = await connection.getConfirmedSignaturesForAddress( - address, - badSlot, - badSlot + 1, - ); - expect(emptySignatures.length).toBe(0); + const emptySignatures = await connection.getConfirmedSignaturesForAddress( + address, + badSlot, + badSlot + 1, + ); + expect(emptySignatures).to.have.length(0); - // getConfirmedSignaturesForAddress2 tests... - mockRpc.push([ - url, - { + // getConfirmedSignaturesForAddress2 tests... + await mockRpcResponse({ method: 'getConfirmedSignaturesForAddress2', params: [address.toBase58(), {limit: 1}], - }, - { - error: null, - result: [ + value: [ { signature: expectedSignature, slot, @@ -810,50 +644,36 @@ test('get confirmed signatures for address', async () => { memo: null, }, ], - }, - ]); + }); - const confirmedSignatures2 = await connection.getConfirmedSignaturesForAddress2( - address, - {limit: 1}, - ); - expect(confirmedSignatures2.length).toBe(1); - if (mockRpcEnabled) { - expect(confirmedSignatures2[0].signature).toBe(expectedSignature); - expect(confirmedSignatures2[0].slot).toBe(slot); - expect(confirmedSignatures2[0].err).toBeNull(); - expect(confirmedSignatures2[0].memo).toBeNull(); - } -}); + const confirmedSignatures2 = await connection.getConfirmedSignaturesForAddress2( + address, + {limit: 1}, + ); + expect(confirmedSignatures2).to.have.length(1); + if (!process.env.TEST_LIVE) { + expect(confirmedSignatures2[0].signature).to.eq(expectedSignature); + expect(confirmedSignatures2[0].slot).to.eq(slot); + expect(confirmedSignatures2[0].err).to.be.null; + expect(confirmedSignatures2[0].memo).to.be.null; + } + }); -test('get confirmed transaction', async () => { - const connection = new Connection(url); - - mockRpc.push([ - url, - { + it('get confirmed transaction', async () => { + await mockRpcResponse({ method: 'getSlot', params: [], - }, - { - error: null, - result: 1, - }, - ]); + value: 1, + }); - while ((await connection.getSlot()) <= 0) { - continue; - } + while ((await connection.getSlot()) <= 0) { + continue; + } - mockRpc.push([ - url, - { + await mockRpcResponse({ method: 'getConfirmedBlock', params: [1], - }, - { - error: null, - result: { + value: { blockhash: '57zQNBZBEiHsCZFqsaY6h176ioXy5MsSLmcvHkEyaLGy', previousBlockhash: 'H5nJ91eGag3B5ZSRHZ7zG5ZwXJ6ywCt2hyR8xCsV7xMo', parentSlot: 0, @@ -898,31 +718,25 @@ test('get confirmed transaction', async () => { }, ], }, - }, - ]); + }); - // Find a block that has a transaction, usually Block 1 - let slot = 0; - let confirmedTransaction: ?string; - while (!confirmedTransaction) { - slot++; - const block = await connection.getConfirmedBlock(slot); - for (const tx of block.transactions) { - if (tx.transaction.signature) { - confirmedTransaction = bs58.encode(tx.transaction.signature); + // Find a block that has a transaction, usually Block 1 + let slot = 0; + let confirmedTransaction: ?string; + while (!confirmedTransaction) { + slot++; + const block = await connection.getConfirmedBlock(slot); + for (const tx of block.transactions) { + if (tx.transaction.signature) { + confirmedTransaction = bs58.encode(tx.transaction.signature); + } } } - } - mockRpc.push([ - url, - { + await mockRpcResponse({ method: 'getConfirmedTransaction', params: [confirmedTransaction], - }, - { - error: null, - result: { + value: { slot, transaction: { message: { @@ -961,222 +775,177 @@ test('get confirmed transaction', async () => { err: null, }, }, - }, - ]); + }); - const result = await connection.getConfirmedTransaction(confirmedTransaction); + const result = await connection.getConfirmedTransaction( + confirmedTransaction, + ); - if (!result) { - expect(result).toBeDefined(); - expect(result).not.toBeNull(); - return; - } + if (!result) { + expect(result).to.be.ok; + return; + } - if (result.transaction.signature === null) { - expect(result.transaction.signature).not.toBeNull(); - return; - } + if (result.transaction.signature === null) { + expect(result.transaction.signature).not.to.be.null; + return; + } - const resultSignature = bs58.encode(result.transaction.signature); - expect(resultSignature).toEqual(confirmedTransaction); + const resultSignature = bs58.encode(result.transaction.signature); + expect(resultSignature).to.eq(confirmedTransaction); - const newAddress = new Account().publicKey; - mockRpc.push([ - url, - { - method: 'requestAirdrop', - params: [newAddress.toBase58(), 1], - }, - { - error: null, - result: - '1WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk', - }, - ]); + const newAddress = new Account().publicKey; + const recentSignature = await helpers.airdrop({ + connection, + address: newAddress, + amount: 1, + }); - const recentSignature = await connection.requestAirdrop(newAddress, 1); - mockRpc.push([ - url, - { + await mockRpcResponse({ method: 'getConfirmedTransaction', params: [recentSignature], - }, - { - error: null, - result: null, - }, - ]); + value: null, + }); - const nullResponse = await connection.getConfirmedTransaction( - recentSignature, - ); - expect(nullResponse).toBeNull(); -}); + // Signature hasn't been finalized yet + const nullResponse = await connection.getConfirmedTransaction( + recentSignature, + ); + expect(nullResponse).to.be.null; + }); -test('get parsed confirmed transaction coerces public keys of inner instructions', async () => { - if (!mockRpcEnabled) { - return; - } + if (!process.env.TEST_LIVE) { + it('get parsed confirmed transaction coerces public keys of inner instructions', async () => { + const confirmedTransaction: TransactionSignature = + '4ADvAUQYxkh4qWKYE9QLW8gCLomGG94QchDLG4quvpBz1WqARYvzWQDDitKduAKspuy1DjcbnaDAnCAfnKpJYs48'; - const connection = new Connection(url); - - const confirmedTransaction: TransactionSignature = - '4ADvAUQYxkh4qWKYE9QLW8gCLomGG94QchDLG4quvpBz1WqARYvzWQDDitKduAKspuy1DjcbnaDAnCAfnKpJYs48'; - - function getMockData(inner) { - return { - error: null, - result: { - slot: 353050305, - transaction: { - message: { - accountKeys: [ - { - pubkey: 'va12u4o9DipLEB2z4fuoHszroq1U9NcAB9aooFDPJSf', - signer: true, - writable: true, - }, - ], - instructions: [ - { - accounts: ['va12u4o9DipLEB2z4fuoHszroq1U9NcAB9aooFDPJSf'], - data: - '37u9WtQpcm6ULa3VtWDFAWoQc1hUvybPrA3dtx99tgHvvcE7pKRZjuGmn7VX2tC3JmYDYGG7', - programId: 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA', - }, - ], - recentBlockhash: 'GeyAFFRY3WGpmam2hbgrKw4rbU2RKzfVLm5QLSeZwTZE', - }, - signatures: [ - 'w2Zeq8YkpyB463DttvfzARD7k9ZxGEwbsEw4boEK7jDp3pfoxZbTdLFSsEPhzXhpCcjGi2kHtHFobgX49MMhbWt', - '4oCEqwGrMdBeMxpzuWiukCYqSfV4DsSKXSiVVCh1iJ6pS772X7y219JZP3mgqBz5PhsvprpKyhzChjYc3VSBQXzG', - ], - }, - meta: { - fee: 10000, - postBalances: [499260347380, 15298080, 1, 1, 1], - preBalances: [499260357380, 15298080, 1, 1, 1], - innerInstructions: [ - { - index: 0, - instructions: [inner], + function getMockData(inner) { + return { + slot: 353050305, + transaction: { + message: { + accountKeys: [ + { + pubkey: 'va12u4o9DipLEB2z4fuoHszroq1U9NcAB9aooFDPJSf', + signer: true, + writable: true, + }, + ], + instructions: [ + { + accounts: ['va12u4o9DipLEB2z4fuoHszroq1U9NcAB9aooFDPJSf'], + data: + '37u9WtQpcm6ULa3VtWDFAWoQc1hUvybPrA3dtx99tgHvvcE7pKRZjuGmn7VX2tC3JmYDYGG7', + programId: 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA', + }, + ], + recentBlockhash: 'GeyAFFRY3WGpmam2hbgrKw4rbU2RKzfVLm5QLSeZwTZE', }, + signatures: [ + 'w2Zeq8YkpyB463DttvfzARD7k9ZxGEwbsEw4boEK7jDp3pfoxZbTdLFSsEPhzXhpCcjGi2kHtHFobgX49MMhbWt', + '4oCEqwGrMdBeMxpzuWiukCYqSfV4DsSKXSiVVCh1iJ6pS772X7y219JZP3mgqBz5PhsvprpKyhzChjYc3VSBQXzG', + ], + }, + meta: { + fee: 10000, + postBalances: [499260347380, 15298080, 1, 1, 1], + preBalances: [499260357380, 15298080, 1, 1, 1], + innerInstructions: [ + { + index: 0, + instructions: [inner], + }, + ], + status: {Ok: null}, + err: null, + }, + }; + } + + await mockRpcResponse({ + method: 'getConfirmedTransaction', + params: [confirmedTransaction, 'jsonParsed'], + value: getMockData({ + parsed: {}, + program: 'spl-token', + programId: 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA', + }), + }); + + const result = await connection.getParsedConfirmedTransaction( + confirmedTransaction, + ); + + if ( + result !== null && + result.meta && + result.meta.innerInstructions !== undefined && + result.meta.innerInstructions.length > 0 + ) { + expect( + result.meta.innerInstructions[0].instructions[0].programId, + ).to.be.instanceOf(PublicKey); + } + + await mockRpcResponse({ + method: 'getConfirmedTransaction', + params: [confirmedTransaction, 'jsonParsed'], + value: getMockData({ + accounts: [ + 'EeJqWk5pczNjsqqY3jia9xfFNG1dD68te4s8gsdCuEk7', + '6tVrjJhFm5SAvvdh6tysjotQurCSELpxuW3JaAAYeC1m', ], - status: {Ok: null}, - err: null, - }, - }, - }; + data: 'ai3535', + programId: 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA', + }), + }); + + //$FlowFixMe + const result2 = await connection.getParsedConfirmedTransaction( + confirmedTransaction, + ); + + let instruction = result2.meta.innerInstructions[0].instructions[0]; + expect(instruction.programId).to.be.instanceOf(PublicKey); + expect(instruction.accounts[0]).to.be.instanceOf(PublicKey); + expect(instruction.accounts[1]).to.be.instanceOf(PublicKey); + }); } - mockRpc.push([ - url, - { - method: 'getConfirmedTransaction', - params: [confirmedTransaction, 'jsonParsed'], - }, - getMockData({ - parsed: {}, - program: 'spl-token', - programId: 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA', - }), - ]); - - const result = await connection.getParsedConfirmedTransaction( - confirmedTransaction, - ); - - if ( - result !== null && - result.meta && - result.meta.innerInstructions !== undefined && - result.meta.innerInstructions.length > 0 - ) { - expect( - result.meta.innerInstructions[0].instructions[0].programId, - ).toBeInstanceOf(PublicKey); - } - - mockRpc.push([ - url, - { - method: 'getConfirmedTransaction', - params: [confirmedTransaction, 'jsonParsed'], - }, - getMockData({ - accounts: [ - 'EeJqWk5pczNjsqqY3jia9xfFNG1dD68te4s8gsdCuEk7', - '6tVrjJhFm5SAvvdh6tysjotQurCSELpxuW3JaAAYeC1m', - ], - data: 'ai3535', - programId: 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA', - }), - ]); - - //$FlowFixMe - const result2 = await connection.getParsedConfirmedTransaction( - confirmedTransaction, - ); - - let instruction = result2.meta.innerInstructions[0].instructions[0]; - expect(instruction.programId).toBeInstanceOf(PublicKey); - expect(instruction.accounts[0]).toBeInstanceOf(PublicKey); - expect(instruction.accounts[1]).toBeInstanceOf(PublicKey); -}); - -test('get confirmed block', async () => { - const connection = new Connection(url); - - mockRpc.push([ - url, - { + it('get confirmed block', async () => { + await mockRpcResponse({ method: 'getSlot', params: [], - }, - { - error: null, - result: 1, - }, - ]); + value: 1, + }); - while ((await connection.getSlot()) <= 0) { - continue; - } + while ((await connection.getSlot()) <= 0) { + continue; + } - mockRpc.push([ - url, - { + await mockRpcResponse({ method: 'getConfirmedBlock', params: [0], - }, - { - error: null, - result: { + value: { blockhash: 'H5nJ91eGag3B5ZSRHZ7zG5ZwXJ6ywCt2hyR8xCsV7xMo', previousBlockhash: 'H5nJ91eGag3B5ZSRHZ7zG5ZwXJ6ywCt2hyR8xCsV7xMo', parentSlot: 0, transactions: [], }, - }, - ]); + }); - // Block 0 never has any transactions in automation localnet - const block0 = await connection.getConfirmedBlock(0); - const blockhash0 = block0.blockhash; - expect(block0.transactions.length).toBe(0); - expect(blockhash0).not.toBeNull(); - expect(block0.previousBlockhash).not.toBeNull(); - expect(block0.parentSlot).toBe(0); + // Block 0 never has any transactions in automation localnet + const block0 = await connection.getConfirmedBlock(0); + const blockhash0 = block0.blockhash; + expect(block0.transactions).to.have.length(0); + expect(blockhash0).not.to.be.null; + expect(block0.previousBlockhash).not.to.be.null; + expect(block0.parentSlot).to.eq(0); - mockRpc.push([ - url, - { + await mockRpcResponse({ method: 'getConfirmedBlock', params: [1], - }, - { - error: null, - result: { + value: { blockhash: '57zQNBZBEiHsCZFqsaY6h176ioXy5MsSLmcvHkEyaLGy', previousBlockhash: 'H5nJ91eGag3B5ZSRHZ7zG5ZwXJ6ywCt2hyR8xCsV7xMo', parentSlot: 0, @@ -1221,1505 +990,1020 @@ test('get confirmed block', async () => { }, ], }, - }, - ]); + }); - // Find a block that has a transaction, usually Block 1 - let x = 1; - while (x < 10) { - const block1 = await connection.getConfirmedBlock(x); - if (block1.transactions.length >= 1) { - expect(block1.previousBlockhash).toBe(blockhash0); - expect(block1.blockhash).not.toBeNull(); - expect(block1.parentSlot).toBe(0); - expect(block1.transactions[0].transaction).not.toBeNull(); - break; + // Find a block that has a transaction, usually Block 1 + let x = 1; + while (x < 10) { + const block1 = await connection.getConfirmedBlock(x); + if (block1.transactions.length >= 1) { + expect(block1.previousBlockhash).to.eq(blockhash0); + expect(block1.blockhash).not.to.be.null; + expect(block1.parentSlot).to.eq(0); + expect(block1.transactions[0].transaction).not.to.be.null; + break; + } + x++; } - x++; - } - mockRpc.push([ - url, - { + await mockRpcResponse({ method: 'getConfirmedBlock', params: [Number.MAX_SAFE_INTEGER], - }, - { error: { message: `Block not available for slot ${Number.MAX_SAFE_INTEGER}`, }, - result: null, - }, - ]); - await expect( - connection.getConfirmedBlock(Number.MAX_SAFE_INTEGER), - ).rejects.toThrow(`Block not available for slot ${Number.MAX_SAFE_INTEGER}`); -}); - -test('get recent blockhash', async () => { - const connection = new Connection(url); - for (const commitment of [ - 'max', - 'singleGossip', - 'root', - 'single', - 'singleGossip', - ]) { - mockGetRecentBlockhash(commitment); - - const {blockhash, feeCalculator} = await connection.getRecentBlockhash( - commitment, + }); + await expect( + connection.getConfirmedBlock(Number.MAX_SAFE_INTEGER), + ).to.be.rejectedWith( + `Block not available for slot ${Number.MAX_SAFE_INTEGER}`, ); - expect(blockhash.length).toBeGreaterThanOrEqual(43); - expect(feeCalculator.lamportsPerSignature).toBeGreaterThanOrEqual(0); - } -}); + }); -test('get fee calculator', async () => { - const connection = new Connection(url); + it('get recent blockhash', async () => { + for (const commitment of [ + 'max', + 'singleGossip', + 'root', + 'single', + 'singleGossip', + ]) { + const {blockhash, feeCalculator} = await helpers.recentBlockhash({ + connection, + commitment, + }); + expect(bs58.decode(blockhash)).to.have.length(32); + expect(feeCalculator.lamportsPerSignature).to.be.at.least(0); + } + }); - mockGetRecentBlockhash('singleGossip'); - const {blockhash} = await connection.getRecentBlockhash('singleGossip'); - - mockRpc.push([ - url, - { + it('get fee calculator', async () => { + const {blockhash} = await helpers.recentBlockhash({connection}); + await mockRpcResponse({ method: 'getFeeCalculatorForBlockhash', params: [blockhash, {commitment: 'singleGossip'}], - }, - { - error: null, - result: { - context: { - slot: 11, - }, - value: { - feeCalculator: { - lamportsPerSignature: 5000, - }, + value: { + feeCalculator: { + lamportsPerSignature: 5000, }, }, - }, - ]); + withContext: true, + }); - const feeCalculator = ( - await connection.getFeeCalculatorForBlockhash(blockhash, 'singleGossip') - ).value; - if (feeCalculator === null) { - expect(feeCalculator).not.toBeNull(); - return; - } - expect(feeCalculator.lamportsPerSignature).toEqual(5000); -}); - -test('get block time', async () => { - const connection = new Connection(url); - - mockRpc.push([ - url, - { - method: 'getBlockTime', - params: [1], - }, - { - error: null, - result: 10000, - }, - ]); - - const blockTime = await connection.getBlockTime(1); - if (blockTime === null) { - // TODO: enable after https://github.com/solana-labs/solana/issues/11849 fixed - // expect(blockTime).not.toBeNull(); - } else { - expect(blockTime).toBeGreaterThan(0); - } -}); - -test('get minimum ledger slot', async () => { - const connection = new Connection(url); - - mockRpc.push([ - url, - { - method: 'minimumLedgerSlot', - params: [], - }, - { - error: null, - result: 0, - }, - ]); - - const minimumLedgerSlot = await connection.getMinimumLedgerSlot(); - expect(minimumLedgerSlot).toBeGreaterThanOrEqual(0); -}); - -test('get first available block', async () => { - const connection = new Connection(url); - - mockRpc.push([ - url, - { - method: 'getFirstAvailableBlock', - params: [], - }, - { - error: null, - result: 0, - }, - ]); - - const firstAvailableBlock = await connection.getFirstAvailableBlock(); - expect(firstAvailableBlock).toBeGreaterThanOrEqual(0); -}); - -test('get supply', async () => { - const connection = new Connection(url); - - mockRpc.push([ - url, - { - method: 'getSupply', - params: [], - }, - { - error: null, - result: { - context: { - slot: 1, - }, - value: { - total: 1000, - circulating: 100, - nonCirculating: 900, - nonCirculatingAccounts: [new Account().publicKey.toBase58()], - }, - }, - }, - ]); - - const supply = (await connection.getSupply()).value; - expect(supply.total).toBeGreaterThan(0); - expect(supply.circulating).toBeGreaterThan(0); - expect(supply.nonCirculating).toBeGreaterThanOrEqual(0); - expect(supply.nonCirculatingAccounts.length).toBeGreaterThanOrEqual(0); -}); - -test('get performance samples', async () => { - const connection = new Connection(url); - - if (mockRpcEnabled) { - mockRpc.push([ - url, - { - method: 'getRecentPerformanceSamples', - params: [], - }, - { - error: null, - result: [ - { - slot: 1234, - numTransactions: 1000, - numSlots: 60, - samplePeriodSecs: 60, - }, - ], - }, - ]); - } - - const perfSamples = await connection.getRecentPerformanceSamples(); - expect(Array.isArray(perfSamples)).toBe(true); - - if (perfSamples.length > 0) { - expect(perfSamples[0].slot).toBeGreaterThan(0); - expect(perfSamples[0].numTransactions).toBeGreaterThan(0); - expect(perfSamples[0].numSlots).toBeGreaterThan(0); - expect(perfSamples[0].samplePeriodSecs).toBeGreaterThan(0); - } -}); - -test('get performance samples limit too high', async () => { - const connection = new Connection(url); - - if (mockRpcEnabled) { - mockRpc.push([ - url, - { - method: 'getRecentPerformanceSamples', - params: [100000], - }, - { - error: { - code: -32602, - message: 'Invalid limit; max 720', - }, - result: null, - }, - ]); - } - - await expect( - connection.getRecentPerformanceSamples(100000), - ).rejects.toThrow(); -}); - -const TOKEN_PROGRAM_ID = new PublicKey( - 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA', -); - -describe('token methods', () => { - if (mockRpcEnabled) { - console.log('non-live test skipped'); - return; - } - - const connection = new Connection(url, 'singleGossip'); - const newAccount = new Account().publicKey; - - let testToken: Token; - let testTokenAccount: PublicKey; - let testSignature: TransactionSignature; - let testOwner: Account; - - // Setup token mints and accounts for token tests - beforeAll(async () => { - const payerAccount = new Account(); - await connection.confirmTransaction( - await connection.requestAirdrop(payerAccount.publicKey, 100000000000), - ); - - const mintOwner = new Account(); - const accountOwner = new Account(); - const token = await Token.createMint( - connection, - payerAccount, - mintOwner.publicKey, - null, - 2, - TOKEN_PROGRAM_ID, - ); - - const tokenAccount = await token.createAccount(accountOwner.publicKey); - await token.mintTo(tokenAccount, mintOwner, [], 11111); - - const token2 = await Token.createMint( - connection, - payerAccount, - mintOwner.publicKey, - null, - 2, - TOKEN_PROGRAM_ID, - ); - - const token2Account = await token2.createAccount(accountOwner.publicKey); - await token2.mintTo(token2Account, mintOwner, [], 100); - - const tokenAccountDest = await token.createAccount(accountOwner.publicKey); - testSignature = await token.transfer( - tokenAccount, - tokenAccountDest, - accountOwner, - [], - new u64(1), - ); - - await connection.confirmTransaction(testSignature, 'max'); - - testOwner = accountOwner; - testToken = token; - testTokenAccount = tokenAccount; - }); - - test('get token supply', async () => { - const supply = (await connection.getTokenSupply(testToken.publicKey)).value; - expect(supply.uiAmount).toEqual(111.11); - expect(supply.decimals).toEqual(2); - expect(supply.amount).toEqual('11111'); - - await expect(connection.getTokenSupply(newAccount)).rejects.toThrow(); - }); - - test('get token largest accounts', async () => { - const largestAccounts = ( - await connection.getTokenLargestAccounts(testToken.publicKey) + const feeCalculator = ( + await connection.getFeeCalculatorForBlockhash(blockhash, 'singleGossip') ).value; - - expect(largestAccounts.length).toEqual(2); - const largestAccount = largestAccounts[0]; - expect(largestAccount.address.equals(testTokenAccount)).toBe(true); - expect(largestAccount.amount).toEqual('11110'); - expect(largestAccount.decimals).toEqual(2); - expect(largestAccount.uiAmount).toEqual(111.1); - - await expect( - connection.getTokenLargestAccounts(newAccount), - ).rejects.toThrow(); - }); - - test('get confirmed token transaction', async () => { - const parsedTx = await connection.getParsedConfirmedTransaction( - testSignature, - ); - if (parsedTx === null) { - expect(parsedTx).not.toBeNull(); + if (feeCalculator === null) { + expect(feeCalculator).not.to.be.null; return; } - const {signatures, message} = parsedTx.transaction; - expect(signatures[0]).toEqual(testSignature); - const ix = message.instructions[0]; - if (ix.parsed) { - expect(ix.program).toEqual('spl-token'); - expect(ix.programId.equals(TOKEN_PROGRAM_ID)).toBe(true); + expect(feeCalculator.lamportsPerSignature).to.eq(5000); + }); + + it('get block time', async () => { + await mockRpcResponse({ + method: 'getBlockTime', + params: [1], + value: 10000, + }); + + const blockTime = await connection.getBlockTime(1); + if (blockTime === null) { + expect(blockTime).not.to.be.null; } else { - expect('parsed' in ix).toBe(true); - } - - const missingSignature = - '45pGoC4Rr3fJ1TKrsiRkhHRbdUeX7633XAGVec6XzVdpRbzQgHhe6ZC6Uq164MPWtiqMg7wCkC6Wy3jy2BqsDEKf'; - const nullResponse = await connection.getParsedConfirmedTransaction( - missingSignature, - ); - - expect(nullResponse).toBeNull(); - }); - - test('get token account balance', async () => { - const balance = (await connection.getTokenAccountBalance(testTokenAccount)) - .value; - expect(balance.amount).toEqual('11110'); - expect(balance.decimals).toEqual(2); - expect(balance.uiAmount).toEqual(111.1); - - await expect( - connection.getTokenAccountBalance(newAccount), - ).rejects.toThrow(); - }); - - test('get parsed token account info', async () => { - const accountInfo = ( - await connection.getParsedAccountInfo(testTokenAccount) - ).value; - if (accountInfo) { - const data = accountInfo.data; - if (data instanceof Buffer) { - expect(data instanceof Buffer).toBe(false); - } else { - expect(data.program).toEqual('spl-token'); - expect(data.parsed).toBeTruthy(); - } + expect(blockTime).to.be.greaterThan(0); } }); - test('get parsed token program accounts', async () => { - const tokenAccounts = await connection.getParsedProgramAccounts( - TOKEN_PROGRAM_ID, - ); - tokenAccounts.forEach(({account}) => { - expect(account.owner.equals(TOKEN_PROGRAM_ID)).toBe(true); - const data = account.data; - if (data instanceof Buffer) { - expect(data instanceof Buffer).toBe(false); - } else { - expect(data.parsed).toBeTruthy(); - expect(data.program).toEqual('spl-token'); - } + it('get minimum ledger slot', async () => { + await mockRpcResponse({ + method: 'minimumLedgerSlot', + params: [], + value: 0, }); + + const minimumLedgerSlot = await connection.getMinimumLedgerSlot(); + expect(minimumLedgerSlot).to.be.at.least(0); }); - test('get parsed token accounts by owner', async () => { - const tokenAccounts = ( - await connection.getParsedTokenAccountsByOwner(testOwner.publicKey, { - mint: testToken.publicKey, - }) - ).value; - tokenAccounts.forEach(({account}) => { - expect(account.owner.equals(TOKEN_PROGRAM_ID)).toBe(true); - const data = account.data; - if (data instanceof Buffer) { - expect(data instanceof Buffer).toBe(false); - } else { - expect(data.parsed).toBeTruthy(); - expect(data.program).toEqual('spl-token'); - } + it('get first available block', async () => { + await mockRpcResponse({ + method: 'getFirstAvailableBlock', + params: [], + value: 0, }); + + const firstAvailableBlock = await connection.getFirstAvailableBlock(); + expect(firstAvailableBlock).to.be.at.least(0); }); - test('get token accounts by owner', async () => { - const accountsWithMintFilter = ( - await connection.getTokenAccountsByOwner(testOwner.publicKey, { - mint: testToken.publicKey, - }) - ).value; - expect(accountsWithMintFilter.length).toEqual(2); + it('get supply', async () => { + await mockRpcResponse({ + method: 'getSupply', + params: [], + value: { + total: 1000, + circulating: 100, + nonCirculating: 900, + nonCirculatingAccounts: [new Account().publicKey.toBase58()], + }, + withContext: true, + }); - const accountsWithProgramFilter = ( - await connection.getTokenAccountsByOwner(testOwner.publicKey, { - programId: TOKEN_PROGRAM_ID, - }) - ).value; - expect(accountsWithProgramFilter.length).toEqual(3); - - const noAccounts = ( - await connection.getTokenAccountsByOwner(newAccount, { - mint: testToken.publicKey, - }) - ).value; - expect(noAccounts.length).toEqual(0); - - await expect( - connection.getTokenAccountsByOwner(testOwner.publicKey, { - mint: newAccount, - }), - ).rejects.toThrow(); - - await expect( - connection.getTokenAccountsByOwner(testOwner.publicKey, { - programId: newAccount, - }), - ).rejects.toThrow(); + const supply = (await connection.getSupply()).value; + expect(supply.total).to.be.greaterThan(0); + expect(supply.circulating).to.be.greaterThan(0); + expect(supply.nonCirculating).to.be.at.least(0); + expect(supply.nonCirculatingAccounts.length).to.be.at.least(0); }); -}); -test('get largest accounts', async () => { - const connection = new Connection(url); + it('get performance samples', async () => { + await mockRpcResponse({ + method: 'getRecentPerformanceSamples', + params: [], + value: [ + { + slot: 1234, + numTransactions: 1000, + numSlots: 60, + samplePeriodSecs: 60, + }, + ], + }); - mockRpc.push([ - url, - { + const perfSamples = await connection.getRecentPerformanceSamples(); + expect(Array.isArray(perfSamples)).to.be.true; + + if (perfSamples.length > 0) { + expect(perfSamples[0].slot).to.be.greaterThan(0); + expect(perfSamples[0].numTransactions).to.be.greaterThan(0); + expect(perfSamples[0].numSlots).to.be.greaterThan(0); + expect(perfSamples[0].samplePeriodSecs).to.be.greaterThan(0); + } + }); + + it('get performance samples limit too high', async () => { + await mockRpcResponse({ + method: 'getRecentPerformanceSamples', + params: [100000], + error: mockErrorResponse, + }); + + await expect(connection.getRecentPerformanceSamples(100000)).to.be.rejected; + }); + + const TOKEN_PROGRAM_ID = new PublicKey( + 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA', + ); + + if (process.env.TEST_LIVE) { + describe('token methods', () => { + const connection = new Connection(url, 'singleGossip'); + const newAccount = new Account().publicKey; + + let testToken: Token; + let testTokenAccount: PublicKey; + let testSignature: TransactionSignature; + let testOwner: Account; + + // Setup token mints and accounts for token tests + before(async function () { + this.timeout(30 * 1000); + + const payerAccount = new Account(); + await connection.confirmTransaction( + await connection.requestAirdrop(payerAccount.publicKey, 100000000000), + ); + + const mintOwner = new Account(); + const accountOwner = new Account(); + const token = await Token.createMint( + connection, + payerAccount, + mintOwner.publicKey, + null, + 2, + TOKEN_PROGRAM_ID, + ); + + const tokenAccount = await token.createAccount(accountOwner.publicKey); + await token.mintTo(tokenAccount, mintOwner, [], 11111); + + const token2 = await Token.createMint( + connection, + payerAccount, + mintOwner.publicKey, + null, + 2, + TOKEN_PROGRAM_ID, + ); + + const token2Account = await token2.createAccount( + accountOwner.publicKey, + ); + await token2.mintTo(token2Account, mintOwner, [], 100); + + const tokenAccountDest = await token.createAccount( + accountOwner.publicKey, + ); + testSignature = await token.transfer( + tokenAccount, + tokenAccountDest, + accountOwner, + [], + new u64(1), + ); + + await connection.confirmTransaction(testSignature, 'max'); + + testOwner = accountOwner; + testToken = token; + testTokenAccount = tokenAccount; + }); + + it('get token supply', async () => { + const supply = (await connection.getTokenSupply(testToken.publicKey)) + .value; + expect(supply.uiAmount).to.eq(111.11); + expect(supply.decimals).to.eq(2); + expect(supply.amount).to.eq('11111'); + + await expect(connection.getTokenSupply(newAccount)).to.be.rejected; + }); + + it('get token largest accounts', async () => { + const largestAccounts = ( + await connection.getTokenLargestAccounts(testToken.publicKey) + ).value; + + expect(largestAccounts).to.have.length(2); + const largestAccount = largestAccounts[0]; + expect(largestAccount.address).to.eql(testTokenAccount); + expect(largestAccount.amount).to.eq('11110'); + expect(largestAccount.decimals).to.eq(2); + expect(largestAccount.uiAmount).to.eq(111.1); + + await expect(connection.getTokenLargestAccounts(newAccount)).to.be + .rejected; + }); + + it('get confirmed token transaction', async () => { + const parsedTx = await connection.getParsedConfirmedTransaction( + testSignature, + ); + if (parsedTx === null) { + expect(parsedTx).not.to.be.null; + return; + } + const {signatures, message} = parsedTx.transaction; + expect(signatures[0]).to.eq(testSignature); + const ix = message.instructions[0]; + if (ix.parsed) { + expect(ix.program).to.eq('spl-token'); + expect(ix.programId).to.eql(TOKEN_PROGRAM_ID); + } else { + expect('parsed' in ix).to.be.true; + } + + const missingSignature = + '45pGoC4Rr3fJ1TKrsiRkhHRbdUeX7633XAGVec6XzVdpRbzQgHhe6ZC6Uq164MPWtiqMg7wCkC6Wy3jy2BqsDEKf'; + const nullResponse = await connection.getParsedConfirmedTransaction( + missingSignature, + ); + + expect(nullResponse).to.be.null; + }); + + it('get token account balance', async () => { + const balance = ( + await connection.getTokenAccountBalance(testTokenAccount) + ).value; + expect(balance.amount).to.eq('11110'); + expect(balance.decimals).to.eq(2); + expect(balance.uiAmount).to.eq(111.1); + + await expect(connection.getTokenAccountBalance(newAccount)).to.be + .rejected; + }); + + it('get parsed token account info', async () => { + const accountInfo = ( + await connection.getParsedAccountInfo(testTokenAccount) + ).value; + if (accountInfo) { + const data = accountInfo.data; + if (data instanceof Buffer) { + expect(data instanceof Buffer).to.eq(false); + } else { + expect(data.program).to.eq('spl-token'); + expect(data.parsed).to.be.ok; + } + } + }); + + it('get parsed token program accounts', async () => { + const tokenAccounts = await connection.getParsedProgramAccounts( + TOKEN_PROGRAM_ID, + ); + tokenAccounts.forEach(({account}) => { + expect(account.owner).to.eql(TOKEN_PROGRAM_ID); + const data = account.data; + if (data instanceof Buffer) { + expect(data instanceof Buffer).to.eq(false); + } else { + expect(data.parsed).to.be.ok; + expect(data.program).to.eq('spl-token'); + } + }); + }); + + it('get parsed token accounts by owner', async () => { + const tokenAccounts = ( + await connection.getParsedTokenAccountsByOwner(testOwner.publicKey, { + mint: testToken.publicKey, + }) + ).value; + tokenAccounts.forEach(({account}) => { + expect(account.owner).to.eql(TOKEN_PROGRAM_ID); + const data = account.data; + if (data instanceof Buffer) { + expect(data instanceof Buffer).to.eq(false); + } else { + expect(data.parsed).to.be.ok; + expect(data.program).to.eq('spl-token'); + } + }); + }); + + it('get token accounts by owner', async () => { + const accountsWithMintFilter = ( + await connection.getTokenAccountsByOwner(testOwner.publicKey, { + mint: testToken.publicKey, + }) + ).value; + expect(accountsWithMintFilter).to.have.length(2); + + const accountsWithProgramFilter = ( + await connection.getTokenAccountsByOwner(testOwner.publicKey, { + programId: TOKEN_PROGRAM_ID, + }) + ).value; + expect(accountsWithProgramFilter).to.have.length(3); + + const noAccounts = ( + await connection.getTokenAccountsByOwner(newAccount, { + mint: testToken.publicKey, + }) + ).value; + expect(noAccounts).to.have.length(0); + + await expect( + connection.getTokenAccountsByOwner(testOwner.publicKey, { + mint: newAccount, + }), + ).to.be.rejected; + + await expect( + connection.getTokenAccountsByOwner(testOwner.publicKey, { + programId: newAccount, + }), + ).to.be.rejected; + }); + }); + } + + it('get largest accounts', async () => { + await mockRpcResponse({ method: 'getLargestAccounts', params: [], - }, - { - error: null, - result: { - context: { - slot: 1, - }, - value: new Array(20).fill(0).map(() => ({ - address: new Account().publicKey.toBase58(), - lamports: 1000, - })), - }, - }, - ]); + value: new Array(20).fill(0).map(() => ({ + address: new Account().publicKey.toBase58(), + lamports: 1000, + })), + withContext: true, + }); - const largestAccounts = (await connection.getLargestAccounts()).value; - expect(largestAccounts.length).toEqual(20); -}); + const largestAccounts = (await connection.getLargestAccounts()).value; + expect(largestAccounts).to.have.length(20); + }); -test('stake activation should throw when called for not delegated account', async () => { - const connection = new Connection(url); - - const publicKey = new Account().publicKey; - mockRpc.push([ - url, - { + it('stake activation should throw when called for not delegated account', async () => { + const publicKey = new Account().publicKey; + await mockRpcResponse({ method: 'getStakeActivation', params: [publicKey.toBase58(), {}], - }, - { error: {message: 'account not delegated'}, - result: undefined, - }, - ]); + }); - await expect(connection.getStakeActivation(publicKey)).rejects.toThrow(); -}); - -test('stake activation should return activating for new accounts', async () => { - if (mockRpcEnabled) { - console.log('non-live test skipped'); - return; - } - - const connection = new Connection(url, 'singleGossip'); - const voteAccounts = await connection.getVoteAccounts(); - const voteAccount = voteAccounts.current.concat(voteAccounts.delinquent)[0]; - const votePubkey = new PublicKey(voteAccount.votePubkey); - - const authorized = new Account(); - let signature = await connection.requestAirdrop( - authorized.publicKey, - 2 * LAMPORTS_PER_SOL, - ); - await connection.confirmTransaction(signature); - - const minimumAmount = await connection.getMinimumBalanceForRentExemption( - StakeProgram.space, - ); - - const newStakeAccount = new Account(); - let createAndInitialize = StakeProgram.createAccount({ - fromPubkey: authorized.publicKey, - stakePubkey: newStakeAccount.publicKey, - authorized: new Authorized(authorized.publicKey, authorized.publicKey), - lockup: new Lockup(0, 0, new PublicKey(0)), - lamports: minimumAmount + 42, + await expect(connection.getStakeActivation(publicKey)).to.be.rejected; }); - await sendAndConfirmTransaction( - connection, - createAndInitialize, - [authorized, newStakeAccount], - { - commitment: 'singleGossip', - }, - ); - let delegation = StakeProgram.delegate({ - stakePubkey: newStakeAccount.publicKey, - authorizedPubkey: authorized.publicKey, - votePubkey, - }); - await sendAndConfirmTransaction(connection, delegation, [authorized], { - commitment: 'singleGossip', - }); + if (process.env.TEST_LIVE) { + it('stake activation should return activating for new accounts', async () => { + const voteAccounts = await connection.getVoteAccounts(); + const voteAccount = voteAccounts.current.concat( + voteAccounts.delinquent, + )[0]; + const votePubkey = new PublicKey(voteAccount.votePubkey); - const LARGE_EPOCH = 4000; - await expect( - connection.getStakeActivation( - newStakeAccount.publicKey, - 'singleGossip', - LARGE_EPOCH, - ), - ).rejects.toThrow( - `failed to get Stake Activation ${newStakeAccount.publicKey.toBase58()}: Invalid param: epoch ${LARGE_EPOCH} has not yet started`, - ); + const authorized = new Account(); + let signature = await connection.requestAirdrop( + authorized.publicKey, + 2 * LAMPORTS_PER_SOL, + ); + await connection.confirmTransaction(signature, 'singleGossip'); - const activationState = await connection.getStakeActivation( - newStakeAccount.publicKey, - ); - expect(activationState.state).toBe('activating'); - expect(activationState.inactive).toBe(42); - expect(activationState.active).toBe(0); -}); + const minimumAmount = await connection.getMinimumBalanceForRentExemption( + StakeProgram.space, + ); -test('stake activation should only accept state with valid string literals', async () => { - if (!mockRpcEnabled) { - console.log('live test skipped'); - return; - } + const newStakeAccount = new Account(); + let createAndInitialize = StakeProgram.createAccount({ + fromPubkey: authorized.publicKey, + stakePubkey: newStakeAccount.publicKey, + authorized: new Authorized(authorized.publicKey, authorized.publicKey), + lockup: new Lockup(0, 0, new PublicKey(0)), + lamports: minimumAmount + 42, + }); - const connection = new Connection(url, 'singleGossip'); - const publicKey = new Account().publicKey; - - const addStakeActivationMock = state => { - mockRpc.push([ - url, - { - method: 'getStakeActivation', - params: [publicKey.toBase58(), {}], - }, - { - error: undefined, - result: { - state: state, - active: 0, - inactive: 80, + await sendAndConfirmTransaction( + connection, + createAndInitialize, + [authorized, newStakeAccount], + { + preflightCommitment: 'singleGossip', + commitment: 'singleGossip', }, - }, - ]); - }; + ); + let delegation = StakeProgram.delegate({ + stakePubkey: newStakeAccount.publicKey, + authorizedPubkey: authorized.publicKey, + votePubkey, + }); + await sendAndConfirmTransaction(connection, delegation, [authorized], { + preflightCommitment: 'singleGossip', + commitment: 'singleGossip', + }); - addStakeActivationMock('active'); - let activation = await connection.getStakeActivation(publicKey); - expect(activation.state).toBe('active'); - expect(activation.active).toBe(0); - expect(activation.inactive).toBe(80); + const LARGE_EPOCH = 4000; + await expect( + connection.getStakeActivation( + newStakeAccount.publicKey, + 'singleGossip', + LARGE_EPOCH, + ), + ).to.be.rejectedWith( + `failed to get Stake Activation ${newStakeAccount.publicKey.toBase58()}: Invalid param: epoch ${LARGE_EPOCH} has not yet started`, + ); - addStakeActivationMock('invalid'); - await expect(connection.getStakeActivation(publicKey)).rejects.toThrow(); -}); + const activationState = await connection.getStakeActivation( + newStakeAccount.publicKey, + 'singleGossip', + ); + expect(activationState.state).to.eq('activating'); + expect(activationState.inactive).to.eq(42); + expect(activationState.active).to.eq(0); + }); + } -test('getVersion', async () => { - const connection = new Connection(url); + if (!process.env.TEST_LIVE) { + it('stake activation should only accept state with valid string literals', async () => { + const publicKey = new Account().publicKey; - mockRpc.push([ - url, - { + const addStakeActivationMock = async state => { + await mockRpcResponse({ + method: 'getStakeActivation', + params: [publicKey.toBase58(), {}], + value: { + state: state, + active: 0, + inactive: 80, + }, + }); + }; + + await addStakeActivationMock('active'); + let activation = await connection.getStakeActivation( + publicKey, + 'singleGossip', + ); + expect(activation.state).to.eq('active'); + expect(activation.active).to.eq(0); + expect(activation.inactive).to.eq(80); + + await addStakeActivationMock('invalid'); + await expect(connection.getStakeActivation(publicKey, 'singleGossip')).to + .be.rejected; + }); + } + + it('getVersion', async () => { + await mockRpcResponse({ method: 'getVersion', params: [], - }, - { - error: null, - result: {'solana-core': '0.20.4'}, - }, - ]); + value: {'solana-core': '0.20.4'}, + }); - const version = await connection.getVersion(); - expect(version['solana-core']).toBeTruthy(); -}); + const version = await connection.getVersion(); + expect(version['solana-core']).to.be.ok; + }); -test('request airdrop', async () => { - const account = new Account(); - const connection = new Connection(url, 'singleGossip'); + it('request airdrop', async () => { + const account = new Account(); - mockRpc.push([ - url, - { - method: 'getMinimumBalanceForRentExemption', - params: [0, {commitment: 'singleGossip'}], - }, - { - error: null, - result: 50, - }, - ]); + await helpers.airdrop({ + connection, + address: account.publicKey, + amount: LAMPORTS_PER_SOL, + }); - const minimumAmount = await connection.getMinimumBalanceForRentExemption( - 0, - 'singleGossip', - ); - - mockRpc.push([ - url, - { - method: 'requestAirdrop', - params: [account.publicKey.toBase58(), minimumAmount + 42], - }, - { - error: null, - result: - '1WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk', - }, - ]); - - const signature = await connection.requestAirdrop( - account.publicKey, - minimumAmount + 42, - ); - - mockConfirmTransaction(signature); - await connection.confirmTransaction(signature); - - mockRpc.push([ - url, - { + await mockRpcResponse({ method: 'getBalance', params: [account.publicKey.toBase58(), {commitment: 'singleGossip'}], - }, - { - error: null, - result: { - context: { - slot: 11, - }, - value: minimumAmount + 42, - }, - }, - ]); + value: LAMPORTS_PER_SOL, + withContext: true, + }); - const balance = await connection.getBalance(account.publicKey); - expect(balance).toBe(minimumAmount + 42); + const balance = await connection.getBalance( + account.publicKey, + 'singleGossip', + ); + expect(balance).to.eq(LAMPORTS_PER_SOL); - mockRpc.push([ - url, - { + await mockRpcResponse({ method: 'getAccountInfo', params: [ account.publicKey.toBase58(), {commitment: 'singleGossip', encoding: 'base64'}, ], - }, - { - error: null, - result: { - context: { - slot: 11, - }, - value: { - owner: '11111111111111111111111111111111', - lamports: minimumAmount + 42, - data: ['', 'base64'], - executable: false, - }, + value: { + owner: '11111111111111111111111111111111', + lamports: LAMPORTS_PER_SOL, + data: ['', 'base64'], + executable: false, }, - }, - ]); + withContext: true, + }); - const accountInfo = await connection.getAccountInfo(account.publicKey); - if (accountInfo === null) { - expect(accountInfo).not.toBeNull(); - return; - } - expect(accountInfo.lamports).toBe(minimumAmount + 42); - expect(accountInfo.data).toHaveLength(0); - expect(accountInfo.owner).toEqual(SystemProgram.programId); + const accountInfo = await connection.getAccountInfo( + account.publicKey, + 'singleGossip', + ); + if (accountInfo === null) { + expect(accountInfo).not.to.be.null; + return; + } + expect(accountInfo.lamports).to.eq(LAMPORTS_PER_SOL); + expect(accountInfo.data).to.have.length(0); + expect(accountInfo.owner).to.eql(SystemProgram.programId); - mockRpc.push([ - url, - { + await mockRpcResponse({ method: 'getAccountInfo', params: [ account.publicKey.toBase58(), {commitment: 'singleGossip', encoding: 'jsonParsed'}, ], - }, - { - error: null, - result: { - context: { - slot: 11, - }, - value: { - owner: '11111111111111111111111111111111', - lamports: minimumAmount + 42, - data: ['', 'base64'], - executable: false, - }, + value: { + owner: '11111111111111111111111111111111', + lamports: LAMPORTS_PER_SOL, + data: ['', 'base64'], + executable: false, }, - }, - ]); + withContext: true, + }); - const parsedAccountInfo = ( - await connection.getParsedAccountInfo(account.publicKey) - ).value; - if (parsedAccountInfo === null) { - expect(parsedAccountInfo).not.toBeNull(); - return; - } else if (parsedAccountInfo.data.parsed) { - expect(parsedAccountInfo.data.parsed).not.toBeTruthy(); - return; - } - expect(parsedAccountInfo.lamports).toBe(minimumAmount + 42); - expect(parsedAccountInfo.data).toHaveLength(0); - expect(parsedAccountInfo.owner).toEqual(SystemProgram.programId); -}); + const parsedAccountInfo = ( + await connection.getParsedAccountInfo(account.publicKey, 'singleGossip') + ).value; + if (parsedAccountInfo === null) { + expect(parsedAccountInfo).not.to.be.null; + return; + } else if (parsedAccountInfo.data.parsed) { + expect(parsedAccountInfo.data.parsed).not.to.be.ok; + return; + } + expect(parsedAccountInfo.lamports).to.eq(LAMPORTS_PER_SOL); + expect(parsedAccountInfo.data).to.have.length(0); + expect(parsedAccountInfo.owner).to.eql(SystemProgram.programId); + }); -test('transaction failure', async () => { - const account = new Account(); - const connection = new Connection(url, 'singleGossip'); + it('transaction failure', async () => { + const payer = new Account(); - mockRpc.push([ - url, - { - method: 'getMinimumBalanceForRentExemption', - params: [0, {commitment: 'singleGossip'}], - }, - { - error: null, - result: 50, - }, - ]); + await helpers.airdrop({ + connection, + address: payer.publicKey, + amount: LAMPORTS_PER_SOL, + }); - const minimumAmount = await connection.getMinimumBalanceForRentExemption( - 0, - 'singleGossip', - ); + const newAccount = new Account(); + let transaction = new Transaction().add( + SystemProgram.createAccount({ + fromPubkey: payer.publicKey, + newAccountPubkey: newAccount.publicKey, + lamports: LAMPORTS_PER_SOL / 2, + space: 0, + programId: SystemProgram.programId, + }), + ); - mockRpc.push([ - url, - { - method: 'requestAirdrop', - params: [account.publicKey.toBase58(), 3 * minimumAmount], - }, - { - error: null, - result: - '2WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk', - }, - ]); - const airdropSignature = await connection.requestAirdrop( - account.publicKey, - 3 * minimumAmount, - ); + await helpers.processTransaction({ + connection, + transaction, + signers: [payer, newAccount], + commitment: 'singleGossip', + }); - mockConfirmTransaction(airdropSignature); - await connection.confirmTransaction(airdropSignature); + // This should fail because the account is already created + const expectedErr = {InstructionError: [0, {Custom: 0}]}; + const confirmResult = ( + await helpers.processTransaction({ + connection, + transaction, + signers: [payer, newAccount], + commitment: 'singleGossip', + err: expectedErr, + }) + ).value; + expect(confirmResult.err).to.eql(expectedErr); - mockRpc.push([ - url, - { - method: 'getBalance', - params: [account.publicKey.toBase58(), {commitment: 'singleGossip'}], - }, - { - error: null, - result: { - context: { - slot: 11, - }, - value: 3 * minimumAmount, - }, - }, - ]); - expect(await connection.getBalance(account.publicKey)).toBe( - 3 * minimumAmount, - ); - - mockGetRecentBlockhash('max'); - mockRpc.push([ - url, - { - method: 'sendTransaction', - }, - { - error: null, - result: - '3WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk', - }, - ]); - - const newAccount = new Account(); - let transaction = new Transaction().add( - SystemProgram.createAccount({ - fromPubkey: account.publicKey, - newAccountPubkey: newAccount.publicKey, - lamports: minimumAmount, - space: 0, - programId: SystemProgram.programId, - }), - ); - - mockConfirmTransaction( - '3WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk', - ); - await sendAndConfirmTransaction( - connection, - transaction, - [account, newAccount], - {commitment: 'singleGossip'}, - ); - - mockRpc.push([ - url, - { - method: 'sendTransaction', - }, - { - error: null, - result: - '3WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk', - }, - ]); - - // This should fail because the account is already created - transaction = new Transaction().add( - SystemProgram.createAccount({ - fromPubkey: account.publicKey, - newAccountPubkey: newAccount.publicKey, - lamports: minimumAmount + 1, - space: 0, - programId: SystemProgram.programId, - }), - ); - const signature = await connection.sendTransaction( - transaction, - [account, newAccount], - {skipPreflight: true}, - ); - - const expectedErr = {InstructionError: [0, {Custom: 0}]}; - mockRpcSocket.push([ - { - method: 'signatureSubscribe', - params: [signature, {commitment: 'singleGossip'}], - }, - { - context: { - slot: 11, - }, - value: {err: expectedErr}, - }, - ]); - - // Wait for one confirmation - const confirmResult = ( - await connection.confirmTransaction(signature, 'singleGossip') - ).value; - expect(confirmResult.err).toEqual(expectedErr); - - mockRpc.push([ - url, - { + const signature = bs58.encode(transaction.signature); + await mockRpcResponse({ method: 'getSignatureStatuses', - params: [ - [ - '3WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk', - ], + params: [[signature]], + value: [ + { + slot: 0, + confirmations: 11, + status: {Err: expectedErr}, + err: expectedErr, + }, ], - }, - { - error: null, - result: { - context: { - slot: 11, - }, - value: [ - { - slot: 0, - confirmations: 11, - status: {Err: expectedErr}, - err: expectedErr, - }, - ], - }, - }, - ]); - - const response = (await connection.getSignatureStatus(signature)).value; - verifySignatureStatus(response, expectedErr); -}); - -test('transaction', async () => { - const accountFrom = new Account(); - const accountTo = new Account(); - const connection = new Connection(url, 'singleGossip'); - - mockRpc.push([ - url, - { - method: 'getMinimumBalanceForRentExemption', - params: [0, {commitment: 'singleGossip'}], - }, - { - error: null, - result: 50, - }, - ]); - - const minimumAmount = await connection.getMinimumBalanceForRentExemption( - 0, - 'singleGossip', - ); - - mockRpc.push([ - url, - { - method: 'requestAirdrop', - params: [accountFrom.publicKey.toBase58(), minimumAmount + 100010], - }, - { - error: null, - result: - '2WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk', - }, - ]); - const airdropFromSig = await connection.requestAirdrop( - accountFrom.publicKey, - minimumAmount + 100010, - ); - - mockConfirmTransaction(airdropFromSig); - await connection.confirmTransaction(airdropFromSig); - - mockRpc.push([ - url, - { - method: 'getBalance', - params: [accountFrom.publicKey.toBase58(), {commitment: 'singleGossip'}], - }, - { - error: null, - result: { - context: { - slot: 11, - }, - value: minimumAmount + 100010, - }, - }, - ]); - expect(await connection.getBalance(accountFrom.publicKey)).toBe( - minimumAmount + 100010, - ); - - mockRpc.push([ - url, - { - method: 'requestAirdrop', - params: [accountTo.publicKey.toBase58(), minimumAmount], - }, - { - error: null, - result: - '8WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk', - }, - ]); - - const airdropToSig = await connection.requestAirdrop( - accountTo.publicKey, - minimumAmount, - ); - - mockConfirmTransaction(airdropToSig); - await connection.confirmTransaction(airdropToSig); - - mockRpc.push([ - url, - { - method: 'getBalance', - params: [accountTo.publicKey.toBase58(), {commitment: 'singleGossip'}], - }, - { - error: null, - result: { - context: { - slot: 11, - }, - value: minimumAmount, - }, - }, - ]); - - expect(await connection.getBalance(accountTo.publicKey)).toBe(minimumAmount); - - mockGetRecentBlockhash('max'); - mockRpc.push([ - url, - { - method: 'sendTransaction', - }, - { - error: null, - result: - '1WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk', - }, - ]); - - const transaction = new Transaction().add( - SystemProgram.transfer({ - fromPubkey: accountFrom.publicKey, - toPubkey: accountTo.publicKey, - lamports: 10, - }), - ); - const signature = await connection.sendTransaction( - transaction, - [accountFrom], - {skipPreflight: true}, - ); - - mockConfirmTransaction(signature); - let confirmResult = ( - await connection.confirmTransaction(signature, 'singleGossip') - ).value; - expect(confirmResult.err).toBeNull(); - - mockGetRecentBlockhash('max'); - mockRpc.push([ - url, - { - method: 'sendTransaction', - }, - { - error: null, - result: - '2WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk', - }, - ]); - - // Send again and ensure that new blockhash is used - const lastFetch = Date.now(); - const transaction2 = new Transaction().add( - SystemProgram.transfer({ - fromPubkey: accountFrom.publicKey, - toPubkey: accountTo.publicKey, - lamports: 10, - }), - ); - const signature2 = await connection.sendTransaction( - transaction2, - [accountFrom], - {skipPreflight: true}, - ); - expect(signature).not.toEqual(signature2); - expect(transaction.recentBlockhash).not.toEqual(transaction2.recentBlockhash); - - mockConfirmTransaction(signature2); - await connection.confirmTransaction(signature2, 'singleGossip'); - - mockRpc.push([ - url, - { - method: 'sendTransaction', - }, - { - error: null, - result: - '3WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk', - }, - ]); - - // Send new transaction and ensure that same blockhash is used - const transaction3 = new Transaction().add( - SystemProgram.transfer({ - fromPubkey: accountFrom.publicKey, - toPubkey: accountTo.publicKey, - lamports: 9, - }), - ); - const signature3 = await connection.sendTransaction( - transaction3, - [accountFrom], - { - skipPreflight: true, - }, - ); - expect(transaction2.recentBlockhash).toEqual(transaction3.recentBlockhash); - - mockConfirmTransaction(signature3); - await connection.confirmTransaction(signature3, 'singleGossip'); - - // Sleep until blockhash cache times out - await sleep( - Math.max(0, 1000 + BLOCKHASH_CACHE_TIMEOUT_MS - (Date.now() - lastFetch)), - ); - - mockGetRecentBlockhash('max'); - mockRpc.push([ - url, - { - method: 'sendTransaction', - }, - { - error: null, - result: - '3WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk', - }, - ]); - - const transaction4 = new Transaction().add( - SystemProgram.transfer({ - fromPubkey: accountFrom.publicKey, - toPubkey: accountTo.publicKey, - lamports: 13, - }), - ); - - const signature4 = await connection.sendTransaction( - transaction4, - [accountFrom], - { - skipPreflight: true, - }, - ); - mockConfirmTransaction(signature4); - await connection.confirmTransaction(signature4, 'singleGossip'); - - expect(transaction4.recentBlockhash).not.toEqual( - transaction3.recentBlockhash, - ); - - mockRpc.push([ - url, - { - method: 'getBalance', - params: [accountFrom.publicKey.toBase58(), {commitment: 'singleGossip'}], - }, - { - error: null, - result: { - context: { - slot: 11, - }, - value: minimumAmount + 2, - }, - }, - ]); - - // accountFrom may have less than 100000 due to transaction fees - const balance = await connection.getBalance(accountFrom.publicKey); - expect(balance).toBeGreaterThan(0); - expect(balance).toBeLessThanOrEqual(minimumAmount + 100000); - - mockRpc.push([ - url, - { - method: 'getBalance', - params: [accountTo.publicKey.toBase58(), {commitment: 'singleGossip'}], - }, - { - error: null, - result: { - context: { - slot: 11, - }, - value: minimumAmount + 42, - }, - }, - ]); - expect(await connection.getBalance(accountTo.publicKey)).toBe( - minimumAmount + 42, - ); -}); - -test('multi-instruction transaction', async () => { - if (mockRpcEnabled) { - console.log('non-live test skipped'); - return; - } - - const accountFrom = new Account(); - const accountTo = new Account(); - const connection = new Connection(url, 'singleGossip'); - - let signature = await connection.requestAirdrop( - accountFrom.publicKey, - LAMPORTS_PER_SOL, - ); - await connection.confirmTransaction(signature, 'singleGossip'); - expect(await connection.getBalance(accountFrom.publicKey)).toBe( - LAMPORTS_PER_SOL, - ); - - const minimumAmount = await connection.getMinimumBalanceForRentExemption( - 0, - 'singleGossip', - ); - - signature = await connection.requestAirdrop( - accountTo.publicKey, - minimumAmount + 21, - ); - await connection.confirmTransaction(signature); - expect(await connection.getBalance(accountTo.publicKey)).toBe( - minimumAmount + 21, - ); - - // 1. Move(accountFrom, accountTo) - // 2. Move(accountTo, accountFrom) - const transaction = new Transaction() - .add( - SystemProgram.transfer({ - fromPubkey: accountFrom.publicKey, - toPubkey: accountTo.publicKey, - lamports: 100, - }), - ) - .add( - SystemProgram.transfer({ - fromPubkey: accountTo.publicKey, - toPubkey: accountFrom.publicKey, - lamports: 100, - }), - ); - signature = await connection.sendTransaction( - transaction, - [accountFrom, accountTo], - {skipPreflight: true}, - ); - - await connection.confirmTransaction(signature, 'singleGossip'); - - const response = (await connection.getSignatureStatus(signature)).value; - if (response !== null) { - expect(typeof response.slot).toEqual('number'); - expect(response.err).toBeNull(); - } else { - expect(response).not.toBeNull(); - } - - // accountFrom may have less than LAMPORTS_PER_SOL due to transaction fees - expect(await connection.getBalance(accountFrom.publicKey)).toBeGreaterThan(0); - expect( - await connection.getBalance(accountFrom.publicKey), - ).toBeLessThanOrEqual(LAMPORTS_PER_SOL); - - expect(await connection.getBalance(accountTo.publicKey)).toBe( - minimumAmount + 21, - ); -}); - -test('account change notification', async () => { - if (mockRpcEnabled) { - console.log('non-live test skipped'); - return; - } - - const connection = new Connection(url, 'singleGossip'); - const owner = new Account(); - const programAccount = new Account(); - - const mockCallback = jest.fn(); - - const subscriptionId = connection.onAccountChange( - programAccount.publicKey, - mockCallback, - 'singleGossip', - ); - - const balanceNeeded = Math.max( - await connection.getMinimumBalanceForRentExemption(0), - 1, - ); - - let signature = await connection.requestAirdrop( - owner.publicKey, - LAMPORTS_PER_SOL, - ); - await connection.confirmTransaction(signature); - try { - const transaction = new Transaction().add( - SystemProgram.transfer({ - fromPubkey: owner.publicKey, - toPubkey: programAccount.publicKey, - lamports: balanceNeeded, - }), - ); - await sendAndConfirmTransaction(connection, transaction, [owner], { - commitment: 'singleGossip', + withContext: true, }); - } catch (err) { - await connection.removeAccountChangeListener(subscriptionId); - throw err; - } - // Wait for mockCallback to receive a call - let i = 0; - for (;;) { - if (mockCallback.mock.calls.length > 0) { - break; - } + const response = (await connection.getSignatureStatus(signature)).value; + verifySignatureStatus(response, expectedErr); + }); - if (++i === 30) { - throw new Error('Account change notification not observed'); - } - // Sleep for a 1/4 of a slot, notifications only occur after a block is - // processed - await sleep((250 * DEFAULT_TICKS_PER_SLOT) / NUM_TICKS_PER_SECOND); - } + if (process.env.TEST_LIVE) { + it('transaction', async () => { + connection._commitment = 'singleGossip'; - await connection.removeAccountChangeListener(subscriptionId); - - expect(mockCallback.mock.calls[0][0].lamports).toBe(balanceNeeded); - expect(mockCallback.mock.calls[0][0].owner).toEqual(SystemProgram.programId); -}); - -test('program account change notification', async () => { - if (mockRpcEnabled) { - console.log('non-live test skipped'); - return; - } - - const connection = new Connection(url, 'singleGossip'); - const owner = new Account(); - const programAccount = new Account(); - - // const mockCallback = jest.fn(); - - const balanceNeeded = await connection.getMinimumBalanceForRentExemption(0); - - let notified = false; - const subscriptionId = connection.onProgramAccountChange( - SystemProgram.programId, - keyedAccountInfo => { - if (keyedAccountInfo.accountId !== programAccount.publicKey.toString()) { - //console.log('Ignoring another account', keyedAccountInfo); - return; - } - expect(keyedAccountInfo.accountInfo.lamports).toBe(balanceNeeded); - expect(keyedAccountInfo.accountInfo.owner).toEqual( - SystemProgram.programId, + const accountFrom = new Account(); + const accountTo = new Account(); + const minimumAmount = await connection.getMinimumBalanceForRentExemption( + 0, ); - notified = true; - }, - ); - let signature = await connection.requestAirdrop( - owner.publicKey, - LAMPORTS_PER_SOL, - ); - await connection.confirmTransaction(signature); - try { - const transaction = new Transaction().add( - SystemProgram.transfer({ - fromPubkey: owner.publicKey, - toPubkey: programAccount.publicKey, - lamports: balanceNeeded, - }), - ); - await sendAndConfirmTransaction(connection, transaction, [owner], { - commitment: 'singleGossip', + await helpers.airdrop({ + connection, + address: accountFrom.publicKey, + amount: minimumAmount + 100010, + }); + await helpers.airdrop({ + connection, + address: accountTo.publicKey, + amount: minimumAmount, + }); + + const transaction = new Transaction().add( + SystemProgram.transfer({ + fromPubkey: accountFrom.publicKey, + toPubkey: accountTo.publicKey, + lamports: 10, + }), + ); + + const signature = await sendAndConfirmTransaction( + connection, + transaction, + [accountFrom], + {preflightCommitment: 'singleGossip'}, + ); + + // Send again and ensure that new blockhash is used + const lastFetch = Date.now(); + const transaction2 = new Transaction().add( + SystemProgram.transfer({ + fromPubkey: accountFrom.publicKey, + toPubkey: accountTo.publicKey, + lamports: 10, + }), + ); + + const signature2 = await sendAndConfirmTransaction( + connection, + transaction2, + [accountFrom], + {preflightCommitment: 'singleGossip'}, + ); + + expect(signature).not.to.eq(signature2); + expect(transaction.recentBlockhash).not.to.eq( + transaction2.recentBlockhash, + ); + + // Send new transaction and ensure that same blockhash is used + const transaction3 = new Transaction().add( + SystemProgram.transfer({ + fromPubkey: accountFrom.publicKey, + toPubkey: accountTo.publicKey, + lamports: 9, + }), + ); + const signature3 = await sendAndConfirmTransaction( + connection, + transaction3, + [accountFrom], + {preflightCommitment: 'singleGossip'}, + ); + expect(transaction2.recentBlockhash).to.eq(transaction3.recentBlockhash); + + // Sleep until blockhash cache times out + await sleep( + Math.max( + 0, + 1000 + BLOCKHASH_CACHE_TIMEOUT_MS - (Date.now() - lastFetch), + ), + ); + + const transaction4 = new Transaction().add( + SystemProgram.transfer({ + fromPubkey: accountFrom.publicKey, + toPubkey: accountTo.publicKey, + lamports: 13, + }), + ); + + await sendAndConfirmTransaction(connection, transaction4, [accountFrom], { + preflightCommitment: 'singleGossip', + }); + + expect(transaction4.recentBlockhash).not.to.eq( + transaction3.recentBlockhash, + ); + + // accountFrom may have less than 100000 due to transaction fees + const balance = await connection.getBalance(accountFrom.publicKey); + expect(balance).to.be.greaterThan(0); + expect(balance).to.be.at.most(minimumAmount + 100000); + expect(await connection.getBalance(accountTo.publicKey)).to.eq( + minimumAmount + 42, + ); + }).timeout(45 * 1000); // waits 30s for cache timeout + + it('multi-instruction transaction', async () => { + connection._commitment = 'singleGossip'; + + const accountFrom = new Account(); + const accountTo = new Account(); + + let signature = await connection.requestAirdrop( + accountFrom.publicKey, + LAMPORTS_PER_SOL, + ); + await connection.confirmTransaction(signature); + expect(await connection.getBalance(accountFrom.publicKey)).to.eq( + LAMPORTS_PER_SOL, + ); + + const minimumAmount = await connection.getMinimumBalanceForRentExemption( + 0, + ); + + signature = await connection.requestAirdrop( + accountTo.publicKey, + minimumAmount + 21, + ); + await connection.confirmTransaction(signature); + expect(await connection.getBalance(accountTo.publicKey)).to.eq( + minimumAmount + 21, + ); + + // 1. Move(accountFrom, accountTo) + // 2. Move(accountTo, accountFrom) + const transaction = new Transaction() + .add( + SystemProgram.transfer({ + fromPubkey: accountFrom.publicKey, + toPubkey: accountTo.publicKey, + lamports: 100, + }), + ) + .add( + SystemProgram.transfer({ + fromPubkey: accountTo.publicKey, + toPubkey: accountFrom.publicKey, + lamports: 100, + }), + ); + signature = await connection.sendTransaction( + transaction, + [accountFrom, accountTo], + {skipPreflight: true}, + ); + + await connection.confirmTransaction(signature); + + const response = (await connection.getSignatureStatus(signature)).value; + if (response !== null) { + expect(typeof response.slot).to.eq('number'); + expect(response.err).to.be.null; + } else { + expect(response).not.to.be.null; + } + + // accountFrom may have less than LAMPORTS_PER_SOL due to transaction fees + expect( + await connection.getBalance(accountFrom.publicKey), + ).to.be.greaterThan(0); + expect(await connection.getBalance(accountFrom.publicKey)).to.be.at.most( + LAMPORTS_PER_SOL, + ); + + expect(await connection.getBalance(accountTo.publicKey)).to.eq( + minimumAmount + 21, + ); }); - } catch (err) { - await connection.removeProgramAccountChangeListener(subscriptionId); - throw err; - } - // Wait for mockCallback to receive a call - let i = 0; - while (!notified) { - //for (;;) { - if (++i === 30) { - throw new Error('Program change notification not observed'); - } - // Sleep for a 1/4 of a slot, notifications only occur after a block is - // processed - await sleep((250 * DEFAULT_TICKS_PER_SLOT) / NUM_TICKS_PER_SECOND); - } + // it('account change notification', async () => { + // if (!process.env.TEST_LIVE) { + // console.log('non-live test skipped'); + // return; + // } - await connection.removeProgramAccountChangeListener(subscriptionId); -}); - -test('slot notification', async () => { - if (mockRpcEnabled) { - console.log('non-live test skipped'); - return; - } - - const connection = new Connection(url, 'singleGossip'); - - let notified = false; - const subscriptionId = connection.onSlotChange(slotInfo => { - expect(slotInfo.parent).toBeDefined(); - expect(slotInfo.slot).toBeDefined(); - expect(slotInfo.root).toBeDefined(); - expect(slotInfo.slot).toBeGreaterThan(slotInfo.parent); - expect(slotInfo.slot).toBeGreaterThanOrEqual(slotInfo.root); - notified = true; - }); - - // Wait for mockCallback to receive a call - let i = 0; - while (!notified) { - if (++i === 30) { - throw new Error('Slot change notification not observed'); - } - // Sleep for a 1/4 of a slot, notifications only occur after a block is - // processed - await sleep((250 * DEFAULT_TICKS_PER_SLOT) / NUM_TICKS_PER_SECOND); - } - - await connection.removeSlotChangeListener(subscriptionId); -}); - -test('root notification', async () => { - if (mockRpcEnabled) { - console.log('non-live test skipped'); - return; - } - - const connection = new Connection(url, 'singleGossip'); - - let roots = []; - const subscriptionId = connection.onRootChange(root => { - roots.push(root); - }); - - // Wait for mockCallback to receive a call - let i = 0; - while (roots.length < 2) { - if (++i === 30) { - throw new Error('Root change notification not observed'); - } - // Sleep for a 1/4 of a slot, notifications only occur after a block is - // processed - await sleep((250 * DEFAULT_TICKS_PER_SLOT) / NUM_TICKS_PER_SECOND); - } - - expect(roots[1]).toBeGreaterThan(roots[0]); - await connection.removeRootChangeListener(subscriptionId); -}); - -test('https request', async () => { - if (mockRpcEnabled) { - console.log('non-live test skipped'); - return; - } - - const connection = new Connection('https://devnet.solana.com'); - const version = await connection.getVersion(); - expect(version['solana-core']).toBeTruthy(); + // const connection = new Connection(url, 'singleGossip'); + // const owner = new Account(); + // const programAccount = new Account(); + + // const mockCallback = jest.fn(); + + // const subscriptionId = connection.onAccountChange( + // programAccount.publicKey, + // mockCallback, + // 'singleGossip', + // ); + + // const balanceNeeded = Math.max( + // await connection.getMinimumBalanceForRentExemption(0), + // 1, + // ); + + // let signature = await connection.requestAirdrop( + // owner.publicKey, + // LAMPORTS_PER_SOL, + // ); + // await connection.confirmTransaction(signature); + // try { + // const transaction = new Transaction().add( + // SystemProgram.transfer({ + // fromPubkey: owner.publicKey, + // toPubkey: programAccount.publicKey, + // lamports: balanceNeeded, + // }), + // ); + // await sendAndConfirmTransaction(connection, transaction, [owner], { + // commitment: 'singleGossip', + // }); + // } catch (err) { + // await connection.removeAccountChangeListener(subscriptionId); + // throw err; + // } + + // // Wait for mockCallback to receive a call + // let i = 0; + // for (;;) { + // if (mockCallback.mock.calls.length > 0) { + // break; + // } + + // if (++i === 30) { + // throw new Error('Account change notification not observed'); + // } + // // Sleep for a 1/4 of a slot, notifications only occur after a block is + // // processed + // await sleep((250 * DEFAULT_TICKS_PER_SLOT) / NUM_TICKS_PER_SECOND); + // } + + // await connection.removeAccountChangeListener(subscriptionId); + + // expect(mockCallback.mock.calls[0][0].lamports).to.eq(balanceNeeded); + // expect(mockCallback.mock.calls[0][0].owner).to.eq(SystemProgram.programId); + // }); + + it('program account change notification', async () => { + connection._commitment = 'singleGossip'; + + const owner = new Account(); + const programAccount = new Account(); + const balanceNeeded = await connection.getMinimumBalanceForRentExemption( + 0, + ); + + let notified = false; + const subscriptionId = connection.onProgramAccountChange( + SystemProgram.programId, + (keyedAccountInfo: KeyedAccountInfo) => { + // FIX: accountId should be `PublicKey` not `string` + if ( + keyedAccountInfo.accountId === programAccount.publicKey.toBase58() + ) { + expect(keyedAccountInfo.accountInfo.lamports).to.eq(balanceNeeded); + expect( + keyedAccountInfo.accountInfo.owner.equals( + SystemProgram.programId, + ), + ).to.be.true; + notified = true; + } + }, + ); + + await helpers.airdrop({ + connection, + address: owner.publicKey, + amount: LAMPORTS_PER_SOL, + }); + + try { + const transaction = new Transaction().add( + SystemProgram.transfer({ + fromPubkey: owner.publicKey, + toPubkey: programAccount.publicKey, + lamports: balanceNeeded, + }), + ); + await sendAndConfirmTransaction(connection, transaction, [owner], { + commitment: 'singleGossip', + }); + } catch (err) { + await connection.removeProgramAccountChangeListener(subscriptionId); + throw err; + } + + let i = 0; + while (!notified) { + if (++i === 30) { + throw new Error('Program change notification not observed'); + } + // Sleep for a 1/4 of a slot, notifications only occur after a block is + // processed + await sleep((250 * DEFAULT_TICKS_PER_SLOT) / NUM_TICKS_PER_SECOND); + } + + await connection.removeProgramAccountChangeListener(subscriptionId); + }); + + it('slot notification', async () => { + let notifiedSlotInfo: SlotInfo; + const subscriptionId = connection.onSlotChange(slotInfo => { + notifiedSlotInfo = slotInfo; + }); + + // Wait for notification + let i = 0; + while (!notifiedSlotInfo) { + if (++i === 30) { + throw new Error('Slot change notification not observed'); + } + // Sleep for a 1/4 of a slot, notifications only occur after a block is + // processed + await sleep((250 * DEFAULT_TICKS_PER_SLOT) / NUM_TICKS_PER_SECOND); + } + + expect(notifiedSlotInfo.parent).to.be.at.least(0); + expect(notifiedSlotInfo.root).to.be.at.least(0); + expect(notifiedSlotInfo.slot).to.be.at.least(1); + + await connection.removeSlotChangeListener(subscriptionId); + }); + + it('root notification', async () => { + let roots = []; + const subscriptionId = connection.onRootChange(root => { + roots.push(root); + }); + + // Wait for mockCallback to receive a call + let i = 0; + while (roots.length < 2) { + if (++i === 30) { + throw new Error('Root change notification not observed'); + } + // Sleep for a 1/4 of a slot, notifications only occur after a block is + // processed + await sleep((250 * DEFAULT_TICKS_PER_SLOT) / NUM_TICKS_PER_SECOND); + } + + expect(roots[1]).to.be.greaterThan(roots[0]); + await connection.removeRootChangeListener(subscriptionId); + }); + + it('https request', async () => { + const connection = new Connection('https://devnet.solana.com'); + const version = await connection.getVersion(); + expect(version['solana-core']).to.be.ok; + }); + } }); diff --git a/web3.js/test/fixtures/noop-c/build.sh b/web3.js/test/fixtures/noop-c/build.sh deleted file mode 100755 index 0043703b15..0000000000 --- a/web3.js/test/fixtures/noop-c/build.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env bash -set -ex - -cd "$(dirname "$0")" - -make -C ../../../examples/bpf-c-noop/ -cp ../../../examples/bpf-c-noop/out/noop.so . diff --git a/web3.js/test/fixtures/noop-rust/build.sh b/web3.js/test/fixtures/noop-rust/build.sh deleted file mode 100755 index 622775f783..0000000000 --- a/web3.js/test/fixtures/noop-rust/build.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env bash -set -ex - -cd "$(dirname "$0")" - -cargo build-bpf --manifest-path=../../../examples/bpf-rust-noop/Cargo.toml -cp ../../../examples/bpf-rust-noop/target/deploy/solana_bpf_rust_noop.so . diff --git a/web3.js/test/get-confirmed-block.test.js b/web3.js/test/get-confirmed-block.test.js deleted file mode 100644 index 4031476b8b..0000000000 --- a/web3.js/test/get-confirmed-block.test.js +++ /dev/null @@ -1,83 +0,0 @@ -// @flow -import {Account} from '../src/account'; -import {SystemProgram} from '../src/system-program'; -import {Transaction} from '../src/transaction'; - -test('verify getConfirmedBlock', () => { - const account0 = new Account(); - const account1 = new Account(); - const account2 = new Account(); - const account3 = new Account(); - const recentBlockhash = account1.publicKey.toBase58(); // Fake recentBlockhash - - // Create a couple signed transactions - const transfer0 = SystemProgram.transfer({ - fromPubkey: account0.publicKey, - toPubkey: account1.publicKey, - lamports: 123, - }); - - const transaction0 = new Transaction({recentBlockhash}).add(transfer0); - transaction0.sign(account0); - const transfer1 = SystemProgram.transfer({ - fromPubkey: account2.publicKey, - toPubkey: account3.publicKey, - lamports: 456, - }); - - let transaction1 = new Transaction({recentBlockhash}).add(transfer1); - transaction1.sign(account2); - - // Build ConfirmedBlock, with dummy data for blockhashes, balances - const confirmedBlock = { - blockhash: recentBlockhash, - previousBlockhash: recentBlockhash, - transactions: [ - { - transaction: transaction0, - meta: { - fee: 0, - preBalances: [100000, 100000, 1, 1, 1], - postBalances: [99877, 100123, 1, 1, 1], - status: {Ok: null}, - err: null, - }, - }, - { - transaction: transaction1, - meta: { - fee: 0, - preBalances: [100000, 100000, 1, 1, 1], - postBalances: [99544, 100456, 1, 1, 1], - status: {Ok: null}, - err: null, - }, - }, - ], - rewards: [], - }; - - // Verify signatures in ConfirmedBlock - for (const transactionWithMeta of confirmedBlock.transactions) { - expect(transactionWithMeta.transaction.verifySignatures()).toBe(true); - } - - const bogusSignature = { - signature: Buffer.alloc(64, 9), - publicKey: account2.publicKey, - }; - transaction1.signatures[0] = bogusSignature; - - let badConfirmedBlock = confirmedBlock; - badConfirmedBlock.transactions[1].transaction = transaction1; - - // Verify signatures in ConfirmedBlock - const verifications = badConfirmedBlock.transactions.map( - transactionWithMeta => transactionWithMeta.transaction.verifySignatures(), - ); - expect( - verifications.reduce( - (accumulator, currentValue) => accumulator && currentValue, - ), - ).toBe(false); -}); diff --git a/web3.js/test/mockrpc/confirm-transaction.js b/web3.js/test/mockrpc/confirm-transaction.js deleted file mode 100644 index faed9e4b95..0000000000 --- a/web3.js/test/mockrpc/confirm-transaction.js +++ /dev/null @@ -1,19 +0,0 @@ -// @flow - -import type {TransactionSignature} from '../../src/transaction'; -import {mockRpcSocket} from '../__mocks__/rpc-websockets'; - -export function mockConfirmTransaction(signature: TransactionSignature) { - mockRpcSocket.push([ - { - method: 'signatureSubscribe', - params: [signature, {commitment: 'singleGossip'}], - }, - { - context: { - slot: 11, - }, - value: {err: null}, - }, - ]); -} diff --git a/web3.js/test/mockrpc/get-recent-blockhash.js b/web3.js/test/mockrpc/get-recent-blockhash.js deleted file mode 100644 index 23c7c316e1..0000000000 --- a/web3.js/test/mockrpc/get-recent-blockhash.js +++ /dev/null @@ -1,36 +0,0 @@ -// @flow - -import {Account} from '../../src'; -import type {Commitment} from '../../src/connection'; -import {url} from '../url'; -import {mockRpc} from '../__mocks__/node-fetch'; - -export function mockGetRecentBlockhash(commitment: ?Commitment) { - const recentBlockhash = new Account(); - const params = []; - if (commitment) { - params.push({commitment}); - } - - mockRpc.push([ - url, - { - method: 'getRecentBlockhash', - params, - }, - { - error: null, - result: { - context: { - slot: 11, - }, - value: { - blockhash: recentBlockhash.publicKey.toBase58(), - feeCalculator: { - lamportsPerSignature: 42, - }, - }, - }, - }, - ]); -} diff --git a/web3.js/test/mocks/rpc-http.js b/web3.js/test/mocks/rpc-http.js new file mode 100644 index 0000000000..2c009016cf --- /dev/null +++ b/web3.js/test/mocks/rpc-http.js @@ -0,0 +1,170 @@ +// @flow + +import bs58 from 'bs58'; +import BN from 'bn.js'; +import * as mockttp from 'mockttp'; +import {mockRpcMessage} from './rpc-websockets'; + +import {Connection} from '../../src'; +import type {Commitment} from '../../src/connection'; + +export const mockServer = process.env.TEST_LIVE || mockttp.getLocal(); + +let uniqueCounter = 0; +export const uniqueSignature = () => { + return bs58.encode(new BN(++uniqueCounter).toArray(null, 64)); +}; +export const uniqueBlockhash = () => { + return bs58.encode(new BN(++uniqueCounter).toArray(null, 32)); +}; + +export const mockErrorMessage = 'Invalid'; +export const mockErrorResponse = { + code: -32602, + message: mockErrorMessage, +}; + +export const mockRpcResponse = async ({ + method, + params, + value, + error, + withContext, +}: { + method: string, + params: Array, + value: any, + error: any, + withContext?: boolean, +}) => { + if (process.env.TEST_LIVE) return; + let result = withContext + ? { + context: { + slot: 11, + }, + value, + } + : value; + await mockServer + .post('/') + .withJsonBodyIncluding({ + jsonrpc: '2.0', + method, + params, + }) + .thenReply( + 200, + JSON.stringify({ + jsonrpc: '2.0', + id: '', + error, + result, + }), + ); +}; + +const recentBlockhash = async ({ + connection, + commitment, +}: { + connection: Connection, + commitment: ?Commitment, +}) => { + const blockhash = uniqueBlockhash(); + const params = []; + if (commitment) { + params.push({commitment}); + } + + await mockRpcResponse({ + method: 'getRecentBlockhash', + params, + value: { + blockhash, + feeCalculator: { + lamportsPerSignature: 42, + }, + }, + withContext: true, + }); + + return await connection.getRecentBlockhash(commitment); +}; + +const processTransaction = async ({ + connection, + transaction, + signers, + commitment, + err, +}: { + connection: Connection, + transaction: Transaction, + signers: Array, + commitment: Commitment, + err?: any, +}) => { + const blockhash = (await recentBlockhash({connection})).blockhash; + transaction.recentBlockhash = blockhash; + transaction.sign(...signers); + + const encoded = transaction.serialize().toString('base64'); + const signature = bs58.encode(transaction.signature); + await mockRpcResponse({ + method: 'sendTransaction', + params: [encoded], + value: signature, + }); + + const sendOptions = err + ? { + skipPreflight: true, + } + : { + preflightCommitment: commitment, + }; + + await connection.sendEncodedTransaction(encoded, sendOptions); + + await mockRpcMessage({ + method: 'signatureSubscribe', + params: [signature, {commitment}], + result: {err: err || null}, + }); + + return await connection.confirmTransaction(signature, commitment); +}; + +const airdrop = async ({ + connection, + address, + amount, +}: { + connection: Connection, + address: PublicKey, + amount: number, +}) => { + await mockRpcResponse({ + method: 'requestAirdrop', + params: [address.toBase58(), amount], + value: uniqueSignature(), + }); + + const signature = await connection.requestAirdrop(address, amount); + + await mockRpcMessage({ + method: 'signatureSubscribe', + params: [signature, {commitment: 'singleGossip'}], + result: {err: null}, + }); + + await connection.confirmTransaction(signature, 'singleGossip'); + return signature; +}; + +export const helpers = { + airdrop, + processTransaction, + recentBlockhash, +}; diff --git a/web3.js/test/mocks/rpc-websockets.js b/web3.js/test/mocks/rpc-websockets.js new file mode 100644 index 0000000000..59e9fe4fa6 --- /dev/null +++ b/web3.js/test/mocks/rpc-websockets.js @@ -0,0 +1,106 @@ +// @flow + +import {Client as LiveClient} from 'rpc-websockets'; +import {expect} from 'chai'; +import sinon from 'sinon'; +import {Connection} from '../../src'; + +type RpcRequest = { + method: string, + params?: Array, +}; + +type RpcResponse = { + context: { + slot: number, + }, + value: any, +}; + +const mockRpcSocket: Array<[RpcRequest, RpcResponse]> = []; +const sandbox = sinon.createSandbox(); + +export const mockRpcMessage = ({ + method, + params, + result, +}: { + method: string, + params: Array, + result: any, +}) => { + mockRpcSocket.push([ + {method, params}, + { + context: {slot: 11}, + value: result, + }, + ]); +}; + +export const stubRpcWebSocket = (connection: Connection) => { + const rpcWebSocket = connection._rpcWebSocket; + const mockClient = new MockClient(rpcWebSocket); + sandbox.stub(rpcWebSocket, 'connect').callsFake(() => { + mockClient.connect(); + }); + sandbox.stub(rpcWebSocket, 'close').callsFake(() => { + mockClient.close(); + }); + sandbox.stub(rpcWebSocket, 'call').callsFake((method, params) => { + return mockClient.call(method, params); + }); +}; + +export const restoreRpcWebSocket = (connection: Connection) => { + connection._rpcWebSocket.close(); + if (connection._rpcWebSocketIdleTimeout !== null) { + clearTimeout(connection._rpcWebSocketIdleTimeout); + connection._rpcWebSocketIdleTimeout = null; + } + sandbox.restore(); +}; + +class MockClient { + mockOpen = false; + subscriptionCounter = 0; + + constructor(rpcWebSocket: LiveClient) { + this.client = rpcWebSocket; + } + + connect() { + if (!this.mockOpen) { + this.mockOpen = true; + this.client.emit('open'); + } + } + + close() { + if (this.mockOpen) { + this.mockOpen = false; + this.client.emit('close'); + } + } + + call(method: string, params: Array): Promise { + expect(mockRpcSocket.length).to.be.at.least(1); + const [mockRequest, mockResponse] = mockRpcSocket.shift(); + + expect(method).to.eq(mockRequest.method); + expect(params).to.eql(mockRequest.params); + + let id = ++this.subscriptionCounter; + const response = { + subscription: id, + result: mockResponse, + }; + + setImmediate(() => { + const eventName = method.replace('Subscribe', 'Notification'); + this.client.emit(eventName, response); + }); + + return Promise.resolve(id); + } +} diff --git a/web3.js/test/new-account-with-lamports.js b/web3.js/test/new-account-with-lamports.js deleted file mode 100644 index 7e6543ee22..0000000000 --- a/web3.js/test/new-account-with-lamports.js +++ /dev/null @@ -1,36 +0,0 @@ -// @flow - -import {Account, Connection} from '../src'; -import {mockRpc} from './__mocks__/node-fetch'; -import {url} from './url'; - -export async function newAccountWithLamports( - connection: Connection, - lamports: number = 1000000, -): Promise { - const account = new Account(); - - { - mockRpc.push([ - url, - { - method: 'requestAirdrop', - params: [account.publicKey.toBase58(), lamports], - }, - { - error: null, - // Signature doesn't matter - result: - '3WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk', - }, - ]); - } - - const signature = await connection.requestAirdrop( - account.publicKey, - lamports, - ); - await connection.confirmTransaction(signature, 'singleGossip'); - - return account; -} diff --git a/web3.js/test/nonce.test.js b/web3.js/test/nonce.test.js index f953e0e776..502581475c 100644 --- a/web3.js/test/nonce.test.js +++ b/web3.js/test/nonce.test.js @@ -1,6 +1,8 @@ // @flow import bs58 from 'bs58'; +import {Buffer} from 'buffer'; +import {expect} from 'chai'; import { Account, @@ -10,15 +12,9 @@ import { PublicKey, } from '../src'; import {NONCE_ACCOUNT_LENGTH} from '../src/nonce-account'; -import {mockRpc, mockRpcEnabled} from './__mocks__/node-fetch'; -import {mockGetRecentBlockhash} from './mockrpc/get-recent-blockhash'; -import {url} from './url'; -import {mockConfirmTransaction} from './mockrpc/confirm-transaction'; - -if (!mockRpcEnabled) { - // Testing max commitment level takes around 20s to complete - jest.setTimeout(30000); -} +import {MOCK_PORT, url} from './url'; +import {helpers, mockRpcResponse, mockServer} from './mocks/rpc-http'; +import {stubRpcWebSocket, restoreRpcWebSocket} from './mocks/rpc-websockets'; const expectedData = (authorizedPubkey: PublicKey): [string, string] => { const expectedData = Buffer.alloc(NONCE_ACCOUNT_LENGTH); @@ -31,257 +27,154 @@ const expectedData = (authorizedPubkey: PublicKey): [string, string] => { return [expectedData.toString('base64'), 'base64']; }; -test('create and query nonce account', async () => { - const from = new Account(); - const nonceAccount = new Account(); - const connection = new Connection(url, 'singleGossip'); +describe('Nonce', () => { + let connection: Connection; + beforeEach(() => { + connection = new Connection(url); + }); - mockRpc.push([ - url, - { + if (!process.env.TEST_LIVE) { + beforeEach(() => { + mockServer.start(MOCK_PORT); + stubRpcWebSocket(connection); + }); + + afterEach(() => { + mockServer.stop(); + restoreRpcWebSocket(connection); + }); + } + + it('create and query nonce account', async () => { + const from = new Account(); + const nonceAccount = new Account(); + + await mockRpcResponse({ method: 'getMinimumBalanceForRentExemption', - params: [NONCE_ACCOUNT_LENGTH, {commitment: 'singleGossip'}], - }, - { - error: null, - result: 50, - }, - ]); + params: [NONCE_ACCOUNT_LENGTH], + value: 50, + }); - const minimumAmount = await connection.getMinimumBalanceForRentExemption( - NONCE_ACCOUNT_LENGTH, - ); + const minimumAmount = await connection.getMinimumBalanceForRentExemption( + NONCE_ACCOUNT_LENGTH, + ); - mockRpc.push([ - url, - { - method: 'requestAirdrop', - params: [from.publicKey.toBase58(), minimumAmount * 2], - }, - { - error: null, - result: - '1WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk', - }, - ]); + await helpers.airdrop({ + connection, + address: from.publicKey, + amount: minimumAmount * 2, + }); - const signature = await connection.requestAirdrop( - from.publicKey, - minimumAmount * 2, - ); - mockConfirmTransaction(signature); - await connection.confirmTransaction(signature, 'singleGossip'); + const transaction = new Transaction().add( + SystemProgram.createNonceAccount({ + fromPubkey: from.publicKey, + noncePubkey: nonceAccount.publicKey, + authorizedPubkey: from.publicKey, + lamports: minimumAmount, + }), + ); - mockRpc.push([ - url, - { - method: 'getBalance', - params: [from.publicKey.toBase58(), {commitment: 'singleGossip'}], - }, - { - error: null, - result: { - context: { - slot: 11, - }, - value: minimumAmount * 2, - }, - }, - ]); + await helpers.processTransaction({ + connection, + transaction, + signers: [from, nonceAccount], + commitment: 'singleGossip', + }); - const balance = await connection.getBalance(from.publicKey); - expect(balance).toBe(minimumAmount * 2); - - mockGetRecentBlockhash('max'); - mockRpc.push([ - url, - { - method: 'sendTransaction', - }, - { - error: null, - result: - '3WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk', - }, - ]); - - const transaction = new Transaction().add( - SystemProgram.createNonceAccount({ - fromPubkey: from.publicKey, - noncePubkey: nonceAccount.publicKey, - authorizedPubkey: from.publicKey, - lamports: minimumAmount, - }), - ); - const nonceSignature = await connection.sendTransaction( - transaction, - [from, nonceAccount], - { - skipPreflight: true, - }, - ); - mockConfirmTransaction(nonceSignature); - await connection.confirmTransaction(nonceSignature, 'singleGossip'); - - mockRpc.push([ - url, - { + await mockRpcResponse({ method: 'getAccountInfo', params: [ nonceAccount.publicKey.toBase58(), {encoding: 'base64', commitment: 'singleGossip'}, ], - }, - { - error: null, - result: { - context: { - slot: 11, - }, - value: { - owner: '11111111111111111111111111111111', - lamports: minimumAmount, - data: expectedData(from.publicKey), - executable: false, - }, + value: { + owner: '11111111111111111111111111111111', + lamports: minimumAmount, + data: expectedData(from.publicKey), + executable: false, }, - }, - ]); - // - const nonceAccountData = await connection.getNonce(nonceAccount.publicKey); - if (nonceAccountData === null) { - expect(nonceAccountData).not.toBeNull(); - return; - } - expect(nonceAccountData.authorizedPubkey).toEqual(from.publicKey); - expect(bs58.decode(nonceAccountData.nonce).length).toBeGreaterThan(30); -}); + withContext: true, + }); -test('create and query nonce account with seed', async () => { - const from = new Account(); - const seed = 'seed'; - const noncePubkey = await PublicKey.createWithSeed( - from.publicKey, - seed, - SystemProgram.programId, - ); - const connection = new Connection(url, 'singleGossip'); - - mockRpc.push([ - url, - { - method: 'getMinimumBalanceForRentExemption', - params: [NONCE_ACCOUNT_LENGTH, {commitment: 'singleGossip'}], - }, - { - error: null, - result: 50, - }, - ]); - - const minimumAmount = await connection.getMinimumBalanceForRentExemption( - NONCE_ACCOUNT_LENGTH, - ); - - mockRpc.push([ - url, - { - method: 'requestAirdrop', - params: [from.publicKey.toBase58(), minimumAmount * 2], - }, - { - error: null, - result: - '1WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk', - }, - ]); - - const signature = await connection.requestAirdrop( - from.publicKey, - minimumAmount * 2, - ); - mockConfirmTransaction(signature); - await connection.confirmTransaction(signature, 'singleGossip'); - - mockRpc.push([ - url, - { - method: 'getBalance', - params: [from.publicKey.toBase58(), {commitment: 'singleGossip'}], - }, - { - error: null, - result: { - context: { - slot: 11, - }, - value: minimumAmount * 2, - }, - }, - ]); - - const balance = await connection.getBalance(from.publicKey); - expect(balance).toBe(minimumAmount * 2); - - mockGetRecentBlockhash('max'); - mockRpc.push([ - url, - { - method: 'sendTransaction', - }, - { - error: null, - result: - '3WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk', - }, - ]); - - const transaction = new Transaction().add( - SystemProgram.createNonceAccount({ - fromPubkey: from.publicKey, - noncePubkey: noncePubkey, - basePubkey: from.publicKey, - seed, - authorizedPubkey: from.publicKey, - lamports: minimumAmount, - }), - ); - const nonceSignature = await connection.sendTransaction(transaction, [from], { - skipPreflight: true, + const nonceAccountData = await connection.getNonce( + nonceAccount.publicKey, + 'singleGossip', + ); + if (nonceAccountData === null) { + expect(nonceAccountData).not.to.be.null; + return; + } + expect(nonceAccountData.authorizedPubkey).to.eql(from.publicKey); + expect(bs58.decode(nonceAccountData.nonce).length).to.be.greaterThan(30); }); - mockConfirmTransaction(nonceSignature); - await connection.confirmTransaction(nonceSignature, 'singleGossip'); - mockRpc.push([ - url, - { + it('create and query nonce account with seed', async () => { + const from = new Account(); + const seed = 'seed'; + const noncePubkey = await PublicKey.createWithSeed( + from.publicKey, + seed, + SystemProgram.programId, + ); + + await mockRpcResponse({ + method: 'getMinimumBalanceForRentExemption', + params: [NONCE_ACCOUNT_LENGTH], + value: 50, + }); + + const minimumAmount = await connection.getMinimumBalanceForRentExemption( + NONCE_ACCOUNT_LENGTH, + ); + + await helpers.airdrop({ + connection, + address: from.publicKey, + amount: minimumAmount * 2, + }); + + const transaction = new Transaction().add( + SystemProgram.createNonceAccount({ + fromPubkey: from.publicKey, + noncePubkey: noncePubkey, + basePubkey: from.publicKey, + seed, + authorizedPubkey: from.publicKey, + lamports: minimumAmount, + }), + ); + + await helpers.processTransaction({ + connection, + transaction, + signers: [from], + commitment: 'singleGossip', + }); + + await mockRpcResponse({ method: 'getAccountInfo', params: [ noncePubkey.toBase58(), {encoding: 'base64', commitment: 'singleGossip'}, ], - }, - { - error: null, - result: { - context: { - slot: 11, - }, - value: { - owner: '11111111111111111111111111111111', - lamports: minimumAmount, - data: expectedData(from.publicKey), - executable: false, - }, + value: { + owner: '11111111111111111111111111111111', + lamports: minimumAmount, + data: expectedData(from.publicKey), + executable: false, }, - }, - ]); - // - const nonceAccountData = await connection.getNonce(noncePubkey); - if (nonceAccountData === null) { - expect(nonceAccountData).not.toBeNull(); - return; - } - expect(nonceAccountData.authorizedPubkey).toEqual(from.publicKey); - expect(bs58.decode(nonceAccountData.nonce).length).toBeGreaterThan(30); + withContext: true, + }); + + const nonceAccountData = await connection.getNonce( + noncePubkey, + 'singleGossip', + ); + if (nonceAccountData === null) { + expect(nonceAccountData).not.to.be.null; + return; + } + expect(nonceAccountData.authorizedPubkey).to.eql(from.publicKey); + expect(bs58.decode(nonceAccountData.nonce).length).to.be.greaterThan(30); + }); }); diff --git a/web3.js/test/publickey.test.js b/web3.js/test/publickey.test.js index 61cf897d6d..ce9738a3d5 100644 --- a/web3.js/test/publickey.test.js +++ b/web3.js/test/publickey.test.js @@ -1,11 +1,77 @@ // @flow import BN from 'bn.js'; - +import {Buffer} from 'buffer'; import {PublicKey, MAX_SEED_LENGTH} from '../src/publickey'; +import {expect, use} from 'chai'; +import chaiAsPromised from 'chai-as-promised'; -test('invalid', () => { - expect(() => { - new PublicKey([ +use(chaiAsPromised); + +describe('PublicKey', function () { + it('invalid', () => { + expect(() => { + new PublicKey([ + 3, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ]); + }).to.throw(); + + expect(() => { + new PublicKey( + '0x300000000000000000000000000000000000000000000000000000000000000000000', + ); + }).to.throw(); + + expect(() => { + new PublicKey( + '0x300000000000000000000000000000000000000000000000000000000000000', + ); + }).to.throw(); + + expect(() => { + new PublicKey( + '135693854574979916511997248057056142015550763280047535983739356259273198796800000', + ); + }).to.throw(); + + expect(() => { + new PublicKey('12345'); + }).to.throw(); + }); + + it('equals', () => { + const arrayKey = new PublicKey([ 3, 0, 0, @@ -38,286 +104,228 @@ test('invalid', () => { 0, 0, 0, + ]); + const base58Key = new PublicKey( + 'CiDwVBFgWV9E5MvXWoLgnEgn2hK7rJikbvfWavzAQz3', + ); + + expect(arrayKey.equals(base58Key)).to.be.true; + }); + + it('toBase58', () => { + const key = new PublicKey('CiDwVBFgWV9E5MvXWoLgnEgn2hK7rJikbvfWavzAQz3'); + expect(key.toBase58()).to.eq('CiDwVBFgWV9E5MvXWoLgnEgn2hK7rJikbvfWavzAQz3'); + expect(key.toString()).to.eq('CiDwVBFgWV9E5MvXWoLgnEgn2hK7rJikbvfWavzAQz3'); + + const key2 = new PublicKey('1111111111111111111111111111BukQL'); + expect(key2.toBase58()).to.eq('1111111111111111111111111111BukQL'); + expect(key2.toString()).to.eq('1111111111111111111111111111BukQL'); + + const key3 = new PublicKey('11111111111111111111111111111111'); + expect(key3.toBase58()).to.eq('11111111111111111111111111111111'); + + const key4 = new PublicKey([ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, 0, ]); - }).toThrow(); + expect(key4.toBase58()).to.eq('11111111111111111111111111111111'); + }); - expect(() => { - new PublicKey( - '0x300000000000000000000000000000000000000000000000000000000000000000000', + it('toBuffer', () => { + const key = new PublicKey('CiDwVBFgWV9E5MvXWoLgnEgn2hK7rJikbvfWavzAQz3'); + expect(key.toBuffer()).to.have.length(32); + expect(key.toBase58()).to.eq('CiDwVBFgWV9E5MvXWoLgnEgn2hK7rJikbvfWavzAQz3'); + + const key2 = new PublicKey('11111111111111111111111111111111'); + expect(key2.toBuffer()).to.have.length(32); + expect(key2.toBase58()).to.eq('11111111111111111111111111111111'); + + const key3 = new PublicKey(0); + expect(key3.toBuffer()).to.have.length(32); + expect(key3.toBase58()).to.eq('11111111111111111111111111111111'); + }); + + it('equals (II)', () => { + const key1 = new PublicKey([ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + ]); + const key2 = new PublicKey(key1.toBuffer()); + + expect(key1.equals(key2)).to.be.true; + }); + + it('createWithSeed', async () => { + const defaultPublicKey = new PublicKey('11111111111111111111111111111111'); + const derivedKey = await PublicKey.createWithSeed( + defaultPublicKey, + 'limber chicken: 4/45', + defaultPublicKey, ); - }).toThrow(); - expect(() => { - new PublicKey( - '0x300000000000000000000000000000000000000000000000000000000000000', + expect( + derivedKey.equals( + new PublicKey('9h1HyLCW5dZnBVap8C5egQ9Z6pHyjsh5MNy83iPqqRuq'), + ), + ).to.be.true; + }); + + it('createProgramAddress', async () => { + const programId = new PublicKey( + 'BPFLoader1111111111111111111111111111111111', ); - }).toThrow(); - - expect(() => { - new PublicKey( - '135693854574979916511997248057056142015550763280047535983739356259273198796800000', + const publicKey = new PublicKey( + 'SeedPubey1111111111111111111111111111111111', ); - }).toThrow(); - expect(() => { - new PublicKey('12345'); - }).toThrow(); -}); - -test('equals', () => { - const arrayKey = new PublicKey([ - 3, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - ]); - const base58Key = new PublicKey( - 'CiDwVBFgWV9E5MvXWoLgnEgn2hK7rJikbvfWavzAQz3', - ); - - expect(arrayKey.equals(base58Key)).toBe(true); -}); - -test('toBase58', () => { - const key = new PublicKey('CiDwVBFgWV9E5MvXWoLgnEgn2hK7rJikbvfWavzAQz3'); - expect(key.toBase58()).toBe('CiDwVBFgWV9E5MvXWoLgnEgn2hK7rJikbvfWavzAQz3'); - expect(key.toString()).toBe('CiDwVBFgWV9E5MvXWoLgnEgn2hK7rJikbvfWavzAQz3'); - - const key2 = new PublicKey('1111111111111111111111111111BukQL'); - expect(key2.toBase58()).toBe('1111111111111111111111111111BukQL'); - expect(key2.toString()).toBe('1111111111111111111111111111BukQL'); - - const key3 = new PublicKey('11111111111111111111111111111111'); - expect(key3.toBase58()).toBe('11111111111111111111111111111111'); - - const key4 = new PublicKey([ - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - ]); - expect(key4.toBase58()).toBe('11111111111111111111111111111111'); -}); - -test('toBuffer', () => { - const key = new PublicKey('CiDwVBFgWV9E5MvXWoLgnEgn2hK7rJikbvfWavzAQz3'); - expect(key.toBuffer()).toHaveLength(32); - expect(key.toBase58()).toBe('CiDwVBFgWV9E5MvXWoLgnEgn2hK7rJikbvfWavzAQz3'); - - const key2 = new PublicKey('11111111111111111111111111111111'); - expect(key2.toBuffer()).toHaveLength(32); - expect(key2.toBase58()).toBe('11111111111111111111111111111111'); - - const key3 = new PublicKey(0); - expect(key3.toBuffer()).toHaveLength(32); - expect(key3.toBase58()).toBe('11111111111111111111111111111111'); -}); - -test('equals (II)', () => { - const key1 = new PublicKey([ - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 1, - ]); - const key2 = new PublicKey(key1.toBuffer()); - - expect(key1.equals(key2)).toBe(true); -}); - -test('createWithSeed', async () => { - const defaultPublicKey = new PublicKey('11111111111111111111111111111111'); - const derivedKey = await PublicKey.createWithSeed( - defaultPublicKey, - 'limber chicken: 4/45', - defaultPublicKey, - ); - - expect( - derivedKey.equals( - new PublicKey('9h1HyLCW5dZnBVap8C5egQ9Z6pHyjsh5MNy83iPqqRuq'), - ), - ).toBe(true); -}); - -test('createProgramAddress', async () => { - const programId = new PublicKey( - 'BPFLoader1111111111111111111111111111111111', - ); - const publicKey = new PublicKey( - 'SeedPubey1111111111111111111111111111111111', - ); - - let programAddress = await PublicKey.createProgramAddress( - [Buffer.from('', 'utf8'), Buffer.from([1])], - programId, - ); - expect( - programAddress.equals( - new PublicKey('3gF2KMe9KiC6FNVBmfg9i267aMPvK37FewCip4eGBFcT'), - ), - ).toBe(true); - - programAddress = await PublicKey.createProgramAddress( - [Buffer.from('☉', 'utf8')], - programId, - ); - expect( - programAddress.equals( - new PublicKey('7ytmC1nT1xY4RfxCV2ZgyA7UakC93do5ZdyhdF3EtPj7'), - ), - ).toBe(true); - - programAddress = await PublicKey.createProgramAddress( - [Buffer.from('Talking', 'utf8'), Buffer.from('Squirrels', 'utf8')], - programId, - ); - expect( - programAddress.equals( - new PublicKey('HwRVBufQ4haG5XSgpspwKtNd3PC9GM9m1196uJW36vds'), - ), - ).toBe(true); - - programAddress = await PublicKey.createProgramAddress( - [publicKey.toBuffer()], - programId, - ); - expect( - programAddress.equals( - new PublicKey('GUs5qLUfsEHkcMB9T38vjr18ypEhRuNWiePW2LoK4E3K'), - ), - ).toBe(true); - - const programAddress2 = await PublicKey.createProgramAddress( - [Buffer.from('Talking', 'utf8')], - programId, - ); - expect(programAddress.equals(programAddress2)).toBe(false); - - await expect( - PublicKey.createProgramAddress( - [Buffer.alloc(MAX_SEED_LENGTH + 1)], + let programAddress = await PublicKey.createProgramAddress( + [Buffer.from('', 'utf8'), Buffer.from([1])], programId, - ), - ).rejects.toThrow('Max seed length exceeded'); - - // https://github.com/solana-labs/solana/issues/11950 - { - let seeds = [ - new PublicKey('H4snTKK9adiU15gP22ErfZYtro3aqR9BTMXiH3AwiUTQ').toBuffer(), - new BN(2).toArrayLike(Buffer, 'le', 8), - ]; - let programId = new PublicKey( - '4ckmDgGdxQoPDLUkDT3vHgSAkzA3QRdNq5ywwY4sUSJn', ); - programAddress = await PublicKey.createProgramAddress(seeds, programId); expect( programAddress.equals( - new PublicKey('12rqwuEgBYiGhBrDJStCiqEtzQpTTiZbh7teNVLuYcFA'), + new PublicKey('3gF2KMe9KiC6FNVBmfg9i267aMPvK37FewCip4eGBFcT'), ), - ).toBe(true); - } -}); + ).to.be.true; -test('findProgramAddress', async () => { - const programId = new PublicKey( - 'BPFLoader1111111111111111111111111111111111', - ); - let [programAddress, nonce] = await PublicKey.findProgramAddress( - [Buffer.from('', 'utf8')], - programId, - ); - expect( - programAddress.equals( - await PublicKey.createProgramAddress( - [Buffer.from('', 'utf8'), Buffer.from([nonce])], + programAddress = await PublicKey.createProgramAddress( + [Buffer.from('☉', 'utf8')], + programId, + ); + expect( + programAddress.equals( + new PublicKey('7ytmC1nT1xY4RfxCV2ZgyA7UakC93do5ZdyhdF3EtPj7'), + ), + ).to.be.true; + + programAddress = await PublicKey.createProgramAddress( + [Buffer.from('Talking', 'utf8'), Buffer.from('Squirrels', 'utf8')], + programId, + ); + expect( + programAddress.equals( + new PublicKey('HwRVBufQ4haG5XSgpspwKtNd3PC9GM9m1196uJW36vds'), + ), + ).to.be.true; + + programAddress = await PublicKey.createProgramAddress( + [publicKey.toBuffer()], + programId, + ); + expect( + programAddress.equals( + new PublicKey('GUs5qLUfsEHkcMB9T38vjr18ypEhRuNWiePW2LoK4E3K'), + ), + ).to.be.true; + + const programAddress2 = await PublicKey.createProgramAddress( + [Buffer.from('Talking', 'utf8')], + programId, + ); + expect(programAddress.equals(programAddress2)).to.eq(false); + + await expect( + PublicKey.createProgramAddress( + [Buffer.alloc(MAX_SEED_LENGTH + 1)], programId, ), - ), - ).toBe(true); + ).to.be.rejectedWith('Max seed length exceeded'); + + // https://github.com/solana-labs/solana/issues/11950 + { + let seeds = [ + new PublicKey( + 'H4snTKK9adiU15gP22ErfZYtro3aqR9BTMXiH3AwiUTQ', + ).toBuffer(), + new BN(2).toArrayLike(Buffer, 'le', 8), + ]; + let programId = new PublicKey( + '4ckmDgGdxQoPDLUkDT3vHgSAkzA3QRdNq5ywwY4sUSJn', + ); + programAddress = await PublicKey.createProgramAddress(seeds, programId); + expect( + programAddress.equals( + new PublicKey('12rqwuEgBYiGhBrDJStCiqEtzQpTTiZbh7teNVLuYcFA'), + ), + ).to.be.true; + } + }); + + it('findProgramAddress', async () => { + const programId = new PublicKey( + 'BPFLoader1111111111111111111111111111111111', + ); + let [programAddress, nonce] = await PublicKey.findProgramAddress( + [Buffer.from('', 'utf8')], + programId, + ); + expect( + programAddress.equals( + await PublicKey.createProgramAddress( + [Buffer.from('', 'utf8'), Buffer.from([nonce])], + programId, + ), + ), + ).to.be.true; + }); }); diff --git a/web3.js/test/rollup.config.js b/web3.js/test/rollup.config.js new file mode 100644 index 0000000000..1596e60586 --- /dev/null +++ b/web3.js/test/rollup.config.js @@ -0,0 +1,76 @@ +import alias from '@rollup/plugin-alias'; +import babel from '@rollup/plugin-babel'; +import commonjs from '@rollup/plugin-commonjs'; +import flowRemoveTypes from 'flow-remove-types'; +import json from '@rollup/plugin-json'; +import multi from '@rollup/plugin-multi-entry'; +import nodeResolve from '@rollup/plugin-node-resolve'; +import nodePolyfills from 'rollup-plugin-node-polyfills'; +import replace from '@rollup/plugin-replace'; + +export default { + input: { + // include: [ + // 'test/account.test.js', + // 'test/cluster.test.js', + // 'test/stake-program.test.js', + // ], + include: ['test/**/*.test.js'], + exclude: ['test/agent-manager.test.js', 'test/bpf-loader.test.js'], + }, + external: ['node-forge', 'http2', '_stream_wrap'], + output: { + file: 'test/dist/bundle.js', + format: 'es', + sourcemap: true, + }, + plugins: [ + flow(), + multi(), + commonjs(), + nodeResolve({ + browser: true, + preferBuiltins: false, + dedupe: ['bn.js', 'buffer'], + }), + babel({ + exclude: '**/node_modules/**', + babelHelpers: 'runtime', + plugins: ['@babel/plugin-transform-runtime'], + }), + nodePolyfills(), + replace({ + 'process.env.BROWSER': 'true', + 'process.env.TEST_LIVE': 'true', + }), + alias({ + entries: [ + { + find: /^\.\.\/src\/.*\.js$/, + replacement: './lib/index.browser.esm.js', + }, + ], + }), + json(), + ], + onwarn: function (warning, rollupWarn) { + if (warning.code !== 'CIRCULAR_DEPENDENCY' && warning.code !== 'EVAL') { + rollupWarn(warning); + } + }, + treeshake: { + moduleSideEffects: path => path.endsWith('test.js'), + }, +}; + +// Using this instead of rollup-plugin-flow due to +// https://github.com/leebyron/rollup-plugin-flow/issues/5 +function flow() { + return { + name: 'flow-remove-types', + transform: code => ({ + code: flowRemoveTypes(code).toString(), + map: null, + }), + }; +} diff --git a/web3.js/test/secp256k1-program.test.js b/web3.js/test/secp256k1-program.test.js index d76e98206f..fa06171fa3 100644 --- a/web3.js/test/secp256k1-program.test.js +++ b/web3.js/test/secp256k1-program.test.js @@ -1,88 +1,82 @@ // @flow -import {keccak_256} from 'js-sha3'; -import secp256k1 from 'secp256k1'; -import {randomBytes} from 'crypto'; +import {Buffer} from 'buffer'; +import createKeccakHash from 'keccak'; +import {privateKeyVerify, ecdsaSign, publicKeyCreate} from 'secp256k1'; -import {Secp256k1Program} from '../src/secp256k1-program'; -import {mockRpcEnabled} from './__mocks__/node-fetch'; -import {url} from './url'; import { Connection, Account, sendAndConfirmTransaction, LAMPORTS_PER_SOL, Transaction, + Secp256k1Program, } from '../src'; +import {url} from './url'; +import {helpers} from './mocks/rpc-http'; -const {privateKeyVerify, ecdsaSign, publicKeyCreate} = secp256k1; +const randomPrivateKey = () => { + let privateKey; + do { + privateKey = new Account().secretKey.slice(0, 32); + } while (!privateKeyVerify(privateKey)); + return privateKey; +}; -if (!mockRpcEnabled) { - jest.setTimeout(20000); +if (process.env.TEST_LIVE) { + describe('secp256k1', () => { + it('create secp256k1 instruction with public key', async () => { + const privateKey = randomPrivateKey(); + const publicKey = publicKeyCreate(privateKey, false); + const message = Buffer.from('This is a message'); + const messageHash = createKeccakHash('keccak256') + .update(message) + .digest(); + const {signature, recid: recoveryId} = ecdsaSign(messageHash, privateKey); + const connection = new Connection(url, 'singleGossip'); + + const from = new Account(); + await connection.confirmTransaction( + await connection.requestAirdrop(from.publicKey, 2 * LAMPORTS_PER_SOL), + 'singleGossip', + ); + + const transaction = new Transaction().add( + Secp256k1Program.createInstructionWithPublicKey({ + publicKey, + message, + signature, + recoveryId, + }), + ); + + await sendAndConfirmTransaction(connection, transaction, [from], { + commitment: 'singleGossip', + preflightCommitment: 'singleGossip', + }); + }); + + it('create secp256k1 instruction with private key', async () => { + const privateKey = randomPrivateKey(); + const connection = new Connection(url, 'singleGossip'); + + const from = new Account(); + await connection.confirmTransaction( + await connection.requestAirdrop(from.publicKey, 2 * LAMPORTS_PER_SOL), + 'singleGossip', + ); + + const transaction = new Transaction().add( + Secp256k1Program.createInstructionWithPrivateKey({ + privateKey, + message: Buffer.from('Test 123'), + }), + ); + + await sendAndConfirmTransaction(connection, transaction, [from], { + commitment: 'singleGossip', + preflightCommitment: 'singleGossip', + }); + }); + }); } - -test('live create secp256k1 instruction with public key', async () => { - if (mockRpcEnabled) { - console.log('non-live test skipped'); - return; - } - - const message = Buffer.from('This is a message'); - - let privateKey; - do { - privateKey = randomBytes(32); - } while (!privateKeyVerify(privateKey)); - - const publicKey = publicKeyCreate(privateKey, false); - const messageHash = Buffer.from(keccak_256.update(message).digest()); - const {signature, recid: recoveryId} = ecdsaSign(messageHash, privateKey); - - const instruction = Secp256k1Program.createInstructionWithPublicKey({ - publicKey, - message, - signature, - recoveryId, - }); - - const transaction = new Transaction(); - transaction.add(instruction); - - const connection = new Connection(url, 'recent'); - const from = new Account(); - await connection.requestAirdrop(from.publicKey, 2 * LAMPORTS_PER_SOL); - - await sendAndConfirmTransaction(connection, transaction, [from], { - commitment: 'single', - skipPreflight: true, - }); -}); - -test('live create secp256k1 instruction with private key', async () => { - if (mockRpcEnabled) { - console.log('non-live test skipped'); - return; - } - - let privateKey; - do { - privateKey = randomBytes(32); - } while (!privateKeyVerify(privateKey)); - - const instruction = Secp256k1Program.createInstructionWithPrivateKey({ - privateKey, - message: Buffer.from('Test 123'), - }); - - const transaction = new Transaction(); - transaction.add(instruction); - - const connection = new Connection(url, 'recent'); - const from = new Account(); - await connection.requestAirdrop(from.publicKey, 2 * LAMPORTS_PER_SOL); - - await sendAndConfirmTransaction(connection, transaction, [from], { - commitment: 'single', - skipPreflight: true, - }); -}); diff --git a/web3.js/test/shortvec-encoding.test.js b/web3.js/test/shortvec-encoding.test.js index e3b2fa980c..07abcc8886 100644 --- a/web3.js/test/shortvec-encoding.test.js +++ b/web3.js/test/shortvec-encoding.test.js @@ -1,38 +1,13 @@ // @flow +import {expect} from 'chai'; import {decodeLength, encodeLength} from '../src/util/shortvec-encoding'; function checkDecodedArray(array: Array, expectedValue: number) { - expect(decodeLength(array)).toEqual(expectedValue); - expect(array.length).toEqual(0); + expect(decodeLength(array)).to.eq(expectedValue); + expect(array).to.have.length(0); } -test('shortvec decodeLength', () => { - let array = []; - checkDecodedArray(array, 0); - - array = [5]; - checkDecodedArray(array, 5); - - array = [0x7f]; - checkDecodedArray(array, 0x7f); - - array = [0x80, 0x01]; - checkDecodedArray(array, 0x80); - - array = [0xff, 0x01]; - checkDecodedArray(array, 0xff); - - array = [0x80, 0x02]; - checkDecodedArray(array, 0x100); - - array = [0xff, 0xff, 0x01]; - checkDecodedArray(array, 0x7fff); - - array = [0x80, 0x80, 0x80, 0x01]; - checkDecodedArray(array, 0x200000); -}); - function checkEncodedArray( array: Array, len: number, @@ -41,31 +16,59 @@ function checkEncodedArray( expectedArray: Array, ) { encodeLength(array, len); - expect(array.length).toEqual(prevLength); - expect(array.slice(-addedLength)).toEqual(expectedArray); + expect(array).to.have.length(prevLength); + expect(array.slice(-addedLength)).to.eql(expectedArray); } -test('shortvec encodeLength', () => { - let array = []; - let prevLength = 1; - checkEncodedArray(array, 0, prevLength, 1, [0]); +describe('shortvec', () => { + it('decodeLength', () => { + let array = []; + checkDecodedArray(array, 0); - checkEncodedArray(array, 5, (prevLength += 1), 1, [5]); + array = [5]; + checkDecodedArray(array, 5); - checkEncodedArray(array, 0x7f, (prevLength += 1), 1, [0x7f]); + array = [0x7f]; + checkDecodedArray(array, 0x7f); - checkEncodedArray(array, 0x80, (prevLength += 2), 2, [0x80, 0x01]); + array = [0x80, 0x01]; + checkDecodedArray(array, 0x80); - checkEncodedArray(array, 0xff, (prevLength += 2), 2, [0xff, 0x01]); + array = [0xff, 0x01]; + checkDecodedArray(array, 0xff); - checkEncodedArray(array, 0x100, (prevLength += 2), 2, [0x80, 0x02]); + array = [0x80, 0x02]; + checkDecodedArray(array, 0x100); - checkEncodedArray(array, 0x7fff, (prevLength += 3), 3, [0xff, 0xff, 0x01]); + array = [0xff, 0xff, 0x01]; + checkDecodedArray(array, 0x7fff); - prevLength = checkEncodedArray(array, 0x200000, (prevLength += 4), 4, [ - 0x80, - 0x80, - 0x80, - 0x01, - ]); + array = [0x80, 0x80, 0x80, 0x01]; + checkDecodedArray(array, 0x200000); + }); + + it('encodeLength', () => { + let array = []; + let prevLength = 1; + checkEncodedArray(array, 0, prevLength, 1, [0]); + + checkEncodedArray(array, 5, (prevLength += 1), 1, [5]); + + checkEncodedArray(array, 0x7f, (prevLength += 1), 1, [0x7f]); + + checkEncodedArray(array, 0x80, (prevLength += 2), 2, [0x80, 0x01]); + + checkEncodedArray(array, 0xff, (prevLength += 2), 2, [0xff, 0x01]); + + checkEncodedArray(array, 0x100, (prevLength += 2), 2, [0x80, 0x02]); + + checkEncodedArray(array, 0x7fff, (prevLength += 3), 3, [0xff, 0xff, 0x01]); + + prevLength = checkEncodedArray(array, 0x200000, (prevLength += 4), 4, [ + 0x80, + 0x80, + 0x80, + 0x01, + ]); + }); }); diff --git a/web3.js/test/stake-program.test.js b/web3.js/test/stake-program.test.js index 5f61d41bc7..db6de1ec23 100644 --- a/web3.js/test/stake-program.test.js +++ b/web3.js/test/stake-program.test.js @@ -1,5 +1,8 @@ // @flow +import {expect, use} from 'chai'; +import chaiAsPromised from 'chai-as-promised'; + import { Account, Authorized, @@ -14,523 +17,547 @@ import { SystemInstruction, Transaction, } from '../src'; -import {mockRpcEnabled} from './__mocks__/node-fetch'; -import {newAccountWithLamports} from './new-account-with-lamports'; +import {helpers} from './mocks/rpc-http'; import {url} from './url'; -if (!mockRpcEnabled) { - // Testing max commitment level takes around 20s to complete - jest.setTimeout(60000); -} +use(chaiAsPromised); -test('createAccountWithSeed', async () => { - const fromPubkey = new Account().publicKey; - const seed = 'test string'; - const newAccountPubkey = await PublicKey.createWithSeed( - fromPubkey, - seed, - StakeProgram.programId, - ); - const authorizedPubkey = new Account().publicKey; - const authorized = new Authorized(authorizedPubkey, authorizedPubkey); - const lockup = new Lockup(0, 0, fromPubkey); - const lamports = 123; - const transaction = StakeProgram.createAccountWithSeed({ - fromPubkey, - stakePubkey: newAccountPubkey, - basePubkey: fromPubkey, - seed, - authorized, - lockup, - lamports, +describe('StakeProgram', () => { + it('createAccountWithSeed', async () => { + const fromPubkey = new Account().publicKey; + const seed = 'test string'; + const newAccountPubkey = await PublicKey.createWithSeed( + fromPubkey, + seed, + StakeProgram.programId, + ); + const authorizedPubkey = new Account().publicKey; + const authorized = new Authorized(authorizedPubkey, authorizedPubkey); + const lockup = new Lockup(0, 0, fromPubkey); + const lamports = 123; + const transaction = StakeProgram.createAccountWithSeed({ + fromPubkey, + stakePubkey: newAccountPubkey, + basePubkey: fromPubkey, + seed, + authorized, + lockup, + lamports, + }); + expect(transaction.instructions).to.have.length(2); + const [systemInstruction, stakeInstruction] = transaction.instructions; + const systemParams = { + fromPubkey, + newAccountPubkey, + basePubkey: fromPubkey, + seed, + lamports, + space: StakeProgram.space, + programId: StakeProgram.programId, + }; + expect(systemParams).to.eql( + SystemInstruction.decodeCreateWithSeed(systemInstruction), + ); + const initParams = {stakePubkey: newAccountPubkey, authorized, lockup}; + expect(initParams).to.eql( + StakeInstruction.decodeInitialize(stakeInstruction), + ); }); - expect(transaction.instructions).toHaveLength(2); - const [systemInstruction, stakeInstruction] = transaction.instructions; - const systemParams = { - fromPubkey, - newAccountPubkey, - basePubkey: fromPubkey, - seed, - lamports, - space: StakeProgram.space, - programId: StakeProgram.programId, - }; - expect(systemParams).toEqual( - SystemInstruction.decodeCreateWithSeed(systemInstruction), - ); - const initParams = {stakePubkey: newAccountPubkey, authorized, lockup}; - expect(initParams).toEqual( - StakeInstruction.decodeInitialize(stakeInstruction), - ); -}); -test('createAccount', () => { - const fromPubkey = new Account().publicKey; - const newAccountPubkey = new Account().publicKey; - const authorizedPubkey = new Account().publicKey; - const authorized = new Authorized(authorizedPubkey, authorizedPubkey); - const lockup = new Lockup(0, 0, fromPubkey); - const lamports = 123; - const transaction = StakeProgram.createAccount({ - fromPubkey, - stakePubkey: newAccountPubkey, - authorized, - lockup, - lamports, + it('createAccount', () => { + const fromPubkey = new Account().publicKey; + const newAccountPubkey = new Account().publicKey; + const authorizedPubkey = new Account().publicKey; + const authorized = new Authorized(authorizedPubkey, authorizedPubkey); + const lockup = new Lockup(0, 0, fromPubkey); + const lamports = 123; + const transaction = StakeProgram.createAccount({ + fromPubkey, + stakePubkey: newAccountPubkey, + authorized, + lockup, + lamports, + }); + expect(transaction.instructions).to.have.length(2); + const [systemInstruction, stakeInstruction] = transaction.instructions; + const systemParams = { + fromPubkey, + newAccountPubkey, + lamports, + space: StakeProgram.space, + programId: StakeProgram.programId, + }; + expect(systemParams).to.eql( + SystemInstruction.decodeCreateAccount(systemInstruction), + ); + + const initParams = {stakePubkey: newAccountPubkey, authorized, lockup}; + expect(initParams).to.eql( + StakeInstruction.decodeInitialize(stakeInstruction), + ); }); - expect(transaction.instructions).toHaveLength(2); - const [systemInstruction, stakeInstruction] = transaction.instructions; - const systemParams = { - fromPubkey, - newAccountPubkey, - lamports, - space: StakeProgram.space, - programId: StakeProgram.programId, - }; - expect(systemParams).toEqual( - SystemInstruction.decodeCreateAccount(systemInstruction), - ); - const initParams = {stakePubkey: newAccountPubkey, authorized, lockup}; - expect(initParams).toEqual( - StakeInstruction.decodeInitialize(stakeInstruction), - ); -}); - -test('delegate', () => { - const stakePubkey = new Account().publicKey; - const authorizedPubkey = new Account().publicKey; - const votePubkey = new Account().publicKey; - const params = { - stakePubkey, - authorizedPubkey, - votePubkey, - }; - const transaction = StakeProgram.delegate(params); - expect(transaction.instructions).toHaveLength(1); - const [stakeInstruction] = transaction.instructions; - expect(params).toEqual(StakeInstruction.decodeDelegate(stakeInstruction)); -}); - -test('authorize', () => { - const stakePubkey = new Account().publicKey; - const authorizedPubkey = new Account().publicKey; - const newAuthorizedPubkey = new Account().publicKey; - const stakeAuthorizationType = StakeAuthorizationLayout.Staker; - const params = { - stakePubkey, - authorizedPubkey, - newAuthorizedPubkey, - stakeAuthorizationType, - }; - const transaction = StakeProgram.authorize(params); - expect(transaction.instructions).toHaveLength(1); - const [stakeInstruction] = transaction.instructions; - expect(params).toEqual(StakeInstruction.decodeAuthorize(stakeInstruction)); -}); - -test('authorize with custodian', () => { - const stakePubkey = new Account().publicKey; - const authorizedPubkey = new Account().publicKey; - const newAuthorizedPubkey = new Account().publicKey; - const stakeAuthorizationType = StakeAuthorizationLayout.Withdrawer; - const custodianPubkey = new Account().publicKey; - const params = { - stakePubkey, - authorizedPubkey, - newAuthorizedPubkey, - stakeAuthorizationType, - custodianPubkey, - }; - const transaction = StakeProgram.authorize(params); - expect(transaction.instructions).toHaveLength(1); - const [stakeInstruction] = transaction.instructions; - expect(params).toEqual(StakeInstruction.decodeAuthorize(stakeInstruction)); -}); - -test('authorizeWithSeed', () => { - const stakePubkey = new Account().publicKey; - const authorityBase = new Account().publicKey; - const authoritySeed = 'test string'; - const authorityOwner = new Account().publicKey; - const newAuthorizedPubkey = new Account().publicKey; - const stakeAuthorizationType = StakeAuthorizationLayout.Staker; - const params = { - stakePubkey, - authorityBase, - authoritySeed, - authorityOwner, - newAuthorizedPubkey, - stakeAuthorizationType, - }; - const transaction = StakeProgram.authorizeWithSeed(params); - expect(transaction.instructions).toHaveLength(1); - const [stakeInstruction] = transaction.instructions; - expect(params).toEqual( - StakeInstruction.decodeAuthorizeWithSeed(stakeInstruction), - ); -}); - -test('authorizeWithSeed with custodian', () => { - const stakePubkey = new Account().publicKey; - const authorityBase = new Account().publicKey; - const authoritySeed = 'test string'; - const authorityOwner = new Account().publicKey; - const newAuthorizedPubkey = new Account().publicKey; - const stakeAuthorizationType = StakeAuthorizationLayout.Staker; - const custodianPubkey = new Account().publicKey; - const params = { - stakePubkey, - authorityBase, - authoritySeed, - authorityOwner, - newAuthorizedPubkey, - stakeAuthorizationType, - custodianPubkey, - }; - const transaction = StakeProgram.authorizeWithSeed(params); - expect(transaction.instructions).toHaveLength(1); - const [stakeInstruction] = transaction.instructions; - expect(params).toEqual( - StakeInstruction.decodeAuthorizeWithSeed(stakeInstruction), - ); -}); - -test('split', () => { - const stakePubkey = new Account().publicKey; - const authorizedPubkey = new Account().publicKey; - const splitStakePubkey = new Account().publicKey; - const params = { - stakePubkey, - authorizedPubkey, - splitStakePubkey, - lamports: 123, - }; - const transaction = StakeProgram.split(params); - expect(transaction.instructions).toHaveLength(2); - const [systemInstruction, stakeInstruction] = transaction.instructions; - const systemParams = { - fromPubkey: authorizedPubkey, - newAccountPubkey: splitStakePubkey, - lamports: 0, - space: StakeProgram.space, - programId: StakeProgram.programId, - }; - expect(systemParams).toEqual( - SystemInstruction.decodeCreateAccount(systemInstruction), - ); - expect(params).toEqual(StakeInstruction.decodeSplit(stakeInstruction)); -}); - -test('withdraw', () => { - const stakePubkey = new Account().publicKey; - const authorizedPubkey = new Account().publicKey; - const toPubkey = new Account().publicKey; - const params = { - stakePubkey, - authorizedPubkey, - toPubkey, - lamports: 123, - }; - const transaction = StakeProgram.withdraw(params); - expect(transaction.instructions).toHaveLength(1); - const [stakeInstruction] = transaction.instructions; - expect(params).toEqual(StakeInstruction.decodeWithdraw(stakeInstruction)); -}); - -test('withdraw with custodian', () => { - const stakePubkey = new Account().publicKey; - const authorizedPubkey = new Account().publicKey; - const toPubkey = new Account().publicKey; - const custodianPubkey = new Account().publicKey; - const params = { - stakePubkey, - authorizedPubkey, - toPubkey, - lamports: 123, - custodianPubkey, - }; - const transaction = StakeProgram.withdraw(params); - expect(transaction.instructions).toHaveLength(1); - const [stakeInstruction] = transaction.instructions; - expect(params).toEqual(StakeInstruction.decodeWithdraw(stakeInstruction)); -}); - -test('deactivate', () => { - const stakePubkey = new Account().publicKey; - const authorizedPubkey = new Account().publicKey; - const params = {stakePubkey, authorizedPubkey}; - const transaction = StakeProgram.deactivate(params); - expect(transaction.instructions).toHaveLength(1); - const [stakeInstruction] = transaction.instructions; - expect(params).toEqual(StakeInstruction.decodeDeactivate(stakeInstruction)); -}); - -test('StakeInstructions', async () => { - const from = new Account(); - const seed = 'test string'; - const newAccountPubkey = await PublicKey.createWithSeed( - from.publicKey, - seed, - StakeProgram.programId, - ); - const authorized = new Account(); - const amount = 123; - const recentBlockhash = 'EETubP5AKHgjPAhzPAFcb8BAY1hMH639CWCFTqi3hq1k'; // Arbitrary known recentBlockhash - const createWithSeed = StakeProgram.createAccountWithSeed({ - fromPubkey: from.publicKey, - stakePubkey: newAccountPubkey, - basePubkey: from.publicKey, - seed, - authorized: new Authorized(authorized.publicKey, authorized.publicKey), - lockup: new Lockup(0, 0, from.publicKey), - lamports: amount, + it('delegate', () => { + const stakePubkey = new Account().publicKey; + const authorizedPubkey = new Account().publicKey; + const votePubkey = new Account().publicKey; + const params = { + stakePubkey, + authorizedPubkey, + votePubkey, + }; + const transaction = StakeProgram.delegate(params); + expect(transaction.instructions).to.have.length(1); + const [stakeInstruction] = transaction.instructions; + expect(params).to.eql(StakeInstruction.decodeDelegate(stakeInstruction)); }); - const createWithSeedTransaction = new Transaction({recentBlockhash}).add( - createWithSeed, - ); - expect(createWithSeedTransaction.instructions).toHaveLength(2); - const systemInstructionType = SystemInstruction.decodeInstructionType( - createWithSeedTransaction.instructions[0], - ); - expect(systemInstructionType).toEqual('CreateWithSeed'); + it('authorize', () => { + const stakePubkey = new Account().publicKey; + const authorizedPubkey = new Account().publicKey; + const newAuthorizedPubkey = new Account().publicKey; + const stakeAuthorizationType = StakeAuthorizationLayout.Staker; + const params = { + stakePubkey, + authorizedPubkey, + newAuthorizedPubkey, + stakeAuthorizationType, + }; + const transaction = StakeProgram.authorize(params); + expect(transaction.instructions).to.have.length(1); + const [stakeInstruction] = transaction.instructions; + expect(params).to.eql(StakeInstruction.decodeAuthorize(stakeInstruction)); + }); - const stakeInstructionType = StakeInstruction.decodeInstructionType( - createWithSeedTransaction.instructions[1], - ); - expect(stakeInstructionType).toEqual('Initialize'); + it('authorize with custodian', () => { + const stakePubkey = new Account().publicKey; + const authorizedPubkey = new Account().publicKey; + const newAuthorizedPubkey = new Account().publicKey; + const stakeAuthorizationType = StakeAuthorizationLayout.Withdrawer; + const custodianPubkey = new Account().publicKey; + const params = { + stakePubkey, + authorizedPubkey, + newAuthorizedPubkey, + stakeAuthorizationType, + custodianPubkey, + }; + const transaction = StakeProgram.authorize(params); + expect(transaction.instructions).to.have.length(1); + const [stakeInstruction] = transaction.instructions; + expect(params).to.eql(StakeInstruction.decodeAuthorize(stakeInstruction)); + }); - expect(() => { - StakeInstruction.decodeInstructionType( + it('authorizeWithSeed', () => { + const stakePubkey = new Account().publicKey; + const authorityBase = new Account().publicKey; + const authoritySeed = 'test string'; + const authorityOwner = new Account().publicKey; + const newAuthorizedPubkey = new Account().publicKey; + const stakeAuthorizationType = StakeAuthorizationLayout.Staker; + const params = { + stakePubkey, + authorityBase, + authoritySeed, + authorityOwner, + newAuthorizedPubkey, + stakeAuthorizationType, + }; + const transaction = StakeProgram.authorizeWithSeed(params); + expect(transaction.instructions).to.have.length(1); + const [stakeInstruction] = transaction.instructions; + expect(params).to.eql( + StakeInstruction.decodeAuthorizeWithSeed(stakeInstruction), + ); + }); + + it('authorizeWithSeed with custodian', () => { + const stakePubkey = new Account().publicKey; + const authorityBase = new Account().publicKey; + const authoritySeed = 'test string'; + const authorityOwner = new Account().publicKey; + const newAuthorizedPubkey = new Account().publicKey; + const stakeAuthorizationType = StakeAuthorizationLayout.Staker; + const custodianPubkey = new Account().publicKey; + const params = { + stakePubkey, + authorityBase, + authoritySeed, + authorityOwner, + newAuthorizedPubkey, + stakeAuthorizationType, + custodianPubkey, + }; + const transaction = StakeProgram.authorizeWithSeed(params); + expect(transaction.instructions).to.have.length(1); + const [stakeInstruction] = transaction.instructions; + expect(params).to.eql( + StakeInstruction.decodeAuthorizeWithSeed(stakeInstruction), + ); + }); + + it('split', () => { + const stakePubkey = new Account().publicKey; + const authorizedPubkey = new Account().publicKey; + const splitStakePubkey = new Account().publicKey; + const params = { + stakePubkey, + authorizedPubkey, + splitStakePubkey, + lamports: 123, + }; + const transaction = StakeProgram.split(params); + expect(transaction.instructions).to.have.length(2); + const [systemInstruction, stakeInstruction] = transaction.instructions; + const systemParams = { + fromPubkey: authorizedPubkey, + newAccountPubkey: splitStakePubkey, + lamports: 0, + space: StakeProgram.space, + programId: StakeProgram.programId, + }; + expect(systemParams).to.eql( + SystemInstruction.decodeCreateAccount(systemInstruction), + ); + expect(params).to.eql(StakeInstruction.decodeSplit(stakeInstruction)); + }); + + it('withdraw', () => { + const stakePubkey = new Account().publicKey; + const authorizedPubkey = new Account().publicKey; + const toPubkey = new Account().publicKey; + const params = { + stakePubkey, + authorizedPubkey, + toPubkey, + lamports: 123, + }; + const transaction = StakeProgram.withdraw(params); + expect(transaction.instructions).to.have.length(1); + const [stakeInstruction] = transaction.instructions; + expect(params).to.eql(StakeInstruction.decodeWithdraw(stakeInstruction)); + }); + + it('withdraw with custodian', () => { + const stakePubkey = new Account().publicKey; + const authorizedPubkey = new Account().publicKey; + const toPubkey = new Account().publicKey; + const custodianPubkey = new Account().publicKey; + const params = { + stakePubkey, + authorizedPubkey, + toPubkey, + lamports: 123, + custodianPubkey, + }; + const transaction = StakeProgram.withdraw(params); + expect(transaction.instructions).to.have.length(1); + const [stakeInstruction] = transaction.instructions; + expect(params).to.eql(StakeInstruction.decodeWithdraw(stakeInstruction)); + }); + + it('deactivate', () => { + const stakePubkey = new Account().publicKey; + const authorizedPubkey = new Account().publicKey; + const params = {stakePubkey, authorizedPubkey}; + const transaction = StakeProgram.deactivate(params); + expect(transaction.instructions).to.have.length(1); + const [stakeInstruction] = transaction.instructions; + expect(params).to.eql(StakeInstruction.decodeDeactivate(stakeInstruction)); + }); + + it('StakeInstructions', async () => { + const from = new Account(); + const seed = 'test string'; + const newAccountPubkey = await PublicKey.createWithSeed( + from.publicKey, + seed, + StakeProgram.programId, + ); + const authorized = new Account(); + const amount = 123; + const recentBlockhash = 'EETubP5AKHgjPAhzPAFcb8BAY1hMH639CWCFTqi3hq1k'; // Arbitrary known recentBlockhash + const createWithSeed = StakeProgram.createAccountWithSeed({ + fromPubkey: from.publicKey, + stakePubkey: newAccountPubkey, + basePubkey: from.publicKey, + seed, + authorized: new Authorized(authorized.publicKey, authorized.publicKey), + lockup: new Lockup(0, 0, from.publicKey), + lamports: amount, + }); + const createWithSeedTransaction = new Transaction({recentBlockhash}).add( + createWithSeed, + ); + + expect(createWithSeedTransaction.instructions).to.have.length(2); + const systemInstructionType = SystemInstruction.decodeInstructionType( createWithSeedTransaction.instructions[0], ); - }).toThrow(); + expect(systemInstructionType).to.eq('CreateWithSeed'); - const stake = new Account(); - const vote = new Account(); - const delegate = StakeProgram.delegate({ - stakePubkey: stake.publicKey, - authorizedPubkey: authorized.publicKey, - votePubkey: vote.publicKey, - }); - - const delegateTransaction = new Transaction({recentBlockhash}).add(delegate); - const anotherStakeInstructionType = StakeInstruction.decodeInstructionType( - delegateTransaction.instructions[0], - ); - expect(anotherStakeInstructionType).toEqual('Delegate'); -}); - -test('live staking actions', async () => { - if (mockRpcEnabled) { - console.log('non-live test skipped'); - return; - } - - const connection = new Connection(url, 'singleGossip'); - const voteAccounts = await connection.getVoteAccounts(); - const voteAccount = voteAccounts.current.concat(voteAccounts.delinquent)[0]; - const votePubkey = new PublicKey(voteAccount.votePubkey); - - const from = await newAccountWithLamports(connection, 2 * LAMPORTS_PER_SOL); - const authorized = await newAccountWithLamports( - connection, - 2 * LAMPORTS_PER_SOL, - ); - - const minimumAmount = await connection.getMinimumBalanceForRentExemption( - StakeProgram.space, - ); - - expect(await connection.getBalance(from.publicKey)).toEqual( - 2 * LAMPORTS_PER_SOL, - ); - expect(await connection.getBalance(authorized.publicKey)).toEqual( - 2 * LAMPORTS_PER_SOL, - ); - - { - // Create Stake account without seed - const newStakeAccount = new Account(); - let createAndInitialize = StakeProgram.createAccount({ - fromPubkey: from.publicKey, - stakePubkey: newStakeAccount.publicKey, - authorized: new Authorized(authorized.publicKey, authorized.publicKey), - lockup: new Lockup(0, 0, new PublicKey(0)), - lamports: minimumAmount + 42, - }); - - await sendAndConfirmTransaction( - connection, - createAndInitialize, - [from, newStakeAccount], - {commitment: 'singleGossip'}, - ); - expect(await connection.getBalance(newStakeAccount.publicKey)).toEqual( - minimumAmount + 42, + const stakeInstructionType = StakeInstruction.decodeInstructionType( + createWithSeedTransaction.instructions[1], ); + expect(stakeInstructionType).to.eq('Initialize'); - let delegation = StakeProgram.delegate({ - stakePubkey: newStakeAccount.publicKey, + expect(() => { + StakeInstruction.decodeInstructionType( + createWithSeedTransaction.instructions[0], + ); + }).to.throw(); + + const stake = new Account(); + const vote = new Account(); + const delegate = StakeProgram.delegate({ + stakePubkey: stake.publicKey, authorizedPubkey: authorized.publicKey, - votePubkey, + votePubkey: vote.publicKey, }); - await sendAndConfirmTransaction(connection, delegation, [authorized], { - commitment: 'singleGossip', - }); - } - // Create Stake account with seed - const seed = 'test string'; - const newAccountPubkey = await PublicKey.createWithSeed( - from.publicKey, - seed, - StakeProgram.programId, - ); - - let createAndInitializeWithSeed = StakeProgram.createAccountWithSeed({ - fromPubkey: from.publicKey, - stakePubkey: newAccountPubkey, - basePubkey: from.publicKey, - seed, - authorized: new Authorized(authorized.publicKey, authorized.publicKey), - lockup: new Lockup(0, 0, new PublicKey(0)), - lamports: 3 * minimumAmount + 42, - }); - - await sendAndConfirmTransaction( - connection, - createAndInitializeWithSeed, - [from], - {commitment: 'singleGossip'}, - ); - let originalStakeBalance = await connection.getBalance(newAccountPubkey); - expect(originalStakeBalance).toEqual(3 * minimumAmount + 42); - - let delegation = StakeProgram.delegate({ - stakePubkey: newAccountPubkey, - authorizedPubkey: authorized.publicKey, - votePubkey, - }); - await sendAndConfirmTransaction(connection, delegation, [authorized], { - commitment: 'singleGossip', - }); - - // Test that withdraw fails before deactivation - const recipient = new Account(); - let withdraw = StakeProgram.withdraw({ - stakePubkey: newAccountPubkey, - authorizedPubkey: authorized.publicKey, - toPubkey: recipient.publicKey, - lamports: 1000, - }); - await expect( - sendAndConfirmTransaction(connection, withdraw, [authorized], { - commitment: 'singleGossip', - }), - ).rejects.toThrow(); - - // Deactivate stake - let deactivate = StakeProgram.deactivate({ - stakePubkey: newAccountPubkey, - authorizedPubkey: authorized.publicKey, - }); - await sendAndConfirmTransaction(connection, deactivate, [authorized], { - commitment: 'singleGossip', - }); - - let stakeActivationState; - do { - stakeActivationState = await connection.getStakeActivation( - newAccountPubkey, + const delegateTransaction = new Transaction({recentBlockhash}).add( + delegate, ); - } while (stakeActivationState.state != 'inactive'); - - // Test that withdraw succeeds after deactivation - withdraw = StakeProgram.withdraw({ - stakePubkey: newAccountPubkey, - authorizedPubkey: authorized.publicKey, - toPubkey: recipient.publicKey, - lamports: minimumAmount + 20, + const anotherStakeInstructionType = StakeInstruction.decodeInstructionType( + delegateTransaction.instructions[0], + ); + expect(anotherStakeInstructionType).to.eq('Delegate'); }); - await sendAndConfirmTransaction(connection, withdraw, [authorized], { - commitment: 'singleGossip', - }); - const recipientBalance = await connection.getBalance(recipient.publicKey); - expect(recipientBalance).toEqual(minimumAmount + 20); + if (process.env.TEST_LIVE) { + it('live staking actions', async () => { + const connection = new Connection(url, 'singleGossip'); - // Split stake - const newStake = new Account(); - let split = StakeProgram.split({ - stakePubkey: newAccountPubkey, - authorizedPubkey: authorized.publicKey, - splitStakePubkey: newStake.publicKey, - lamports: minimumAmount + 20, - }); - await sendAndConfirmTransaction(connection, split, [authorized, newStake], { - commitment: 'singleGossip', - }); - const balance = await connection.getBalance(newAccountPubkey); - expect(balance).toEqual(minimumAmount + 2); + const voteAccounts = await connection.getVoteAccounts(); + const voteAccount = voteAccounts.current.concat( + voteAccounts.delinquent, + )[0]; + const votePubkey = new PublicKey(voteAccount.votePubkey); - // Authorize to new account - const newAuthorized = new Account(); - await connection.requestAirdrop(newAuthorized.publicKey, LAMPORTS_PER_SOL); + const payer = new Account(); + await helpers.airdrop({ + connection, + address: payer.publicKey, + amount: 2 * LAMPORTS_PER_SOL, + }); - let authorize = StakeProgram.authorize({ - stakePubkey: newAccountPubkey, - authorizedPubkey: authorized.publicKey, - newAuthorizedPubkey: newAuthorized.publicKey, - stakeAuthorizationType: StakeAuthorizationLayout.Withdrawer, - }); - await sendAndConfirmTransaction(connection, authorize, [authorized], { - commitment: 'singleGossip', - }); - authorize = StakeProgram.authorize({ - stakePubkey: newAccountPubkey, - authorizedPubkey: authorized.publicKey, - newAuthorizedPubkey: newAuthorized.publicKey, - stakeAuthorizationType: StakeAuthorizationLayout.Staker, - }); - await sendAndConfirmTransaction(connection, authorize, [authorized], { - commitment: 'singleGossip', - }); + const authorized = new Account(); + await helpers.airdrop({ + connection, + address: authorized.publicKey, + amount: 2 * LAMPORTS_PER_SOL, + }); - // Test old authorized can't delegate - let delegateNotAuthorized = StakeProgram.delegate({ - stakePubkey: newAccountPubkey, - authorizedPubkey: authorized.publicKey, - votePubkey, - }); - await expect( - sendAndConfirmTransaction(connection, delegateNotAuthorized, [authorized], { - commitment: 'singleGossip', - }), - ).rejects.toThrow(); + const minimumAmount = await connection.getMinimumBalanceForRentExemption( + StakeProgram.space, + ); - // Authorize a derived address - authorize = StakeProgram.authorize({ - stakePubkey: newAccountPubkey, - authorizedPubkey: newAuthorized.publicKey, - newAuthorizedPubkey: newAccountPubkey, - stakeAuthorizationType: StakeAuthorizationLayout.Withdrawer, - }); - await sendAndConfirmTransaction(connection, authorize, [newAuthorized], { - commitment: 'singleGossip', - }); + expect(await connection.getBalance(payer.publicKey)).to.eq( + 2 * LAMPORTS_PER_SOL, + ); + expect(await connection.getBalance(authorized.publicKey)).to.eq( + 2 * LAMPORTS_PER_SOL, + ); - // Restore the previous authority using a derived address - authorize = StakeProgram.authorizeWithSeed({ - stakePubkey: newAccountPubkey, - authorityBase: from.publicKey, - authoritySeed: seed, - authorityOwner: StakeProgram.programId, - newAuthorizedPubkey: newAuthorized.publicKey, - stakeAuthorizationType: StakeAuthorizationLayout.Withdrawer, - }); - await sendAndConfirmTransaction(connection, authorize, [from], { - commitment: 'singleGossip', - }); + { + // Create Stake account without seed + const newStakeAccount = new Account(); + let createAndInitialize = StakeProgram.createAccount({ + fromPubkey: payer.publicKey, + stakePubkey: newStakeAccount.publicKey, + authorized: new Authorized( + authorized.publicKey, + authorized.publicKey, + ), + lockup: new Lockup(0, 0, new PublicKey(0)), + lamports: minimumAmount + 42, + }); + + await sendAndConfirmTransaction( + connection, + createAndInitialize, + [payer, newStakeAccount], + {preflightCommitment: 'singleGossip'}, + ); + expect(await connection.getBalance(newStakeAccount.publicKey)).to.eq( + minimumAmount + 42, + ); + + const delegation = StakeProgram.delegate({ + stakePubkey: newStakeAccount.publicKey, + authorizedPubkey: authorized.publicKey, + votePubkey, + }); + await sendAndConfirmTransaction(connection, delegation, [authorized], { + commitment: 'singleGossip', + }); + } + + // Create Stake account with seed + const seed = 'test string'; + const newAccountPubkey = await PublicKey.createWithSeed( + payer.publicKey, + seed, + StakeProgram.programId, + ); + + let createAndInitializeWithSeed = StakeProgram.createAccountWithSeed({ + fromPubkey: payer.publicKey, + stakePubkey: newAccountPubkey, + basePubkey: payer.publicKey, + seed, + authorized: new Authorized(authorized.publicKey, authorized.publicKey), + lockup: new Lockup(0, 0, new PublicKey(0)), + lamports: 3 * minimumAmount + 42, + }); + + await sendAndConfirmTransaction( + connection, + createAndInitializeWithSeed, + [payer], + {preflightCommitment: 'singleGossip'}, + ); + let originalStakeBalance = await connection.getBalance(newAccountPubkey); + expect(originalStakeBalance).to.eq(3 * minimumAmount + 42); + + let delegation = StakeProgram.delegate({ + stakePubkey: newAccountPubkey, + authorizedPubkey: authorized.publicKey, + votePubkey, + }); + await sendAndConfirmTransaction(connection, delegation, [authorized], { + preflightCommitment: 'singleGossip', + }); + + // Test that withdraw fails before deactivation + const recipient = new Account(); + let withdraw = StakeProgram.withdraw({ + stakePubkey: newAccountPubkey, + authorizedPubkey: authorized.publicKey, + toPubkey: recipient.publicKey, + lamports: 1000, + }); + await expect( + sendAndConfirmTransaction(connection, withdraw, [authorized], { + preflightCommitment: 'singleGossip', + }), + ).to.be.rejected; + + // Deactivate stake + let deactivate = StakeProgram.deactivate({ + stakePubkey: newAccountPubkey, + authorizedPubkey: authorized.publicKey, + }); + await sendAndConfirmTransaction(connection, deactivate, [authorized], { + preflightCommitment: 'singleGossip', + }); + + let stakeActivationState; + do { + stakeActivationState = await connection.getStakeActivation( + newAccountPubkey, + ); + } while (stakeActivationState.state != 'inactive'); + + // Test that withdraw succeeds after deactivation + withdraw = StakeProgram.withdraw({ + stakePubkey: newAccountPubkey, + authorizedPubkey: authorized.publicKey, + toPubkey: recipient.publicKey, + lamports: minimumAmount + 20, + }); + + await sendAndConfirmTransaction(connection, withdraw, [authorized], { + preflightCommitment: 'singleGossip', + }); + const recipientBalance = await connection.getBalance(recipient.publicKey); + expect(recipientBalance).to.eq(minimumAmount + 20); + + // Split stake + const newStake = new Account(); + let split = StakeProgram.split({ + stakePubkey: newAccountPubkey, + authorizedPubkey: authorized.publicKey, + splitStakePubkey: newStake.publicKey, + lamports: minimumAmount + 20, + }); + await sendAndConfirmTransaction( + connection, + split, + [authorized, newStake], + { + preflightCommitment: 'singleGossip', + }, + ); + const balance = await connection.getBalance(newAccountPubkey); + expect(balance).to.eq(minimumAmount + 2); + + // Authorize to new account + const newAuthorized = new Account(); + await connection.requestAirdrop( + newAuthorized.publicKey, + LAMPORTS_PER_SOL, + ); + + let authorize = StakeProgram.authorize({ + stakePubkey: newAccountPubkey, + authorizedPubkey: authorized.publicKey, + newAuthorizedPubkey: newAuthorized.publicKey, + stakeAuthorizationType: StakeAuthorizationLayout.Withdrawer, + }); + await sendAndConfirmTransaction(connection, authorize, [authorized], { + preflightCommitment: 'singleGossip', + }); + authorize = StakeProgram.authorize({ + stakePubkey: newAccountPubkey, + authorizedPubkey: authorized.publicKey, + newAuthorizedPubkey: newAuthorized.publicKey, + stakeAuthorizationType: StakeAuthorizationLayout.Staker, + }); + await sendAndConfirmTransaction(connection, authorize, [authorized], { + preflightCommitment: 'singleGossip', + }); + + // Test old authorized can't delegate + let delegateNotAuthorized = StakeProgram.delegate({ + stakePubkey: newAccountPubkey, + authorizedPubkey: authorized.publicKey, + votePubkey, + }); + await expect( + sendAndConfirmTransaction( + connection, + delegateNotAuthorized, + [authorized], + { + preflightCommitment: 'singleGossip', + }, + ), + ).to.be.rejected; + + // Authorize a derived address + authorize = StakeProgram.authorize({ + stakePubkey: newAccountPubkey, + authorizedPubkey: newAuthorized.publicKey, + newAuthorizedPubkey: newAccountPubkey, + stakeAuthorizationType: StakeAuthorizationLayout.Withdrawer, + }); + await sendAndConfirmTransaction(connection, authorize, [newAuthorized], { + preflightCommitment: 'singleGossip', + }); + + // Restore the previous authority using a derived address + authorize = StakeProgram.authorizeWithSeed({ + stakePubkey: newAccountPubkey, + authorityBase: payer.publicKey, + authoritySeed: seed, + authorityOwner: StakeProgram.programId, + newAuthorizedPubkey: newAuthorized.publicKey, + stakeAuthorizationType: StakeAuthorizationLayout.Withdrawer, + }); + await sendAndConfirmTransaction(connection, authorize, [payer], { + preflightCommitment: 'singleGossip', + }); + }).timeout(10 * 1000); + } }); diff --git a/web3.js/test/system-program.test.js b/web3.js/test/system-program.test.js index 35210afacf..0bd10ef560 100644 --- a/web3.js/test/system-program.test.js +++ b/web3.js/test/system-program.test.js @@ -1,5 +1,8 @@ // @flow +import {Buffer} from 'buffer'; +import {expect} from 'chai'; + import { Account, Connection, @@ -13,562 +16,566 @@ import { LAMPORTS_PER_SOL, } from '../src'; import {NONCE_ACCOUNT_LENGTH} from '../src/nonce-account'; -import {mockRpcEnabled} from './__mocks__/node-fetch'; -import {newAccountWithLamports} from './new-account-with-lamports'; import {sleep} from '../src/util/sleep'; +import {helpers} from './mocks/rpc-http'; import {url} from './url'; -if (!mockRpcEnabled) { - // Testing max commitment level takes around 20s to complete - jest.setTimeout(30000); -} - -test('createAccount', () => { - const params = { - fromPubkey: new Account().publicKey, - newAccountPubkey: new Account().publicKey, - lamports: 123, - space: 0, - programId: SystemProgram.programId, - }; - const transaction = new Transaction().add( - SystemProgram.createAccount(params), - ); - expect(transaction.instructions).toHaveLength(1); - const [systemInstruction] = transaction.instructions; - expect(params).toEqual( - SystemInstruction.decodeCreateAccount(systemInstruction), - ); -}); - -test('transfer', () => { - const params = { - fromPubkey: new Account().publicKey, - toPubkey: new Account().publicKey, - lamports: 123, - }; - const transaction = new Transaction().add(SystemProgram.transfer(params)); - expect(transaction.instructions).toHaveLength(1); - const [systemInstruction] = transaction.instructions; - expect(params).toEqual(SystemInstruction.decodeTransfer(systemInstruction)); -}); - -test('transferWithSeed', () => { - const params = { - fromPubkey: new Account().publicKey, - basePubkey: new Account().publicKey, - toPubkey: new Account().publicKey, - lamports: 123, - seed: '你好', - programId: new Account().publicKey, - }; - const transaction = new Transaction().add(SystemProgram.transfer(params)); - expect(transaction.instructions).toHaveLength(1); - const [systemInstruction] = transaction.instructions; - expect(params).toEqual( - SystemInstruction.decodeTransferWithSeed(systemInstruction), - ); -}); - -test('allocate', () => { - const params = { - accountPubkey: new Account().publicKey, - space: 42, - }; - const transaction = new Transaction().add(SystemProgram.allocate(params)); - expect(transaction.instructions).toHaveLength(1); - const [systemInstruction] = transaction.instructions; - expect(params).toEqual(SystemInstruction.decodeAllocate(systemInstruction)); -}); - -test('allocateWithSeed', () => { - const params = { - accountPubkey: new Account().publicKey, - basePubkey: new Account().publicKey, - seed: '你好', - space: 42, - programId: new Account().publicKey, - }; - const transaction = new Transaction().add(SystemProgram.allocate(params)); - expect(transaction.instructions).toHaveLength(1); - const [systemInstruction] = transaction.instructions; - expect(params).toEqual( - SystemInstruction.decodeAllocateWithSeed(systemInstruction), - ); -}); - -test('assign', () => { - const params = { - accountPubkey: new Account().publicKey, - programId: new Account().publicKey, - }; - const transaction = new Transaction().add(SystemProgram.assign(params)); - expect(transaction.instructions).toHaveLength(1); - const [systemInstruction] = transaction.instructions; - expect(params).toEqual(SystemInstruction.decodeAssign(systemInstruction)); -}); - -test('assignWithSeed', () => { - const params = { - accountPubkey: new Account().publicKey, - basePubkey: new Account().publicKey, - seed: '你好', - programId: new Account().publicKey, - }; - const transaction = new Transaction().add(SystemProgram.assign(params)); - expect(transaction.instructions).toHaveLength(1); - const [systemInstruction] = transaction.instructions; - expect(params).toEqual( - SystemInstruction.decodeAssignWithSeed(systemInstruction), - ); -}); - -test('createAccountWithSeed', () => { - const fromPubkey = new Account().publicKey; - const params = { - fromPubkey, - newAccountPubkey: new Account().publicKey, - basePubkey: fromPubkey, - seed: 'hi there', - lamports: 123, - space: 0, - programId: SystemProgram.programId, - }; - const transaction = new Transaction().add( - SystemProgram.createAccountWithSeed(params), - ); - expect(transaction.instructions).toHaveLength(1); - const [systemInstruction] = transaction.instructions; - expect(params).toEqual( - SystemInstruction.decodeCreateWithSeed(systemInstruction), - ); -}); - -test('createNonceAccount', () => { - const fromPubkey = new Account().publicKey; - const params = { - fromPubkey, - noncePubkey: new Account().publicKey, - authorizedPubkey: fromPubkey, - lamports: 123, - }; - - const transaction = new Transaction().add( - SystemProgram.createNonceAccount(params), - ); - expect(transaction.instructions).toHaveLength(2); - const [createInstruction, initInstruction] = transaction.instructions; - - const createParams = { - fromPubkey: params.fromPubkey, - newAccountPubkey: params.noncePubkey, - lamports: params.lamports, - space: NONCE_ACCOUNT_LENGTH, - programId: SystemProgram.programId, - }; - expect(createParams).toEqual( - SystemInstruction.decodeCreateAccount(createInstruction), - ); - - const initParams = { - noncePubkey: params.noncePubkey, - authorizedPubkey: fromPubkey, - }; - expect(initParams).toEqual( - SystemInstruction.decodeNonceInitialize(initInstruction), - ); -}); - -test('createNonceAccount with seed', () => { - const fromPubkey = new Account().publicKey; - const params = { - fromPubkey, - noncePubkey: new Account().publicKey, - authorizedPubkey: fromPubkey, - basePubkey: fromPubkey, - seed: 'hi there', - lamports: 123, - }; - - const transaction = new Transaction().add( - SystemProgram.createNonceAccount(params), - ); - expect(transaction.instructions).toHaveLength(2); - const [createInstruction, initInstruction] = transaction.instructions; - - const createParams = { - fromPubkey: params.fromPubkey, - newAccountPubkey: params.noncePubkey, - basePubkey: fromPubkey, - seed: 'hi there', - lamports: params.lamports, - space: NONCE_ACCOUNT_LENGTH, - programId: SystemProgram.programId, - }; - expect(createParams).toEqual( - SystemInstruction.decodeCreateWithSeed(createInstruction), - ); - - const initParams = { - noncePubkey: params.noncePubkey, - authorizedPubkey: fromPubkey, - }; - expect(initParams).toEqual( - SystemInstruction.decodeNonceInitialize(initInstruction), - ); -}); - -test('nonceAdvance', () => { - const params = { - noncePubkey: new Account().publicKey, - authorizedPubkey: new Account().publicKey, - }; - const instruction = SystemProgram.nonceAdvance(params); - expect(params).toEqual(SystemInstruction.decodeNonceAdvance(instruction)); -}); - -test('nonceWithdraw', () => { - const params = { - noncePubkey: new Account().publicKey, - authorizedPubkey: new Account().publicKey, - toPubkey: new Account().publicKey, - lamports: 123, - }; - const transaction = new Transaction().add( - SystemProgram.nonceWithdraw(params), - ); - expect(transaction.instructions).toHaveLength(1); - const [instruction] = transaction.instructions; - expect(params).toEqual(SystemInstruction.decodeNonceWithdraw(instruction)); -}); - -test('nonceAuthorize', () => { - const params = { - noncePubkey: new Account().publicKey, - authorizedPubkey: new Account().publicKey, - newAuthorizedPubkey: new Account().publicKey, - }; - - const transaction = new Transaction().add( - SystemProgram.nonceAuthorize(params), - ); - expect(transaction.instructions).toHaveLength(1); - const [instruction] = transaction.instructions; - expect(params).toEqual(SystemInstruction.decodeNonceAuthorize(instruction)); -}); - -test('non-SystemInstruction error', () => { - const from = new Account(); - const to = new Account(); - - const badProgramId = { - keys: [ - {pubkey: from.publicKey, isSigner: true, isWritable: true}, - {pubkey: to.publicKey, isSigner: false, isWritable: true}, - ], - programId: StakeProgram.programId, - data: Buffer.from([2, 0, 0, 0]), - }; - expect(() => { - SystemInstruction.decodeInstructionType( - new TransactionInstruction(badProgramId), +describe('SystemProgram', () => { + it('createAccount', () => { + const params = { + fromPubkey: new Account().publicKey, + newAccountPubkey: new Account().publicKey, + lamports: 123, + space: 0, + programId: SystemProgram.programId, + }; + const transaction = new Transaction().add( + SystemProgram.createAccount(params), ); - }).toThrow(); + expect(transaction.instructions).to.have.length(1); + const [systemInstruction] = transaction.instructions; + expect(params).to.eql( + SystemInstruction.decodeCreateAccount(systemInstruction), + ); + }); - const stakePubkey = new Account().publicKey; - const authorizedPubkey = new Account().publicKey; - const params = {stakePubkey, authorizedPubkey}; - const transaction = StakeProgram.deactivate(params); + it('transfer', () => { + const params = { + fromPubkey: new Account().publicKey, + toPubkey: new Account().publicKey, + lamports: 123, + }; + const transaction = new Transaction().add(SystemProgram.transfer(params)); + expect(transaction.instructions).to.have.length(1); + const [systemInstruction] = transaction.instructions; + expect(params).to.eql(SystemInstruction.decodeTransfer(systemInstruction)); + }); - expect(() => { - SystemInstruction.decodeInstructionType(transaction.instructions[1]); - }).toThrow(); + it('transferWithSeed', () => { + const params = { + fromPubkey: new Account().publicKey, + basePubkey: new Account().publicKey, + toPubkey: new Account().publicKey, + lamports: 123, + seed: '你好', + programId: new Account().publicKey, + }; + const transaction = new Transaction().add(SystemProgram.transfer(params)); + expect(transaction.instructions).to.have.length(1); + const [systemInstruction] = transaction.instructions; + expect(params).to.eql( + SystemInstruction.decodeTransferWithSeed(systemInstruction), + ); + }); - transaction.instructions[0].data[0] = 11; - expect(() => { - SystemInstruction.decodeInstructionType(transaction.instructions[0]); - }).toThrow(); -}); - -test('live Nonce actions', async () => { - if (mockRpcEnabled) { - console.log('non-live test skipped'); - return; - } - - const connection = new Connection(url, 'singleGossip'); - const nonceAccount = new Account(); - const from = await newAccountWithLamports(connection, 2 * LAMPORTS_PER_SOL); - const to = new Account(); - const newAuthority = await newAccountWithLamports( - connection, - LAMPORTS_PER_SOL, - ); - - const minimumAmount = await connection.getMinimumBalanceForRentExemption( - NONCE_ACCOUNT_LENGTH, - ); - - let createNonceAccount = new Transaction().add( - SystemProgram.createNonceAccount({ - fromPubkey: from.publicKey, - noncePubkey: nonceAccount.publicKey, - authorizedPubkey: from.publicKey, - lamports: minimumAmount, - }), - ); - await sendAndConfirmTransaction( - connection, - createNonceAccount, - [from, nonceAccount], - {commitment: 'singleGossip', preflightCommitment: 'singleGossip'}, - ); - const nonceBalance = await connection.getBalance(nonceAccount.publicKey); - expect(nonceBalance).toEqual(minimumAmount); - - const nonceQuery1 = await connection.getNonce(nonceAccount.publicKey); - if (nonceQuery1 === null) { - expect(nonceQuery1).not.toBeNull(); - return; - } - - const nonceQuery2 = await connection.getNonce(nonceAccount.publicKey); - if (nonceQuery2 === null) { - expect(nonceQuery2).not.toBeNull(); - return; - } - - expect(nonceQuery1.nonce).toEqual(nonceQuery2.nonce); - - // Wait for blockhash to advance - await sleep(500); - - const advanceNonce = new Transaction().add( - SystemProgram.nonceAdvance({ - noncePubkey: nonceAccount.publicKey, - authorizedPubkey: from.publicKey, - }), - ); - await sendAndConfirmTransaction(connection, advanceNonce, [from], { - commitment: 'singleGossip', - preflightCommitment: 'singleGossip', - }); - const nonceQuery3 = await connection.getNonce(nonceAccount.publicKey); - if (nonceQuery3 === null) { - expect(nonceQuery3).not.toBeNull(); - return; - } - expect(nonceQuery1.nonce).not.toEqual(nonceQuery3.nonce); - const nonce = nonceQuery3.nonce; - - // Wait for blockhash to advance - await sleep(500); - - const authorizeNonce = new Transaction().add( - SystemProgram.nonceAuthorize({ - noncePubkey: nonceAccount.publicKey, - authorizedPubkey: from.publicKey, - newAuthorizedPubkey: newAuthority.publicKey, - }), - ); - await sendAndConfirmTransaction(connection, authorizeNonce, [from], { - commitment: 'singleGossip', - preflightCommitment: 'singleGossip', - }); - - let transfer = new Transaction().add( - SystemProgram.transfer({ - fromPubkey: from.publicKey, - toPubkey: to.publicKey, - lamports: minimumAmount, - }), - ); - transfer.nonceInfo = { - nonce, - nonceInstruction: SystemProgram.nonceAdvance({ - noncePubkey: nonceAccount.publicKey, - authorizedPubkey: newAuthority.publicKey, - }), - }; - - await sendAndConfirmTransaction(connection, transfer, [from, newAuthority], { - commitment: 'singleGossip', - preflightCommitment: 'singleGossip', - }); - const toBalance = await connection.getBalance(to.publicKey); - expect(toBalance).toEqual(minimumAmount); - - // Wait for blockhash to advance - await sleep(500); - - const withdrawAccount = new Account(); - const withdrawNonce = new Transaction().add( - SystemProgram.nonceWithdraw({ - noncePubkey: nonceAccount.publicKey, - authorizedPubkey: newAuthority.publicKey, - lamports: minimumAmount, - toPubkey: withdrawAccount.publicKey, - }), - ); - await sendAndConfirmTransaction(connection, withdrawNonce, [newAuthority], { - commitment: 'singleGossip', - preflightCommitment: 'singleGossip', - }); - expect(await connection.getBalance(nonceAccount.publicKey)).toEqual(0); - const withdrawBalance = await connection.getBalance( - withdrawAccount.publicKey, - ); - expect(withdrawBalance).toEqual(minimumAmount); -}); - -test('live withSeed actions', async () => { - if (mockRpcEnabled) { - console.log('non-live test skipped'); - return; - } - - const connection = new Connection(url, 'singleGossip'); - const baseAccount = await newAccountWithLamports( - connection, - 2 * LAMPORTS_PER_SOL, - ); - const basePubkey = baseAccount.publicKey; - const seed = 'hi there'; - const programId = new Account().publicKey; - const createAccountWithSeedAddress = await PublicKey.createWithSeed( - basePubkey, - seed, - programId, - ); - const space = 0; - - const minimumAmount = await connection.getMinimumBalanceForRentExemption( - space, - ); - - // Test CreateAccountWithSeed - const createAccountWithSeedParams = { - fromPubkey: basePubkey, - newAccountPubkey: createAccountWithSeedAddress, - basePubkey, - seed, - lamports: minimumAmount, - space, - programId, - }; - const createAccountWithSeedTransaction = new Transaction().add( - SystemProgram.createAccountWithSeed(createAccountWithSeedParams), - ); - await sendAndConfirmTransaction( - connection, - createAccountWithSeedTransaction, - [baseAccount], - {commitment: 'singleGossip', preflightCommitment: 'singleGossip'}, - ); - const createAccountWithSeedBalance = await connection.getBalance( - createAccountWithSeedAddress, - ); - expect(createAccountWithSeedBalance).toEqual(minimumAmount); - - // Transfer to a derived address to prep for TransferWithSeed - const programId2 = new Account().publicKey; - const transferWithSeedAddress = await PublicKey.createWithSeed( - basePubkey, - seed, - programId2, - ); - await sendAndConfirmTransaction( - connection, - new Transaction().add( - SystemProgram.transfer({ - fromPubkey: baseAccount.publicKey, - toPubkey: transferWithSeedAddress, - lamports: 3 * minimumAmount, - }), - ), - [baseAccount], - {commitment: 'singleGossip', preflightCommitment: 'singleGossip'}, - ); - let transferWithSeedAddressBalance = await connection.getBalance( - transferWithSeedAddress, - ); - expect(transferWithSeedAddressBalance).toEqual(3 * minimumAmount); - - // Test TransferWithSeed - const programId3 = new Account(); - const toPubkey = await PublicKey.createWithSeed( - basePubkey, - seed, - programId3.publicKey, - ); - const transferWithSeedParams = { - fromPubkey: transferWithSeedAddress, - basePubkey, - toPubkey, - lamports: 2 * minimumAmount, - seed, - programId: programId2, - }; - const transferWithSeedTransaction = new Transaction().add( - SystemProgram.transfer(transferWithSeedParams), - ); - await sendAndConfirmTransaction( - connection, - transferWithSeedTransaction, - [baseAccount], - {commitment: 'singleGossip', preflightCommitment: 'singleGossip'}, - ); - const toBalance = await connection.getBalance(toPubkey); - expect(toBalance).toEqual(2 * minimumAmount); - transferWithSeedAddressBalance = await connection.getBalance( - createAccountWithSeedAddress, - ); - expect(transferWithSeedAddressBalance).toEqual(minimumAmount); - - // Test AllocateWithSeed - const allocateWithSeedParams = { - accountPubkey: toPubkey, - basePubkey, - seed, - space: 10, - programId: programId3.publicKey, - }; - const allocateWithSeedTransaction = new Transaction().add( - SystemProgram.allocate(allocateWithSeedParams), - ); - await sendAndConfirmTransaction( - connection, - allocateWithSeedTransaction, - [baseAccount], - {commitment: 'singleGossip', preflightCommitment: 'singleGossip'}, - ); - let account = await connection.getAccountInfo(toPubkey); - if (account === null) { - expect(account).not.toBeNull(); - return; - } - expect(account.data).toHaveLength(10); - - // Test AssignWithSeed - const assignWithSeedParams = { - accountPubkey: toPubkey, - basePubkey, - seed, - programId: programId3.publicKey, - }; - const assignWithSeedTransaction = new Transaction().add( - SystemProgram.assign(assignWithSeedParams), - ); - await sendAndConfirmTransaction( - connection, - assignWithSeedTransaction, - [baseAccount], - {commitment: 'singleGossip', preflightCommitment: 'singleGossip'}, - ); - account = await connection.getAccountInfo(toPubkey); - if (account === null) { - expect(account).not.toBeNull(); - return; - } - expect(account.owner).toEqual(programId3.publicKey); + it('allocate', () => { + const params = { + accountPubkey: new Account().publicKey, + space: 42, + }; + const transaction = new Transaction().add(SystemProgram.allocate(params)); + expect(transaction.instructions).to.have.length(1); + const [systemInstruction] = transaction.instructions; + expect(params).to.eql(SystemInstruction.decodeAllocate(systemInstruction)); + }); + + it('allocateWithSeed', () => { + const params = { + accountPubkey: new Account().publicKey, + basePubkey: new Account().publicKey, + seed: '你好', + space: 42, + programId: new Account().publicKey, + }; + const transaction = new Transaction().add(SystemProgram.allocate(params)); + expect(transaction.instructions).to.have.length(1); + const [systemInstruction] = transaction.instructions; + expect(params).to.eql( + SystemInstruction.decodeAllocateWithSeed(systemInstruction), + ); + }); + + it('assign', () => { + const params = { + accountPubkey: new Account().publicKey, + programId: new Account().publicKey, + }; + const transaction = new Transaction().add(SystemProgram.assign(params)); + expect(transaction.instructions).to.have.length(1); + const [systemInstruction] = transaction.instructions; + expect(params).to.eql(SystemInstruction.decodeAssign(systemInstruction)); + }); + + it('assignWithSeed', () => { + const params = { + accountPubkey: new Account().publicKey, + basePubkey: new Account().publicKey, + seed: '你好', + programId: new Account().publicKey, + }; + const transaction = new Transaction().add(SystemProgram.assign(params)); + expect(transaction.instructions).to.have.length(1); + const [systemInstruction] = transaction.instructions; + expect(params).to.eql( + SystemInstruction.decodeAssignWithSeed(systemInstruction), + ); + }); + + it('createAccountWithSeed', () => { + const fromPubkey = new Account().publicKey; + const params = { + fromPubkey, + newAccountPubkey: new Account().publicKey, + basePubkey: fromPubkey, + seed: 'hi there', + lamports: 123, + space: 0, + programId: SystemProgram.programId, + }; + const transaction = new Transaction().add( + SystemProgram.createAccountWithSeed(params), + ); + expect(transaction.instructions).to.have.length(1); + const [systemInstruction] = transaction.instructions; + expect(params).to.eql( + SystemInstruction.decodeCreateWithSeed(systemInstruction), + ); + }); + + it('createNonceAccount', () => { + const fromPubkey = new Account().publicKey; + const params = { + fromPubkey, + noncePubkey: new Account().publicKey, + authorizedPubkey: fromPubkey, + lamports: 123, + }; + + const transaction = new Transaction().add( + SystemProgram.createNonceAccount(params), + ); + expect(transaction.instructions).to.have.length(2); + const [createInstruction, initInstruction] = transaction.instructions; + + const createParams = { + fromPubkey: params.fromPubkey, + newAccountPubkey: params.noncePubkey, + lamports: params.lamports, + space: NONCE_ACCOUNT_LENGTH, + programId: SystemProgram.programId, + }; + expect(createParams).to.eql( + SystemInstruction.decodeCreateAccount(createInstruction), + ); + + const initParams = { + noncePubkey: params.noncePubkey, + authorizedPubkey: fromPubkey, + }; + expect(initParams).to.eql( + SystemInstruction.decodeNonceInitialize(initInstruction), + ); + }); + + it('createNonceAccount with seed', () => { + const fromPubkey = new Account().publicKey; + const params = { + fromPubkey, + noncePubkey: new Account().publicKey, + authorizedPubkey: fromPubkey, + basePubkey: fromPubkey, + seed: 'hi there', + lamports: 123, + }; + + const transaction = new Transaction().add( + SystemProgram.createNonceAccount(params), + ); + expect(transaction.instructions).to.have.length(2); + const [createInstruction, initInstruction] = transaction.instructions; + + const createParams = { + fromPubkey: params.fromPubkey, + newAccountPubkey: params.noncePubkey, + basePubkey: fromPubkey, + seed: 'hi there', + lamports: params.lamports, + space: NONCE_ACCOUNT_LENGTH, + programId: SystemProgram.programId, + }; + expect(createParams).to.eql( + SystemInstruction.decodeCreateWithSeed(createInstruction), + ); + + const initParams = { + noncePubkey: params.noncePubkey, + authorizedPubkey: fromPubkey, + }; + expect(initParams).to.eql( + SystemInstruction.decodeNonceInitialize(initInstruction), + ); + }); + + it('nonceAdvance', () => { + const params = { + noncePubkey: new Account().publicKey, + authorizedPubkey: new Account().publicKey, + }; + const instruction = SystemProgram.nonceAdvance(params); + expect(params).to.eql(SystemInstruction.decodeNonceAdvance(instruction)); + }); + + it('nonceWithdraw', () => { + const params = { + noncePubkey: new Account().publicKey, + authorizedPubkey: new Account().publicKey, + toPubkey: new Account().publicKey, + lamports: 123, + }; + const transaction = new Transaction().add( + SystemProgram.nonceWithdraw(params), + ); + expect(transaction.instructions).to.have.length(1); + const [instruction] = transaction.instructions; + expect(params).to.eql(SystemInstruction.decodeNonceWithdraw(instruction)); + }); + + it('nonceAuthorize', () => { + const params = { + noncePubkey: new Account().publicKey, + authorizedPubkey: new Account().publicKey, + newAuthorizedPubkey: new Account().publicKey, + }; + + const transaction = new Transaction().add( + SystemProgram.nonceAuthorize(params), + ); + expect(transaction.instructions).to.have.length(1); + const [instruction] = transaction.instructions; + expect(params).to.eql(SystemInstruction.decodeNonceAuthorize(instruction)); + }); + + it('non-SystemInstruction error', () => { + const from = new Account(); + const to = new Account(); + + const badProgramId = { + keys: [ + {pubkey: from.publicKey, isSigner: true, isWritable: true}, + {pubkey: to.publicKey, isSigner: false, isWritable: true}, + ], + programId: StakeProgram.programId, + data: Buffer.from([2, 0, 0, 0]), + }; + expect(() => { + SystemInstruction.decodeInstructionType( + new TransactionInstruction(badProgramId), + ); + }).to.throw(); + + const stakePubkey = new Account().publicKey; + const authorizedPubkey = new Account().publicKey; + const params = {stakePubkey, authorizedPubkey}; + const transaction = StakeProgram.deactivate(params); + + expect(() => { + SystemInstruction.decodeInstructionType(transaction.instructions[1]); + }).to.throw(); + + transaction.instructions[0].data[0] = 11; + expect(() => { + SystemInstruction.decodeInstructionType(transaction.instructions[0]); + }).to.throw(); + }); + + if (process.env.TEST_LIVE) { + it('live Nonce actions', async () => { + const connection = new Connection(url, 'singleGossip'); + const nonceAccount = new Account(); + const from = new Account(); + await helpers.airdrop({ + connection, + address: from.publicKey, + amount: 2 * LAMPORTS_PER_SOL, + }); + + const to = new Account(); + const newAuthority = new Account(); + await helpers.airdrop({ + connection, + address: newAuthority.publicKey, + amount: LAMPORTS_PER_SOL, + }); + + const minimumAmount = await connection.getMinimumBalanceForRentExemption( + NONCE_ACCOUNT_LENGTH, + ); + + let createNonceAccount = new Transaction().add( + SystemProgram.createNonceAccount({ + fromPubkey: from.publicKey, + noncePubkey: nonceAccount.publicKey, + authorizedPubkey: from.publicKey, + lamports: minimumAmount, + }), + ); + await sendAndConfirmTransaction( + connection, + createNonceAccount, + [from, nonceAccount], + {preflightCommitment: 'singleGossip'}, + ); + const nonceBalance = await connection.getBalance(nonceAccount.publicKey); + expect(nonceBalance).to.eq(minimumAmount); + + const nonceQuery1 = await connection.getNonce(nonceAccount.publicKey); + if (nonceQuery1 === null) { + expect(nonceQuery1).not.to.be.null; + return; + } + + const nonceQuery2 = await connection.getNonce(nonceAccount.publicKey); + if (nonceQuery2 === null) { + expect(nonceQuery2).not.to.be.null; + return; + } + + expect(nonceQuery1.nonce).to.eq(nonceQuery2.nonce); + + // Wait for blockhash to advance + await sleep(500); + + const advanceNonce = new Transaction().add( + SystemProgram.nonceAdvance({ + noncePubkey: nonceAccount.publicKey, + authorizedPubkey: from.publicKey, + }), + ); + await sendAndConfirmTransaction(connection, advanceNonce, [from], { + preflightCommitment: 'singleGossip', + }); + const nonceQuery3 = await connection.getNonce(nonceAccount.publicKey); + if (nonceQuery3 === null) { + expect(nonceQuery3).not.to.be.null; + return; + } + expect(nonceQuery1.nonce).not.to.eq(nonceQuery3.nonce); + const nonce = nonceQuery3.nonce; + + // Wait for blockhash to advance + await sleep(500); + + const authorizeNonce = new Transaction().add( + SystemProgram.nonceAuthorize({ + noncePubkey: nonceAccount.publicKey, + authorizedPubkey: from.publicKey, + newAuthorizedPubkey: newAuthority.publicKey, + }), + ); + await sendAndConfirmTransaction(connection, authorizeNonce, [from], { + preflightCommitment: 'singleGossip', + }); + + let transfer = new Transaction().add( + SystemProgram.transfer({ + fromPubkey: from.publicKey, + toPubkey: to.publicKey, + lamports: minimumAmount, + }), + ); + transfer.nonceInfo = { + nonce, + nonceInstruction: SystemProgram.nonceAdvance({ + noncePubkey: nonceAccount.publicKey, + authorizedPubkey: newAuthority.publicKey, + }), + }; + + await sendAndConfirmTransaction( + connection, + transfer, + [from, newAuthority], + { + preflightCommitment: 'singleGossip', + }, + ); + const toBalance = await connection.getBalance(to.publicKey); + expect(toBalance).to.eq(minimumAmount); + + // Wait for blockhash to advance + await sleep(500); + + const withdrawAccount = new Account(); + const withdrawNonce = new Transaction().add( + SystemProgram.nonceWithdraw({ + noncePubkey: nonceAccount.publicKey, + authorizedPubkey: newAuthority.publicKey, + lamports: minimumAmount, + toPubkey: withdrawAccount.publicKey, + }), + ); + await sendAndConfirmTransaction( + connection, + withdrawNonce, + [newAuthority], + { + preflightCommitment: 'singleGossip', + }, + ); + expect(await connection.getBalance(nonceAccount.publicKey)).to.eq(0); + const withdrawBalance = await connection.getBalance( + withdrawAccount.publicKey, + ); + expect(withdrawBalance).to.eq(minimumAmount); + }).timeout(10 * 1000); + + it('live withSeed actions', async () => { + const connection = new Connection(url, 'singleGossip'); + const baseAccount = new Account(); + await helpers.airdrop({ + connection, + address: baseAccount.publicKey, + amount: 2 * LAMPORTS_PER_SOL, + }); + const basePubkey = baseAccount.publicKey; + const seed = 'hi there'; + const programId = new Account().publicKey; + const createAccountWithSeedAddress = await PublicKey.createWithSeed( + basePubkey, + seed, + programId, + ); + const space = 0; + + const minimumAmount = await connection.getMinimumBalanceForRentExemption( + space, + ); + + // Test CreateAccountWithSeed + const createAccountWithSeedParams = { + fromPubkey: basePubkey, + newAccountPubkey: createAccountWithSeedAddress, + basePubkey, + seed, + lamports: minimumAmount, + space, + programId, + }; + const createAccountWithSeedTransaction = new Transaction().add( + SystemProgram.createAccountWithSeed(createAccountWithSeedParams), + ); + await sendAndConfirmTransaction( + connection, + createAccountWithSeedTransaction, + [baseAccount], + {preflightCommitment: 'singleGossip'}, + ); + const createAccountWithSeedBalance = await connection.getBalance( + createAccountWithSeedAddress, + ); + expect(createAccountWithSeedBalance).to.eq(minimumAmount); + + // Transfer to a derived address to prep for TransferWithSeed + const programId2 = new Account().publicKey; + const transferWithSeedAddress = await PublicKey.createWithSeed( + basePubkey, + seed, + programId2, + ); + await sendAndConfirmTransaction( + connection, + new Transaction().add( + SystemProgram.transfer({ + fromPubkey: baseAccount.publicKey, + toPubkey: transferWithSeedAddress, + lamports: 3 * minimumAmount, + }), + ), + [baseAccount], + {preflightCommitment: 'singleGossip'}, + ); + let transferWithSeedAddressBalance = await connection.getBalance( + transferWithSeedAddress, + ); + expect(transferWithSeedAddressBalance).to.eq(3 * minimumAmount); + + // Test TransferWithSeed + const programId3 = new Account(); + const toPubkey = await PublicKey.createWithSeed( + basePubkey, + seed, + programId3.publicKey, + ); + const transferWithSeedParams = { + fromPubkey: transferWithSeedAddress, + basePubkey, + toPubkey, + lamports: 2 * minimumAmount, + seed, + programId: programId2, + }; + const transferWithSeedTransaction = new Transaction().add( + SystemProgram.transfer(transferWithSeedParams), + ); + await sendAndConfirmTransaction( + connection, + transferWithSeedTransaction, + [baseAccount], + {preflightCommitment: 'singleGossip'}, + ); + const toBalance = await connection.getBalance(toPubkey); + expect(toBalance).to.eq(2 * minimumAmount); + transferWithSeedAddressBalance = await connection.getBalance( + createAccountWithSeedAddress, + ); + expect(transferWithSeedAddressBalance).to.eq(minimumAmount); + + // Test AllocateWithSeed + const allocateWithSeedParams = { + accountPubkey: toPubkey, + basePubkey, + seed, + space: 10, + programId: programId3.publicKey, + }; + const allocateWithSeedTransaction = new Transaction().add( + SystemProgram.allocate(allocateWithSeedParams), + ); + await sendAndConfirmTransaction( + connection, + allocateWithSeedTransaction, + [baseAccount], + {preflightCommitment: 'singleGossip'}, + ); + let account = await connection.getAccountInfo(toPubkey); + if (account === null) { + expect(account).not.to.be.null; + return; + } + expect(account.data).to.have.length(10); + + // Test AssignWithSeed + const assignWithSeedParams = { + accountPubkey: toPubkey, + basePubkey, + seed, + programId: programId3.publicKey, + }; + const assignWithSeedTransaction = new Transaction().add( + SystemProgram.assign(assignWithSeedParams), + ); + await sendAndConfirmTransaction( + connection, + assignWithSeedTransaction, + [baseAccount], + {preflightCommitment: 'singleGossip'}, + ); + account = await connection.getAccountInfo(toPubkey); + if (account === null) { + expect(account).not.to.be.null; + return; + } + expect(account.owner).to.eql(programId3.publicKey); + }).timeout(10 * 1000); + } }); diff --git a/web3.js/test/transaction-payer.test.js b/web3.js/test/transaction-payer.test.js index 7de94a1469..3996ea2554 100644 --- a/web3.js/test/transaction-payer.test.js +++ b/web3.js/test/transaction-payer.test.js @@ -1,4 +1,7 @@ // @flow + +import {expect} from 'chai'; + import { Account, Connection, @@ -6,202 +9,136 @@ import { SystemProgram, LAMPORTS_PER_SOL, } from '../src'; -import {mockRpc, mockRpcEnabled} from './__mocks__/node-fetch'; -import {mockGetRecentBlockhash} from './mockrpc/get-recent-blockhash'; -import {url} from './url'; -import {mockConfirmTransaction} from './mockrpc/confirm-transaction'; +import {MOCK_PORT, url} from './url'; -if (!mockRpcEnabled) { - // The default of 5 seconds is too slow for live testing sometimes - jest.setTimeout(30000); -} +import { + helpers, + mockErrorMessage, + mockErrorResponse, + uniqueSignature, + uniqueBlockhash, + mockRpcResponse, + mockServer, +} from './mocks/rpc-http'; +import { + stubRpcWebSocket, + restoreRpcWebSocket, + mockRpcMessage, +} from './mocks/rpc-websockets'; +import base58 from 'bs58'; -test('transaction-payer', async () => { - const accountPayer = new Account(); - const accountFrom = new Account(); - const accountTo = new Account(); - const connection = new Connection(url, 'singleGossip'); +describe('Transaction Payer', () => { + let connection: Connection; + beforeEach(() => { + connection = new Connection(url); + }); - mockRpc.push([ - url, - { - method: 'getMinimumBalanceForRentExemption', - params: [0, {commitment: 'singleGossip'}], - }, - { - error: null, - result: 50, - }, - ]); + if (!process.env.TEST_LIVE) { + beforeEach(() => { + mockServer.start(MOCK_PORT); + stubRpcWebSocket(connection); + }); - const minimumAmount = await connection.getMinimumBalanceForRentExemption( - 0, - 'singleGossip', - ); - - mockRpc.push([ - url, - { - method: 'requestAirdrop', - params: [accountPayer.publicKey.toBase58(), LAMPORTS_PER_SOL], - }, - { - error: null, - result: - '8WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk', - }, - ]); - let signature = await connection.requestAirdrop( - accountPayer.publicKey, - LAMPORTS_PER_SOL, - ); - mockConfirmTransaction(signature); - await connection.confirmTransaction(signature, 'singleGossip'); - - mockRpc.push([ - url, - { - method: 'requestAirdrop', - params: [accountFrom.publicKey.toBase58(), minimumAmount + 12], - }, - { - error: null, - result: - '8WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk', - }, - ]); - signature = await connection.requestAirdrop( - accountFrom.publicKey, - minimumAmount + 12, - ); - mockConfirmTransaction(signature); - await connection.confirmTransaction(signature, 'singleGossip'); - - mockRpc.push([ - url, - { - method: 'requestAirdrop', - params: [accountTo.publicKey.toBase58(), minimumAmount + 21], - }, - { - error: null, - result: - '8WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk', - }, - ]); - signature = await connection.requestAirdrop( - accountTo.publicKey, - minimumAmount + 21, - ); - mockConfirmTransaction(signature); - await connection.confirmTransaction(signature, 'singleGossip'); - - mockGetRecentBlockhash('max'); - mockRpc.push([ - url, - { - method: 'sendTransaction', - }, - { - error: null, - result: - '3WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk', - }, - ]); - - const transaction = new Transaction().add( - SystemProgram.transfer({ - fromPubkey: accountFrom.publicKey, - toPubkey: accountTo.publicKey, - lamports: 10, - }), - ); - - signature = await connection.sendTransaction( - transaction, - [accountPayer, accountFrom], - {skipPreflight: true}, - ); - - mockConfirmTransaction(signature); - await connection.confirmTransaction(signature, 'singleGossip'); - - mockRpc.push([ - url, - { - method: 'getSignatureStatuses', - params: [ - [ - '3WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk', - ], - ], - }, - { - error: null, - result: { - context: { - slot: 11, - }, - value: [ - { - slot: 0, - confirmations: 11, - status: {Ok: null}, - err: null, - }, - ], - }, - }, - ]); - const {value} = await connection.getSignatureStatus(signature); - if (value !== null) { - expect(typeof value.slot).toEqual('number'); - expect(value.err).toBeNull(); - } else { - expect(value).not.toBeNull(); + afterEach(() => { + mockServer.stop(); + restoreRpcWebSocket(connection); + }); } - mockRpc.push([ - url, - { + it('transaction-payer', async () => { + const accountPayer = new Account(); + const accountFrom = new Account(); + const accountTo = new Account(); + + await helpers.airdrop({ + connection, + address: accountPayer.publicKey, + amount: LAMPORTS_PER_SOL, + }); + + await mockRpcResponse({ + method: 'getMinimumBalanceForRentExemption', + params: [0], + value: 50, + }); + + const minimumAmount = await connection.getMinimumBalanceForRentExemption(0); + + await helpers.airdrop({ + connection, + address: accountFrom.publicKey, + amount: minimumAmount + 12, + }); + + await helpers.airdrop({ + connection, + address: accountTo.publicKey, + amount: minimumAmount + 21, + }); + + const transaction = new Transaction().add( + SystemProgram.transfer({ + fromPubkey: accountFrom.publicKey, + toPubkey: accountTo.publicKey, + lamports: 10, + }), + ); + + await helpers.processTransaction({ + connection, + transaction, + signers: [accountPayer, accountFrom], + commitment: 'singleGossip', + }); + + const signature = base58.encode(transaction.signature); + + await mockRpcResponse({ + method: 'getSignatureStatuses', + params: [[signature]], + value: [ + { + slot: 0, + confirmations: 11, + status: {Ok: null}, + err: null, + }, + ], + withContext: true, + }); + const {value} = await connection.getSignatureStatus(signature); + if (value !== null) { + expect(typeof value.slot).to.eq('number'); + expect(value.err).to.be.null; + } else { + expect(value).not.to.be.null; + } + + await mockRpcResponse({ method: 'getBalance', params: [accountPayer.publicKey.toBase58(), {commitment: 'singleGossip'}], - }, - { - error: null, - result: { - context: { - slot: 11, - }, - value: LAMPORTS_PER_SOL - 1, - }, - }, - ]); + value: LAMPORTS_PER_SOL - 1, + withContext: true, + }); - // accountPayer should be less than LAMPORTS_PER_SOL as it paid for the transaction - // (exact amount less depends on the current cluster fees) - const balance = await connection.getBalance(accountPayer.publicKey); - expect(balance).toBeGreaterThan(0); - expect(balance).toBeLessThanOrEqual(LAMPORTS_PER_SOL); + // accountPayer should be less than LAMPORTS_PER_SOL as it paid for the transaction + // (exact amount less depends on the current cluster fees) + const balance = await connection.getBalance( + accountPayer.publicKey, + 'singleGossip', + ); + expect(balance).to.be.greaterThan(0); + expect(balance).to.be.at.most(LAMPORTS_PER_SOL); - // accountFrom should have exactly 2, since it didn't pay for the transaction - mockRpc.push([ - url, - { + // accountFrom should have exactly 2, since it didn't pay for the transaction + await mockRpcResponse({ method: 'getBalance', params: [accountFrom.publicKey.toBase58(), {commitment: 'singleGossip'}], - }, - { - error: null, - result: { - context: { - slot: 11, - }, - value: minimumAmount + 2, - }, - }, - ]); - expect(await connection.getBalance(accountFrom.publicKey)).toBe( - minimumAmount + 2, - ); + value: minimumAmount + 2, + withContext: true, + }); + expect( + await connection.getBalance(accountFrom.publicKey, 'singleGossip'), + ).to.eq(minimumAmount + 2); + }); }); diff --git a/web3.js/test/transaction.test.js b/web3.js/test/transaction.test.js index 8281089d24..b47ea76339 100644 --- a/web3.js/test/transaction.test.js +++ b/web3.js/test/transaction.test.js @@ -1,6 +1,8 @@ // @flow import bs58 from 'bs58'; +import {Buffer} from 'buffer'; import nacl from 'tweetnacl'; +import {expect, use} from 'chai'; import {Account} from '../src/account'; import {PublicKey} from '../src/publickey'; @@ -9,529 +11,529 @@ import {StakeProgram} from '../src/stake-program'; import {SystemProgram} from '../src/system-program'; import {Message} from '../src/message'; -describe('compileMessage', () => { - test('accountKeys are ordered', () => { - const payer = new Account(); +describe('Transaction', () => { + describe('compileMessage', () => { + it('accountKeys are ordered', () => { + const payer = new Account(); + const account2 = new Account(); + const account3 = new Account(); + const recentBlockhash = new Account().publicKey.toBase58(); + const programId = new Account().publicKey; + const transaction = new Transaction({recentBlockhash}).add({ + keys: [ + {pubkey: account3.publicKey, isSigner: true, isWritable: false}, + {pubkey: payer.publicKey, isSigner: true, isWritable: true}, + {pubkey: account2.publicKey, isSigner: true, isWritable: true}, + ], + programId, + }); + + transaction.setSigners( + payer.publicKey, + account2.publicKey, + account3.publicKey, + ); + + const message = transaction.compileMessage(); + expect(message.accountKeys[0]).to.eql(payer.publicKey); + expect(message.accountKeys[1]).to.eql(account2.publicKey); + expect(message.accountKeys[2]).to.eql(account3.publicKey); + }); + + it('payer is first account meta', () => { + const payer = new Account(); + const other = new Account(); + const recentBlockhash = new Account().publicKey.toBase58(); + const programId = new Account().publicKey; + const transaction = new Transaction({recentBlockhash}).add({ + keys: [ + {pubkey: other.publicKey, isSigner: true, isWritable: true}, + {pubkey: payer.publicKey, isSigner: true, isWritable: true}, + ], + programId, + }); + + transaction.sign(payer, other); + const message = transaction.compileMessage(); + expect(message.accountKeys[0]).to.eql(payer.publicKey); + expect(message.accountKeys[1]).to.eql(other.publicKey); + expect(message.header.numRequiredSignatures).to.eq(2); + expect(message.header.numReadonlySignedAccounts).to.eq(0); + expect(message.header.numReadonlyUnsignedAccounts).to.eq(1); + }); + + it('validation', () => { + const payer = new Account(); + const other = new Account(); + const recentBlockhash = new Account().publicKey.toBase58(); + const programId = new Account().publicKey; + + const transaction = new Transaction(); + expect(() => { + transaction.compileMessage(); + }).to.throw('Transaction recentBlockhash required'); + + transaction.recentBlockhash = recentBlockhash; + + expect(() => { + transaction.compileMessage(); + }).to.throw('No instructions provided'); + + transaction.add({ + keys: [ + {pubkey: other.publicKey, isSigner: true, isWritable: true}, + {pubkey: payer.publicKey, isSigner: true, isWritable: true}, + ], + programId, + }); + + expect(() => { + transaction.compileMessage(); + }).to.throw('Transaction fee payer required'); + + transaction.setSigners(payer.publicKey, new Account().publicKey); + + expect(() => { + transaction.compileMessage(); + }).to.throw('unknown signer'); + + // Expect compile to succeed with implicit fee payer from signers + transaction.setSigners(payer.publicKey); + transaction.compileMessage(); + + // Expect compile to succeed with fee payer and no signers + transaction.signatures = []; + transaction.feePayer = payer.publicKey; + transaction.compileMessage(); + }); + + it('payer is writable', () => { + const payer = new Account(); + const recentBlockhash = new Account().publicKey.toBase58(); + const programId = new Account().publicKey; + const transaction = new Transaction({recentBlockhash}).add({ + keys: [{pubkey: payer.publicKey, isSigner: true, isWritable: false}], + programId, + }); + + transaction.sign(payer); + const message = transaction.compileMessage(); + expect(message.accountKeys[0]).to.eql(payer.publicKey); + expect(message.header.numRequiredSignatures).to.eq(1); + expect(message.header.numReadonlySignedAccounts).to.eq(0); + expect(message.header.numReadonlyUnsignedAccounts).to.eq(1); + }); + }); + + it('partialSign', () => { + const account1 = new Account(); const account2 = new Account(); - const account3 = new Account(); - const recentBlockhash = new Account().publicKey.toBase58(); - const programId = new Account().publicKey; - const transaction = new Transaction({recentBlockhash}).add({ - keys: [ - {pubkey: account3.publicKey, isSigner: true, isWritable: false}, - {pubkey: payer.publicKey, isSigner: true, isWritable: true}, - {pubkey: account2.publicKey, isSigner: true, isWritable: true}, - ], - programId, - }); - - transaction.setSigners( - payer.publicKey, - account2.publicKey, - account3.publicKey, - ); - - const message = transaction.compileMessage(); - expect(message.accountKeys[0].equals(payer.publicKey)).toBe(true); - expect(message.accountKeys[1].equals(account2.publicKey)).toBe(true); - expect(message.accountKeys[2].equals(account3.publicKey)).toBe(true); - }); - - test('payer is first account meta', () => { - const payer = new Account(); - const other = new Account(); - const recentBlockhash = new Account().publicKey.toBase58(); - const programId = new Account().publicKey; - const transaction = new Transaction({recentBlockhash}).add({ - keys: [ - {pubkey: other.publicKey, isSigner: true, isWritable: true}, - {pubkey: payer.publicKey, isSigner: true, isWritable: true}, - ], - programId, - }); - - transaction.sign(payer, other); - const message = transaction.compileMessage(); - expect(message.accountKeys[0].equals(payer.publicKey)).toBe(true); - expect(message.accountKeys[1].equals(other.publicKey)).toBe(true); - expect(message.header.numRequiredSignatures).toEqual(2); - expect(message.header.numReadonlySignedAccounts).toEqual(0); - expect(message.header.numReadonlyUnsignedAccounts).toEqual(1); - }); - - test('validation', () => { - const payer = new Account(); - const other = new Account(); - const recentBlockhash = new Account().publicKey.toBase58(); - const programId = new Account().publicKey; - - const transaction = new Transaction(); - expect(() => { - transaction.compileMessage(); - }).toThrow('Transaction recentBlockhash required'); - - transaction.recentBlockhash = recentBlockhash; - - expect(() => { - transaction.compileMessage(); - }).toThrow('No instructions provided'); - - transaction.add({ - keys: [ - {pubkey: other.publicKey, isSigner: true, isWritable: true}, - {pubkey: payer.publicKey, isSigner: true, isWritable: true}, - ], - programId, - }); - - expect(() => { - transaction.compileMessage(); - }).toThrow('Transaction fee payer required'); - - transaction.setSigners(payer.publicKey, new Account().publicKey); - - expect(() => { - transaction.compileMessage(); - }).toThrow('unknown signer'); - - // Expect compile to succeed with implicit fee payer from signers - transaction.setSigners(payer.publicKey); - transaction.compileMessage(); - - // Expect compile to succeed with fee payer and no signers - transaction.signatures = []; - transaction.feePayer = payer.publicKey; - transaction.compileMessage(); - }); - - test('payer is writable', () => { - const payer = new Account(); - const recentBlockhash = new Account().publicKey.toBase58(); - const programId = new Account().publicKey; - const transaction = new Transaction({recentBlockhash}).add({ - keys: [{pubkey: payer.publicKey, isSigner: true, isWritable: false}], - programId, - }); - - transaction.sign(payer); - const message = transaction.compileMessage(); - expect(message.accountKeys[0].equals(payer.publicKey)).toBe(true); - expect(message.header.numRequiredSignatures).toEqual(1); - expect(message.header.numReadonlySignedAccounts).toEqual(0); - expect(message.header.numReadonlyUnsignedAccounts).toEqual(1); - }); -}); - -test('partialSign', () => { - const account1 = new Account(); - const account2 = new Account(); - const recentBlockhash = account1.publicKey.toBase58(); // Fake recentBlockhash - const transfer = SystemProgram.transfer({ - fromPubkey: account1.publicKey, - toPubkey: account2.publicKey, - lamports: 123, - }); - - const transaction = new Transaction({recentBlockhash}).add(transfer); - transaction.sign(account1, account2); - - const partialTransaction = new Transaction({recentBlockhash}).add(transfer); - partialTransaction.setSigners(account1.publicKey, account2.publicKey); - expect(partialTransaction.signatures[0].signature).toBeNull(); - expect(partialTransaction.signatures[1].signature).toBeNull(); - - partialTransaction.partialSign(account1); - expect(partialTransaction.signatures[0].signature).not.toBeNull(); - expect(partialTransaction.signatures[1].signature).toBeNull(); - - expect(() => partialTransaction.serialize()).toThrow(); - expect(() => - partialTransaction.serialize({requireAllSignatures: false}), - ).not.toThrow(); - - partialTransaction.partialSign(account2); - - expect(partialTransaction.signatures[0].signature).not.toBeNull(); - expect(partialTransaction.signatures[1].signature).not.toBeNull(); - - expect(() => partialTransaction.serialize()).not.toThrow(); - - expect(partialTransaction).toEqual(transaction); - - if ( - partialTransaction.signatures[0].signature != null /* <-- pacify flow */ - ) { - partialTransaction.signatures[0].signature[0] = 0; - expect(() => - partialTransaction.serialize({requireAllSignatures: false}), - ).toThrow(); - expect(() => - partialTransaction.serialize({ - verifySignatures: false, - requireAllSignatures: false, - }), - ).not.toThrow(); - } else { - throw new Error('unreachable'); - } -}); - -describe('dedupe', () => { - const payer = new Account(); - const duplicate1 = payer; - const duplicate2 = payer; - const recentBlockhash = new Account().publicKey.toBase58(); - const programId = new Account().publicKey; - - test('setSigners', () => { - const transaction = new Transaction({recentBlockhash}).add({ - keys: [ - {pubkey: duplicate1.publicKey, isSigner: true, isWritable: true}, - {pubkey: payer.publicKey, isSigner: false, isWritable: true}, - {pubkey: duplicate2.publicKey, isSigner: true, isWritable: false}, - ], - programId, - }); - - transaction.setSigners( - payer.publicKey, - duplicate1.publicKey, - duplicate2.publicKey, - ); - - expect(transaction.signatures.length).toEqual(1); - expect(transaction.signatures[0].publicKey.equals(payer.publicKey)).toBe( - true, - ); - - const message = transaction.compileMessage(); - expect(message.accountKeys[0].equals(payer.publicKey)).toBe(true); - expect(message.header.numRequiredSignatures).toEqual(1); - expect(message.header.numReadonlySignedAccounts).toEqual(0); - expect(message.header.numReadonlyUnsignedAccounts).toEqual(1); - - transaction.signatures; - }); - - test('sign', () => { - const transaction = new Transaction({recentBlockhash}).add({ - keys: [ - {pubkey: duplicate1.publicKey, isSigner: true, isWritable: true}, - {pubkey: payer.publicKey, isSigner: false, isWritable: true}, - {pubkey: duplicate2.publicKey, isSigner: true, isWritable: false}, - ], - programId, - }); - - transaction.sign(payer, duplicate1, duplicate2); - - expect(transaction.signatures.length).toEqual(1); - expect(transaction.signatures[0].publicKey.equals(payer.publicKey)).toBe( - true, - ); - - const message = transaction.compileMessage(); - expect(message.accountKeys[0].equals(payer.publicKey)).toBe(true); - expect(message.header.numRequiredSignatures).toEqual(1); - expect(message.header.numReadonlySignedAccounts).toEqual(0); - expect(message.header.numReadonlyUnsignedAccounts).toEqual(1); - - transaction.signatures; - }); -}); - -test('transfer signatures', () => { - const account1 = new Account(); - const account2 = new Account(); - const recentBlockhash = account1.publicKey.toBase58(); // Fake recentBlockhash - const transfer1 = SystemProgram.transfer({ - fromPubkey: account1.publicKey, - toPubkey: account2.publicKey, - lamports: 123, - }); - const transfer2 = SystemProgram.transfer({ - fromPubkey: account2.publicKey, - toPubkey: account1.publicKey, - lamports: 123, - }); - - const orgTransaction = new Transaction({recentBlockhash}).add( - transfer1, - transfer2, - ); - orgTransaction.sign(account1, account2); - - const newTransaction = new Transaction({ - recentBlockhash: orgTransaction.recentBlockhash, - feePayer: orgTransaction.feePayer, - signatures: orgTransaction.signatures, - }).add(transfer1, transfer2); - - expect(newTransaction).toEqual(orgTransaction); -}); - -test('dedup signatures', () => { - const account1 = new Account(); - const account2 = new Account(); - const recentBlockhash = account1.publicKey.toBase58(); // Fake recentBlockhash - const transfer1 = SystemProgram.transfer({ - fromPubkey: account1.publicKey, - toPubkey: account2.publicKey, - lamports: 123, - }); - const transfer2 = SystemProgram.transfer({ - fromPubkey: account1.publicKey, - toPubkey: account2.publicKey, - lamports: 123, - }); - - const orgTransaction = new Transaction({recentBlockhash}).add( - transfer1, - transfer2, - ); - orgTransaction.sign(account1); -}); - -test('use nonce', () => { - const account1 = new Account(); - const account2 = new Account(); - const nonceAccount = new Account(); - const nonce = account2.publicKey.toBase58(); // Fake Nonce hash - - const nonceInfo = { - nonce, - nonceInstruction: SystemProgram.nonceAdvance({ - noncePubkey: nonceAccount.publicKey, - authorizedPubkey: account1.publicKey, - }), - }; - - const transferTransaction = new Transaction({nonceInfo}).add( - SystemProgram.transfer({ + const recentBlockhash = account1.publicKey.toBase58(); // Fake recentBlockhash + const transfer = SystemProgram.transfer({ fromPubkey: account1.publicKey, toPubkey: account2.publicKey, lamports: 123, - }), - ); - transferTransaction.sign(account1); + }); - let expectedData = Buffer.alloc(4); - expectedData.writeInt32LE(4, 0); + const transaction = new Transaction({recentBlockhash}).add(transfer); + transaction.sign(account1, account2); - expect(transferTransaction.instructions).toHaveLength(2); - expect(transferTransaction.instructions[0].programId).toEqual( - SystemProgram.programId, - ); - expect(transferTransaction.instructions[0].data).toEqual(expectedData); - expect(transferTransaction.recentBlockhash).toEqual(nonce); + const partialTransaction = new Transaction({recentBlockhash}).add(transfer); + partialTransaction.setSigners(account1.publicKey, account2.publicKey); + expect(partialTransaction.signatures[0].signature).to.be.null; + expect(partialTransaction.signatures[1].signature).to.be.null; - const stakeAccount = new Account(); - const voteAccount = new Account(); - const stakeTransaction = new Transaction({nonceInfo}).add( - StakeProgram.delegate({ - stakePubkey: stakeAccount.publicKey, - authorizedPubkey: account1.publicKey, - votePubkey: voteAccount.publicKey, - }), - ); - stakeTransaction.sign(account1); + partialTransaction.partialSign(account1); + expect(partialTransaction.signatures[0].signature).not.to.be.null; + expect(partialTransaction.signatures[1].signature).to.be.null; - expect(stakeTransaction.instructions).toHaveLength(2); - expect(stakeTransaction.instructions[0].programId).toEqual( - SystemProgram.programId, - ); - expect(stakeTransaction.instructions[0].data).toEqual(expectedData); - expect(stakeTransaction.recentBlockhash).toEqual(nonce); -}); + expect(() => partialTransaction.serialize()).to.throw(); + expect(() => + partialTransaction.serialize({requireAllSignatures: false}), + ).not.to.throw(); -test('parse wire format and serialize', () => { - const keypair = nacl.sign.keyPair.fromSeed( - Uint8Array.from(Array(32).fill(8)), - ); - const sender = new Account(Buffer.from(keypair.secretKey)); // Arbitrary known account - const recentBlockhash = 'EETubP5AKHgjPAhzPAFcb8BAY1hMH639CWCFTqi3hq1k'; // Arbitrary known recentBlockhash - const recipient = new PublicKey( - 'J3dxNj7nDRRqRRXuEMynDG57DkZK4jYRuv3Garmb1i99', - ); // Arbitrary known public key - const transfer = SystemProgram.transfer({ - fromPubkey: sender.publicKey, - toPubkey: recipient, - lamports: 49, + partialTransaction.partialSign(account2); + + expect(partialTransaction.signatures[0].signature).not.to.be.null; + expect(partialTransaction.signatures[1].signature).not.to.be.null; + + expect(() => partialTransaction.serialize()).not.to.throw(); + + expect(partialTransaction).to.eql(transaction); + + if ( + partialTransaction.signatures[0].signature != null /* <-- pacify flow */ + ) { + partialTransaction.signatures[0].signature[0] = 0; + expect(() => + partialTransaction.serialize({requireAllSignatures: false}), + ).to.throw(); + expect(() => + partialTransaction.serialize({ + verifySignatures: false, + requireAllSignatures: false, + }), + ).not.to.throw(); + } else { + throw new Error('unreachable'); + } }); - const expectedTransaction = new Transaction({ - recentBlockhash, - feePayer: sender.publicKey, - }).add(transfer); - expectedTransaction.sign(sender); - const wireTransaction = Buffer.from( - 'AVuErQHaXv0SG0/PchunfxHKt8wMRfMZzqV0tkC5qO6owYxWU2v871AoWywGoFQr4z+q/7mE8lIufNl/kxj+nQ0BAAEDE5j2LG0aRXxRumpLXz29L2n8qTIWIY3ImX5Ba9F9k8r9Q5/Mtmcn8onFxt47xKj+XdXXd3C8j/FcPu7csUrz/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAxJrndgN4IFTxep3s6kO0ROug7bEsbx0xxuDkqEvwUusBAgIAAQwCAAAAMQAAAAAAAAA=', - 'base64', - ); - const tx = Transaction.from(wireTransaction); + describe('dedupe', () => { + const payer = new Account(); + const duplicate1 = payer; + const duplicate2 = payer; + const recentBlockhash = new Account().publicKey.toBase58(); + const programId = new Account().publicKey; - expect(tx).toEqual(expectedTransaction); - expect(wireTransaction).toEqual(expectedTransaction.serialize()); -}); + it('setSigners', () => { + const transaction = new Transaction({recentBlockhash}).add({ + keys: [ + {pubkey: duplicate1.publicKey, isSigner: true, isWritable: true}, + {pubkey: payer.publicKey, isSigner: false, isWritable: true}, + {pubkey: duplicate2.publicKey, isSigner: true, isWritable: false}, + ], + programId, + }); -test('populate transaction', () => { - const recentBlockhash = new PublicKey(1).toString(); - const message = { - accountKeys: [ - new PublicKey(1).toString(), - new PublicKey(2).toString(), - new PublicKey(3).toString(), - new PublicKey(4).toString(), - new PublicKey(5).toString(), - ], - header: { - numReadonlySignedAccounts: 0, - numReadonlyUnsignedAccounts: 3, - numRequiredSignatures: 2, - }, - instructions: [ - { - accounts: [1, 2, 3], - data: bs58.encode(Buffer.alloc(5).fill(9)), - programIdIndex: 4, + transaction.setSigners( + payer.publicKey, + duplicate1.publicKey, + duplicate2.publicKey, + ); + + expect(transaction.signatures).to.have.length(1); + expect(transaction.signatures[0].publicKey).to.eql(payer.publicKey); + + const message = transaction.compileMessage(); + expect(message.accountKeys[0]).to.eql(payer.publicKey); + expect(message.header.numRequiredSignatures).to.eq(1); + expect(message.header.numReadonlySignedAccounts).to.eq(0); + expect(message.header.numReadonlyUnsignedAccounts).to.eq(1); + + transaction.signatures; + }); + + it('sign', () => { + const transaction = new Transaction({recentBlockhash}).add({ + keys: [ + {pubkey: duplicate1.publicKey, isSigner: true, isWritable: true}, + {pubkey: payer.publicKey, isSigner: false, isWritable: true}, + {pubkey: duplicate2.publicKey, isSigner: true, isWritable: false}, + ], + programId, + }); + + transaction.sign(payer, duplicate1, duplicate2); + + expect(transaction.signatures).to.have.length(1); + expect(transaction.signatures[0].publicKey).to.eql(payer.publicKey); + + const message = transaction.compileMessage(); + expect(message.accountKeys[0]).to.eql(payer.publicKey); + expect(message.header.numRequiredSignatures).to.eq(1); + expect(message.header.numReadonlySignedAccounts).to.eq(0); + expect(message.header.numReadonlyUnsignedAccounts).to.eq(1); + + transaction.signatures; + }); + }); + + it('transfer signatures', () => { + const account1 = new Account(); + const account2 = new Account(); + const recentBlockhash = account1.publicKey.toBase58(); // Fake recentBlockhash + const transfer1 = SystemProgram.transfer({ + fromPubkey: account1.publicKey, + toPubkey: account2.publicKey, + lamports: 123, + }); + const transfer2 = SystemProgram.transfer({ + fromPubkey: account2.publicKey, + toPubkey: account1.publicKey, + lamports: 123, + }); + + const orgTransaction = new Transaction({recentBlockhash}).add( + transfer1, + transfer2, + ); + orgTransaction.sign(account1, account2); + + const newTransaction = new Transaction({ + recentBlockhash: orgTransaction.recentBlockhash, + feePayer: orgTransaction.feePayer, + signatures: orgTransaction.signatures, + }).add(transfer1, transfer2); + + expect(newTransaction).to.eql(orgTransaction); + }); + + it('dedup signatures', () => { + const account1 = new Account(); + const account2 = new Account(); + const recentBlockhash = account1.publicKey.toBase58(); // Fake recentBlockhash + const transfer1 = SystemProgram.transfer({ + fromPubkey: account1.publicKey, + toPubkey: account2.publicKey, + lamports: 123, + }); + const transfer2 = SystemProgram.transfer({ + fromPubkey: account1.publicKey, + toPubkey: account2.publicKey, + lamports: 123, + }); + + const orgTransaction = new Transaction({recentBlockhash}).add( + transfer1, + transfer2, + ); + orgTransaction.sign(account1); + }); + + it('use nonce', () => { + const account1 = new Account(); + const account2 = new Account(); + const nonceAccount = new Account(); + const nonce = account2.publicKey.toBase58(); // Fake Nonce hash + + const nonceInfo = { + nonce, + nonceInstruction: SystemProgram.nonceAdvance({ + noncePubkey: nonceAccount.publicKey, + authorizedPubkey: account1.publicKey, + }), + }; + + const transferTransaction = new Transaction({nonceInfo}).add( + SystemProgram.transfer({ + fromPubkey: account1.publicKey, + toPubkey: account2.publicKey, + lamports: 123, + }), + ); + transferTransaction.sign(account1); + + let expectedData = Buffer.alloc(4); + expectedData.writeInt32LE(4, 0); + + expect(transferTransaction.instructions).to.have.length(2); + expect(transferTransaction.instructions[0].programId).to.eql( + SystemProgram.programId, + ); + expect(transferTransaction.instructions[0].data).to.eql(expectedData); + expect(transferTransaction.recentBlockhash).to.eq(nonce); + + const stakeAccount = new Account(); + const voteAccount = new Account(); + const stakeTransaction = new Transaction({nonceInfo}).add( + StakeProgram.delegate({ + stakePubkey: stakeAccount.publicKey, + authorizedPubkey: account1.publicKey, + votePubkey: voteAccount.publicKey, + }), + ); + stakeTransaction.sign(account1); + + expect(stakeTransaction.instructions).to.have.length(2); + expect(stakeTransaction.instructions[0].programId).to.eql( + SystemProgram.programId, + ); + expect(stakeTransaction.instructions[0].data).to.eql(expectedData); + expect(stakeTransaction.recentBlockhash).to.eq(nonce); + }); + + it('parse wire format and serialize', () => { + const keypair = nacl.sign.keyPair.fromSeed( + Uint8Array.from(Array(32).fill(8)), + ); + const sender = new Account(Buffer.from(keypair.secretKey)); // Arbitrary known account + const recentBlockhash = 'EETubP5AKHgjPAhzPAFcb8BAY1hMH639CWCFTqi3hq1k'; // Arbitrary known recentBlockhash + const recipient = new PublicKey( + 'J3dxNj7nDRRqRRXuEMynDG57DkZK4jYRuv3Garmb1i99', + ); // Arbitrary known public key + const transfer = SystemProgram.transfer({ + fromPubkey: sender.publicKey, + toPubkey: recipient, + lamports: 49, + }); + const expectedTransaction = new Transaction({ + recentBlockhash, + feePayer: sender.publicKey, + }).add(transfer); + expectedTransaction.sign(sender); + + const wireTransaction = Buffer.from( + 'AVuErQHaXv0SG0/PchunfxHKt8wMRfMZzqV0tkC5qO6owYxWU2v871AoWywGoFQr4z+q/7mE8lIufNl/kxj+nQ0BAAEDE5j2LG0aRXxRumpLXz29L2n8qTIWIY3ImX5Ba9F9k8r9Q5/Mtmcn8onFxt47xKj+XdXXd3C8j/FcPu7csUrz/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAxJrndgN4IFTxep3s6kO0ROug7bEsbx0xxuDkqEvwUusBAgIAAQwCAAAAMQAAAAAAAAA=', + 'base64', + ); + const tx = Transaction.from(wireTransaction); + + expect(tx).to.eql(expectedTransaction); + expect(wireTransaction).to.eql(expectedTransaction.serialize()); + }); + + it('populate transaction', () => { + const recentBlockhash = new PublicKey(1).toString(); + const message = { + accountKeys: [ + new PublicKey(1).toString(), + new PublicKey(2).toString(), + new PublicKey(3).toString(), + new PublicKey(4).toString(), + new PublicKey(5).toString(), + ], + header: { + numReadonlySignedAccounts: 0, + numReadonlyUnsignedAccounts: 3, + numRequiredSignatures: 2, }, - ], - recentBlockhash, - }; + instructions: [ + { + accounts: [1, 2, 3], + data: bs58.encode(Buffer.alloc(5).fill(9)), + programIdIndex: 4, + }, + ], + recentBlockhash, + }; - const signatures = [ - bs58.encode(Buffer.alloc(64).fill(1)), - bs58.encode(Buffer.alloc(64).fill(2)), - ]; + const signatures = [ + bs58.encode(Buffer.alloc(64).fill(1)), + bs58.encode(Buffer.alloc(64).fill(2)), + ]; - const transaction = Transaction.populate(new Message(message), signatures); - expect(transaction.instructions.length).toEqual(1); - expect(transaction.signatures.length).toEqual(2); - expect(transaction.recentBlockhash).toEqual(recentBlockhash); -}); - -test('serialize unsigned transaction', () => { - const keypair = nacl.sign.keyPair.fromSeed( - Uint8Array.from(Array(32).fill(8)), - ); - const sender = new Account(Buffer.from(keypair.secretKey)); // Arbitrary known account - const recentBlockhash = 'EETubP5AKHgjPAhzPAFcb8BAY1hMH639CWCFTqi3hq1k'; // Arbitrary known recentBlockhash - const recipient = new PublicKey( - 'J3dxNj7nDRRqRRXuEMynDG57DkZK4jYRuv3Garmb1i99', - ); // Arbitrary known public key - const transfer = SystemProgram.transfer({ - fromPubkey: sender.publicKey, - toPubkey: recipient, - lamports: 49, + const transaction = Transaction.populate(new Message(message), signatures); + expect(transaction.instructions).to.have.length(1); + expect(transaction.signatures).to.have.length(2); + expect(transaction.recentBlockhash).to.eq(recentBlockhash); }); - const expectedTransaction = new Transaction({recentBlockhash}).add(transfer); - // Empty signature array fails. - expect(expectedTransaction.signatures.length).toBe(0); - expect(() => { - expectedTransaction.serialize(); - }).toThrow('Transaction fee payer required'); - expect(() => { + it('serialize unsigned transaction', () => { + const keypair = nacl.sign.keyPair.fromSeed( + Uint8Array.from(Array(32).fill(8)), + ); + const sender = new Account(Buffer.from(keypair.secretKey)); // Arbitrary known account + const recentBlockhash = 'EETubP5AKHgjPAhzPAFcb8BAY1hMH639CWCFTqi3hq1k'; // Arbitrary known recentBlockhash + const recipient = new PublicKey( + 'J3dxNj7nDRRqRRXuEMynDG57DkZK4jYRuv3Garmb1i99', + ); // Arbitrary known public key + const transfer = SystemProgram.transfer({ + fromPubkey: sender.publicKey, + toPubkey: recipient, + lamports: 49, + }); + const expectedTransaction = new Transaction({recentBlockhash}).add( + transfer, + ); + + // Empty signature array fails. + expect(expectedTransaction.signatures).to.have.length(0); + expect(() => { + expectedTransaction.serialize(); + }).to.throw('Transaction fee payer required'); + expect(() => { + expectedTransaction.serialize({verifySignatures: false}); + }).to.throw('Transaction fee payer required'); + expect(() => { + expectedTransaction.serializeMessage(); + }).to.throw('Transaction fee payer required'); + + expectedTransaction.feePayer = sender.publicKey; + + // Transactions with missing signatures will fail sigverify. + expect(() => { + expectedTransaction.serialize(); + }).to.throw('Signature verification failed'); + + // Serializing without signatures is allowed if sigverify disabled. expectedTransaction.serialize({verifySignatures: false}); - }).toThrow('Transaction fee payer required'); - expect(() => { + + // Serializing the message is allowed when signature array has null signatures expectedTransaction.serializeMessage(); - }).toThrow('Transaction fee payer required'); - expectedTransaction.feePayer = sender.publicKey; + expectedTransaction.feePayer = null; + expectedTransaction.setSigners(sender.publicKey); + expect(expectedTransaction.signatures).to.have.length(1); - // Transactions with missing signatures will fail sigverify. - expect(() => { - expectedTransaction.serialize(); - }).toThrow('Signature verification failed'); + // Transactions with missing signatures will fail sigverify. + expect(() => { + expectedTransaction.serialize(); + }).to.throw('Signature verification failed'); - // Serializing without signatures is allowed if sigverify disabled. - expectedTransaction.serialize({verifySignatures: false}); + // Serializing without signatures is allowed if sigverify disabled. + expectedTransaction.serialize({verifySignatures: false}); - // Serializing the message is allowed when signature array has null signatures - expectedTransaction.serializeMessage(); + // Serializing the message is allowed when signature array has null signatures + expectedTransaction.serializeMessage(); - expectedTransaction.feePayer = null; - expectedTransaction.setSigners(sender.publicKey); - expect(expectedTransaction.signatures.length).toBe(1); + const expectedSerializationWithNoSignatures = Buffer.from( + 'AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + + 'AAAAAAAAAAAAAAAAAAABAAEDE5j2LG0aRXxRumpLXz29L2n8qTIWIY3ImX5Ba9F9k8r9' + + 'Q5/Mtmcn8onFxt47xKj+XdXXd3C8j/FcPu7csUrz/AAAAAAAAAAAAAAAAAAAAAAAAAAA' + + 'AAAAAAAAAAAAAAAAxJrndgN4IFTxep3s6kO0ROug7bEsbx0xxuDkqEvwUusBAgIAAQwC' + + 'AAAAMQAAAAAAAAA=', + 'base64', + ); + expect(expectedTransaction.serialize({requireAllSignatures: false})).to.eql( + expectedSerializationWithNoSignatures, + ); - // Transactions with missing signatures will fail sigverify. - expect(() => { - expectedTransaction.serialize(); - }).toThrow('Signature verification failed'); - - // Serializing without signatures is allowed if sigverify disabled. - expectedTransaction.serialize({verifySignatures: false}); - - // Serializing the message is allowed when signature array has null signatures - expectedTransaction.serializeMessage(); - - const expectedSerializationWithNoSignatures = Buffer.from( - 'AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + - 'AAAAAAAAAAAAAAAAAAABAAEDE5j2LG0aRXxRumpLXz29L2n8qTIWIY3ImX5Ba9F9k8r9' + - 'Q5/Mtmcn8onFxt47xKj+XdXXd3C8j/FcPu7csUrz/AAAAAAAAAAAAAAAAAAAAAAAAAAA' + - 'AAAAAAAAAAAAAAAAxJrndgN4IFTxep3s6kO0ROug7bEsbx0xxuDkqEvwUusBAgIAAQwC' + - 'AAAAMQAAAAAAAAA=', - 'base64', - ); - expect( - expectedTransaction.serialize({requireAllSignatures: false}), - ).toStrictEqual(expectedSerializationWithNoSignatures); - - // Properly signed transaction succeeds - expectedTransaction.partialSign(sender); - expect(expectedTransaction.signatures.length).toBe(1); - const expectedSerialization = Buffer.from( - 'AVuErQHaXv0SG0/PchunfxHKt8wMRfMZzqV0tkC5qO6owYxWU2v871AoWywGoFQr4z+q/7mE8lIufNl/' + - 'kxj+nQ0BAAEDE5j2LG0aRXxRumpLXz29L2n8qTIWIY3ImX5Ba9F9k8r9Q5/Mtmcn8onFxt47xKj+XdXX' + - 'd3C8j/FcPu7csUrz/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAxJrndgN4IFTxep3s6kO0' + - 'ROug7bEsbx0xxuDkqEvwUusBAgIAAQwCAAAAMQAAAAAAAAA=', - 'base64', - ); - expect(expectedTransaction.serialize()).toStrictEqual(expectedSerialization); - expect(expectedTransaction.signatures.length).toBe(1); -}); - -test('deprecated - externally signed stake delegate', () => { - const from_keypair = nacl.sign.keyPair.fromSeed( - Uint8Array.from(Array(32).fill(1)), - ); - const authority = new Account(Buffer.from(from_keypair.secretKey)); - const stake = new PublicKey(2); - const recentBlockhash = new PublicKey(3).toBuffer(); - const vote = new PublicKey(4); - var tx = StakeProgram.delegate({ - stakePubkey: stake, - authorizedPubkey: authority.publicKey, - votePubkey: vote, + // Properly signed transaction succeeds + expectedTransaction.partialSign(sender); + expect(expectedTransaction.signatures).to.have.length(1); + const expectedSerialization = Buffer.from( + 'AVuErQHaXv0SG0/PchunfxHKt8wMRfMZzqV0tkC5qO6owYxWU2v871AoWywGoFQr4z+q/7mE8lIufNl/' + + 'kxj+nQ0BAAEDE5j2LG0aRXxRumpLXz29L2n8qTIWIY3ImX5Ba9F9k8r9Q5/Mtmcn8onFxt47xKj+XdXX' + + 'd3C8j/FcPu7csUrz/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAxJrndgN4IFTxep3s6kO0' + + 'ROug7bEsbx0xxuDkqEvwUusBAgIAAQwCAAAAMQAAAAAAAAA=', + 'base64', + ); + expect(expectedTransaction.serialize()).to.eql(expectedSerialization); + expect(expectedTransaction.signatures).to.have.length(1); }); - const from = authority; - tx.recentBlockhash = bs58.encode(recentBlockhash); - tx.setSigners(from.publicKey); - const tx_bytes = tx.serializeMessage(); - const signature = nacl.sign.detached(tx_bytes, from.secretKey); - tx.addSignature(from.publicKey, signature); - expect(tx.verifySignatures()).toBe(true); -}); -test('externally signed stake delegate', () => { - const from_keypair = nacl.sign.keyPair.fromSeed( - Uint8Array.from(Array(32).fill(1)), - ); - const authority = new Account(Buffer.from(from_keypair.secretKey)); - const stake = new PublicKey(2); - const recentBlockhash = new PublicKey(3).toBuffer(); - const vote = new PublicKey(4); - var tx = StakeProgram.delegate({ - stakePubkey: stake, - authorizedPubkey: authority.publicKey, - votePubkey: vote, + it('deprecated - externally signed stake delegate', () => { + const from_keypair = nacl.sign.keyPair.fromSeed( + Uint8Array.from(Array(32).fill(1)), + ); + const authority = new Account(Buffer.from(from_keypair.secretKey)); + const stake = new PublicKey(2); + const recentBlockhash = new PublicKey(3).toBuffer(); + const vote = new PublicKey(4); + var tx = StakeProgram.delegate({ + stakePubkey: stake, + authorizedPubkey: authority.publicKey, + votePubkey: vote, + }); + const from = authority; + tx.recentBlockhash = bs58.encode(recentBlockhash); + tx.setSigners(from.publicKey); + const tx_bytes = tx.serializeMessage(); + const signature = nacl.sign.detached(tx_bytes, from.secretKey); + tx.addSignature(from.publicKey, signature); + expect(tx.verifySignatures()).to.be.true; + }); + + it('externally signed stake delegate', () => { + const from_keypair = nacl.sign.keyPair.fromSeed( + Uint8Array.from(Array(32).fill(1)), + ); + const authority = new Account(Buffer.from(from_keypair.secretKey)); + const stake = new PublicKey(2); + const recentBlockhash = new PublicKey(3).toBuffer(); + const vote = new PublicKey(4); + var tx = StakeProgram.delegate({ + stakePubkey: stake, + authorizedPubkey: authority.publicKey, + votePubkey: vote, + }); + const from = authority; + tx.recentBlockhash = bs58.encode(recentBlockhash); + tx.feePayer = from.publicKey; + const tx_bytes = tx.serializeMessage(); + const signature = nacl.sign.detached(tx_bytes, from.secretKey); + tx.addSignature(from.publicKey, signature); + expect(tx.verifySignatures()).to.be.true; }); - const from = authority; - tx.recentBlockhash = bs58.encode(recentBlockhash); - tx.feePayer = from.publicKey; - const tx_bytes = tx.serializeMessage(); - const signature = nacl.sign.detached(tx_bytes, from.secretKey); - tx.addSignature(from.publicKey, signature); - expect(tx.verifySignatures()).toBe(true); }); diff --git a/web3.js/test/url.js b/web3.js/test/url.js index 3a3f4bb941..be667c4a96 100644 --- a/web3.js/test/url.js +++ b/web3.js/test/url.js @@ -4,7 +4,10 @@ * The connection url to use when running unit tests against a live cluster */ -export const url = 'http://localhost:8899/'; +export const MOCK_PORT = 9999; +export const url = process.env.TEST_LIVE + ? 'http://localhost:8899/' + : 'http://localhost:9999/'; //export const url = 'https://devnet.solana.com/'; //export const url = 'http://devnet.solana.com/'; diff --git a/web3.js/test/validator-info.test.js b/web3.js/test/validator-info.test.js index 8cb966311b..4fbf88d1aa 100644 --- a/web3.js/test/validator-info.test.js +++ b/web3.js/test/validator-info.test.js @@ -1,36 +1,40 @@ // @flow +import {Buffer} from 'buffer'; +import {expect} from 'chai'; import nacl from 'tweetnacl'; import {PublicKey} from '../src/publickey'; import {ValidatorInfo} from '../src/validator-info'; -test('from config account data', () => { - const keypair = nacl.sign.keyPair.fromSeed( - Uint8Array.from(Array(32).fill(8)), - ); +describe('ValidatorInfo', () => { + it('from config account data', () => { + const keypair = nacl.sign.keyPair.fromSeed( + Uint8Array.from(Array(32).fill(8)), + ); - const expectedValidatorInfo = new ValidatorInfo( - new PublicKey(keypair.publicKey), - { - name: 'Validator', - keybaseUsername: 'validator_id', - }, - ); + const expectedValidatorInfo = new ValidatorInfo( + new PublicKey(keypair.publicKey), + { + name: 'Validator', + keybaseUsername: 'validator_id', + }, + ); - // Config data string steps: - // 1) Generate a keypair - // 2) Airdrop lamports to the account - // 3) Modify the `solana-validator-info` tool - // a) Remove the keybase id verification step - // b) Print base64 account data in the `get --all` codepath - // c) Add `println!("Account data: {:?}", base64::encode(&account.data));` - // 4) Use modified `solana-validator-info` tool to publish validator info - // 5) And then use it again to fetch the data! (feel free to trim some A's) - const configData = Buffer.from( - 'AgdRlwF0SPKsXcI8nrx6x4wKJyV6xhRFjeCk8W+AAAAAABOY9ixtGkV8UbpqS189vS9p/KkyFiGNyJl+QWvRfZPKATUAAAAAAAAAeyJrZXliYXNlVXNlcm5hbWUiOiJ2YWxpZGF0b3JfaWQiLCJuYW1lIjoiVmFsaWRhdG9yIn0', - 'base64', - ); - const info = ValidatorInfo.fromConfigData(configData); + // Config data string steps: + // 1) Generate a keypair + // 2) Airdrop lamports to the account + // 3) Modify the `solana-validator-info` tool + // a) Remove the keybase id verification step + // b) Print base64 account data in the `get --all` codepath + // c) Add `println!("Account data: {:?}", base64::encode(&account.data));` + // 4) Use modified `solana-validator-info` tool to publish validator info + // 5) And then use it again to fetch the data! (feel free to trim some A's) + const configData = Buffer.from( + 'AgdRlwF0SPKsXcI8nrx6x4wKJyV6xhRFjeCk8W+AAAAAABOY9ixtGkV8UbpqS189vS9p/KkyFiGNyJl+QWvRfZPKATUAAAAAAAAAeyJrZXliYXNlVXNlcm5hbWUiOiJ2YWxpZGF0b3JfaWQiLCJuYW1lIjoiVmFsaWRhdG9yIn0', + 'base64', + ); + const info = ValidatorInfo.fromConfigData(configData); - expect(info).toEqual(expectedValidatorInfo); + expect(info).to.eql(expectedValidatorInfo); + }); }); diff --git a/web3.js/test/websocket.test.js b/web3.js/test/websocket.test.js index 39fac8fb32..0ffbc6c410 100644 --- a/web3.js/test/websocket.test.js +++ b/web3.js/test/websocket.test.js @@ -1,68 +1,68 @@ // @flow import bs58 from 'bs58'; +import {Buffer} from 'buffer'; +import {expect, use} from 'chai'; +import chaiAsPromised from 'chai-as-promised'; import {Connection} from '../src'; import {url} from './url'; -import {mockRpcEnabled} from './__mocks__/node-fetch'; import {sleep} from '../src/util/sleep'; -describe('websocket', () => { - if (mockRpcEnabled) { - test('no-op', () => {}); - console.log('non-live test skipped'); - return; - } +use(chaiAsPromised); - const connection = new Connection(url); +if (process.env.TEST_LIVE) { + describe('websocket', () => { + const connection = new Connection(url); - test('connect and disconnect', async () => { - const testSignature = bs58.encode(Buffer.alloc(64)); - const id = connection.onSignature(testSignature, () => {}); + it('connect and disconnect', async () => { + const testSignature = bs58.encode(Buffer.alloc(64)); + const id = connection.onSignature(testSignature, () => {}); - // wait for websocket to connect - await sleep(100); - expect(connection._rpcWebSocketConnected).toBe(true); - expect(connection._rpcWebSocketHeartbeat).not.toBe(null); + // wait for websocket to connect + await sleep(100); + expect(connection._rpcWebSocketConnected).to.be.true; + expect(connection._rpcWebSocketHeartbeat).not.to.eq(null); - // test if socket is open - await connection._rpcWebSocket.notify('ping'); + // test if socket is open + await connection._rpcWebSocket.notify('ping'); - await connection.removeSignatureListener(id); - expect(connection._rpcWebSocketConnected).toBe(false); - expect(connection._rpcWebSocketHeartbeat).not.toBe(null); - expect(connection._rpcWebSocketIdleTimeout).not.toBe(null); + await connection.removeSignatureListener(id); + expect(connection._rpcWebSocketConnected).to.eq(false); + expect(connection._rpcWebSocketHeartbeat).not.to.eq(null); + expect(connection._rpcWebSocketIdleTimeout).not.to.eq(null); - // wait for websocket to disconnect - await sleep(1100); - expect(connection._rpcWebSocketConnected).toBe(false); - expect(connection._rpcWebSocketHeartbeat).toBe(null); - expect(connection._rpcWebSocketIdleTimeout).toBe(null); + // wait for websocket to disconnect + await sleep(1100); + expect(connection._rpcWebSocketConnected).to.eq(false); + expect(connection._rpcWebSocketHeartbeat).to.eq(null); + expect(connection._rpcWebSocketIdleTimeout).to.eq(null); - // test if socket is closed - await expect(connection._rpcWebSocket.notify('ping')).rejects.toThrow( - 'socket not ready', - ); + // test if socket is closed + await expect(connection._rpcWebSocket.notify('ping')).to.be.rejectedWith( + 'socket not ready', + ); + }); + + it('idle timeout', async () => { + const testSignature = bs58.encode(Buffer.alloc(64)); + const id = connection.onSignature(testSignature, () => {}); + + // wait for websocket to connect + await sleep(100); + expect(connection._rpcWebSocketIdleTimeout).to.eq(null); + + await connection.removeSignatureListener(id); + expect(connection._rpcWebSocketIdleTimeout).not.to.eq(null); + + const nextId = connection.onSignature(testSignature, () => {}); + expect(connection._rpcWebSocketIdleTimeout).to.eq(null); + + await connection.removeSignatureListener(nextId); + expect(connection._rpcWebSocketIdleTimeout).not.to.eq(null); + + // wait for websocket to disconnect + await sleep(1100); + expect(connection._rpcWebSocketIdleTimeout).to.eq(null); + }); }); - - test('idle timeout', async () => { - const testSignature = bs58.encode(Buffer.alloc(64)); - const id = connection.onSignature(testSignature, () => {}); - - // wait for websocket to connect - await sleep(100); - expect(connection._rpcWebSocketIdleTimeout).toBe(null); - - await connection.removeSignatureListener(id); - expect(connection._rpcWebSocketIdleTimeout).not.toBe(null); - - const nextId = connection.onSignature(testSignature, () => {}); - expect(connection._rpcWebSocketIdleTimeout).toBe(null); - - await connection.removeSignatureListener(nextId); - expect(connection._rpcWebSocketIdleTimeout).not.toBe(null); - - // wait for websocket to disconnect - await sleep(1100); - expect(connection._rpcWebSocketIdleTimeout).toBe(null); - }); -}); +}