UNPKG

stellar-plus

Version:

beta version of stellar-plus, an all-in-one sdk for the Stellar blockchain

302 lines (301 loc) 19.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const tslib_1 = require("tslib"); const stellar_sdk_1 = require("@stellar/stellar-sdk"); const simulate_transaction_1 = require("../../../../stellar-plus/core/pipelines/simulate-transaction"); const soroban_auth_1 = require("../../../../stellar-plus/core/pipelines/soroban-auth"); const errors_1 = require("../../../../stellar-plus/core/pipelines/soroban-auth/errors"); const types_1 = require("../../../../stellar-plus/core/pipelines/soroban-auth/types"); const network_1 = require("../../../../stellar-plus/network"); const default_handler_1 = require("../../../../stellar-plus/rpc/default-handler"); const transaction_mock_1 = require("../../../../stellar-plus/test/mocks/transaction-mock"); const TESTNET_NETWORK_CONFIG = (0, network_1.TestNet)(); const MOCKED_TRANSACTION_OUTPUT = new stellar_sdk_1.Transaction('AAAAAgAAAAA/s0szuJKLyO2bQJ0DxXjYA2p8sf8kTBjkhAVTV64DQgAAAGQAAAAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', TESTNET_NETWORK_CONFIG.networkPassphrase); jest.mock('stellar-plus/core/pipelines/simulate-transaction', () => { return { SimulateTransactionPipeline: jest.fn().mockImplementation(() => { return { execute: jest.fn().mockImplementation(() => { return Promise.resolve({ assembledTransaction: MOCKED_TRANSACTION_OUTPUT }); }), }; }), }; }); jest.mock('stellar-plus/rpc/default-handler', () => { return { DefaultRpcHandler: jest.fn().mockImplementation(() => { return { getLatestLedger: jest.fn().mockImplementation(() => { return { sequence: 0, }; }), }; }), }; }); const MOCKED_SIMULATE_TRANSACTION_PIPELINE = simulate_transaction_1.SimulateTransactionPipeline; const MOCKED_DEFAULT_RPC_HANDLER = default_handler_1.DefaultRpcHandler; const MOCKED_PK_A = 'GACF23GKVFTU77K6W6PWSVN7YBM63UHDULILIEXJO6FR4YKMJ7FW3DTI'; const MOCKED_ACCOUNT_A = new stellar_sdk_1.Account(MOCKED_PK_A, '100'); const MOCKED_TX_OPTIONS = { fee: '100', networkPassphrase: TESTNET_NETWORK_CONFIG.networkPassphrase, timebounds: { minTime: 0, maxTime: 10, }, }; const MOCKED_AUTH_ENTRY_A_XDR = 'AAAAAQAAAAAAAAAArb8JC0i36Dmfgz6KW5WQ0tnZdAEJokT6E14XqgChUA1ikGR38zUPUgAAAAAAAAABAAAAAAAAAAFk7Ujzh9RWi2IGWZcvksnk6CDy39UadWJfYdD9K0mf2AAAAAh0cmFuc2ZlcgAAAAMAAAASAAAAAAAAAACtvwkLSLfoOZ+DPopblZDS2dl0AQmiRPoTXheqAKFQDQAAABIAAAAAAAAAAPVDIFPI3hOwtWMsV6KpQaDW/2Yg+ouWRCzidXCyoZP5AAAACgAAAAAAAAAAAAAAAAAAAAEAAAAA'; const MOCKED_AUTH_ENTRY_A = stellar_sdk_1.xdr.SorobanAuthorizationEntry.fromXDR(MOCKED_AUTH_ENTRY_A_XDR, 'base64'); const MOCKED_AUTH_ENTRY_A_REQ_SIGNER = 'GCW36CILJC36QOM7QM7IUW4VSDJNTWLUAEE2ERH2CNPBPKQAUFIA2GRV'; const MOCKED_AUTH_ENTRY_B_XDR = 'AAAAAQAAAAAAAAAAovR6uAp1T7jRCxGrvh8SBIppUR9ZD2/eCQT9tGO26OdII5B9b2HQOAAAAAAAAAABAAAAAAAAAAHTgPPxxvd0QWWI9GqET9mM/ybmAbehCVOSDLq8gmyohQAAAAh0cmFuc2ZlcgAAAAMAAAASAAAAAAAAAACi9Hq4CnVPuNELEau+HxIEimlRH1kPb94JBP20Y7bo5wAAABIAAAAAAAAAAIgOB9aLzALzAdu7EDOHT7sRsX1lczERWJjuKgPDLKcGAAAACgAAAAAAAAAAAAAAAAAAAAEAAAAA'; const MOCKED_AUTH_ENTRY_B = stellar_sdk_1.xdr.SorobanAuthorizationEntry.fromXDR(MOCKED_AUTH_ENTRY_B_XDR, 'base64'); const MOCKED_AUTH_ENTRY_B_REQ_SIGNER = 'GCRPI6VYBJ2U7OGRBMI2XPQ7CICIU2KRD5MQ6366BECP3NDDW3UOP6HV'; const MOCKED_AUTH_ENTRY_SOURCE_XDR = 'AAAAAAAAAAAAAAABTtqfrXuuo4yIBEW0azSfUHab0yKalJuUPxe/LGg6o7YAAAAIdHJhbnNmZXIAAAADAAAAEgAAAAAAAAAA+lIrHIESwBahZaC2Y1qy8V3ofBOcmfRqmYgk2MXmIDsAAAASAAAAAAAAAAC3qK45KNxeE/HNayxHRBU3O0+FQle0MkiVl4yuhRI0fwAAAAoAAAAAAAAAAAAAAAAAAAABAAAAAA=='; const MOCKED_AUTH_ENTRY_SOURCE = stellar_sdk_1.xdr.SorobanAuthorizationEntry.fromXDR(MOCKED_AUTH_ENTRY_SOURCE_XDR, 'base64'); const MOCKED_TRANSACTION_XDR = 'AAAAAgAAAACPVHnhwyBwtNC/1qxeu9dTtItat/QecxIsa7H316JigQAPQkAADkkGAAAAAgAAAAEAAAAAAAAAAAAAAABmDYF/AAAAAAAAAAEAAAAAAAAAGAAAAAAAAAABZO1I84fUVotiBlmXL5LJ5Ogg8t/VGnViX2HQ/StJn9gAAAAIdHJhbnNmZXIAAAADAAAAEgAAAAAAAAAArb8JC0i36Dmfgz6KW5WQ0tnZdAEJokT6E14XqgChUA0AAAASAAAAAAAAAAD1QyBTyN4TsLVjLFeiqUGg1v9mIPqLlkQs4nVwsqGT+QAAAAoAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAA'; const MOCKED_TRANSACTION = new stellar_sdk_1.Transaction(MOCKED_TRANSACTION_XDR, TESTNET_NETWORK_CONFIG.networkPassphrase); const MOCKED_SIMULATION_RESPONSE_SUCCESS = { events: [], id: 'mocked-id', latestLedger: 0, _parsed: true, transactionData: new stellar_sdk_1.SorobanDataBuilder(), minResourceFee: '0', cost: { cpuInsns: '0', memBytes: '0', }, result: { auth: [], xdr: 'mocked-xdr', retval: stellar_sdk_1.xdr.ScVal.scvVoid(), }, }; const MOCKED_RPC = new default_handler_1.DefaultRpcHandler(TESTNET_NETWORK_CONFIG); const MOCKED_ACCOUNT_HANDLER_A = (0, transaction_mock_1.mockAccountHandler)({ accountKey: MOCKED_PK_A }); const MOCKED_PIPELINE_ITEM = { transaction: MOCKED_TRANSACTION, simulation: MOCKED_SIMULATION_RESPONSE_SUCCESS, signers: [], rpcHandler: MOCKED_RPC, }; const mockConveyorBeltErrorMeta = (item) => { return { item, meta: { itemId: 'mocked-id', beltId: 'mocked-belt-id', beltType: types_1.SorobanAuthPipelineType.id, }, }; }; describe('Soroban Auth Pipeline', () => { describe('Initialization', () => { it('should initialize the pipeline', () => { const pipeline = new soroban_auth_1.SorobanAuthPipeline(); expect(pipeline).toBeInstanceOf(soroban_auth_1.SorobanAuthPipeline); }); }); describe('Process', () => { beforeEach(() => { jest.clearAllMocks(); jest.useFakeTimers(); }); it('should not sign the auth entries if there are no auth entries in the simulation nor additional entries provided', () => tslib_1.__awaiter(void 0, void 0, void 0, function* () { const pipeline = new soroban_auth_1.SorobanAuthPipeline(); const mockedItem = Object.assign(Object.assign({}, MOCKED_PIPELINE_ITEM), { transaction: MOCKED_TRANSACTION, simulation: MOCKED_SIMULATION_RESPONSE_SUCCESS, signers: [MOCKED_ACCOUNT_HANDLER_A] }); const output = yield pipeline.execute(mockedItem); expect(output).toEqual(MOCKED_TRANSACTION); })); it('should not sign the auth entries if there are no auth entries in the simulation and additional entries are provided empty', () => tslib_1.__awaiter(void 0, void 0, void 0, function* () { const pipeline = new soroban_auth_1.SorobanAuthPipeline(); const mockedItem = Object.assign(Object.assign({}, MOCKED_PIPELINE_ITEM), { transaction: MOCKED_TRANSACTION, simulation: MOCKED_SIMULATION_RESPONSE_SUCCESS, signers: [MOCKED_ACCOUNT_HANDLER_A], additionalSorobanAuthToSign: [], additionalSignedSorobanAuth: [] }); const output = yield pipeline.execute(mockedItem); expect(output).toEqual(MOCKED_TRANSACTION); })); it('should not sign the auth entries if there are only auth entries for source', () => tslib_1.__awaiter(void 0, void 0, void 0, function* () { const pipeline = new soroban_auth_1.SorobanAuthPipeline(); const simulation = Object.assign({}, MOCKED_SIMULATION_RESPONSE_SUCCESS); if (simulation.result) simulation.result.auth = [MOCKED_AUTH_ENTRY_SOURCE]; const mockedItem = Object.assign(Object.assign({}, MOCKED_PIPELINE_ITEM), { transaction: MOCKED_TRANSACTION, simulation, signers: [MOCKED_ACCOUNT_HANDLER_A], additionalSorobanAuthToSign: [], additionalSignedSorobanAuth: [] }); const output = yield pipeline.execute(mockedItem); expect(output).toEqual(MOCKED_TRANSACTION); })); it('should not sign the auth entries if there is no result in the simulation', () => tslib_1.__awaiter(void 0, void 0, void 0, function* () { const pipeline = new soroban_auth_1.SorobanAuthPipeline(); const simulation = Object.assign({}, MOCKED_SIMULATION_RESPONSE_SUCCESS); if (simulation.result) simulation.result = undefined; const mockedSigner = (0, transaction_mock_1.mockAccountHandler)({ accountKey: MOCKED_AUTH_ENTRY_A_REQ_SIGNER, outputSignedAuthEntry: MOCKED_AUTH_ENTRY_A, }); const spyMockSign = jest.spyOn(mockedSigner, 'signSorobanAuthEntry'); const mockedItem = Object.assign(Object.assign({}, MOCKED_PIPELINE_ITEM), { transaction: MOCKED_TRANSACTION, simulation, signers: [mockedSigner], additionalSorobanAuthToSign: [], additionalSignedSorobanAuth: [] }); const output = yield pipeline.execute(mockedItem); expect(output).toEqual(MOCKED_TRANSACTION); expect(spyMockSign).not.toHaveBeenCalled(); })); it('should sign the auth entries if there are auth entries for the transaction and the required signer', () => tslib_1.__awaiter(void 0, void 0, void 0, function* () { const pipeline = new soroban_auth_1.SorobanAuthPipeline(); const simulation = Object.assign({}, MOCKED_SIMULATION_RESPONSE_SUCCESS); if (simulation.result) simulation.result.auth = [MOCKED_AUTH_ENTRY_A]; const mockedSigner = (0, transaction_mock_1.mockAccountHandler)({ accountKey: MOCKED_AUTH_ENTRY_A_REQ_SIGNER, outputSignedAuthEntry: MOCKED_AUTH_ENTRY_A, }); const spyMockSign = jest.spyOn(mockedSigner, 'signSorobanAuthEntry'); const mockedItem = Object.assign(Object.assign({}, MOCKED_PIPELINE_ITEM), { transaction: MOCKED_TRANSACTION, simulation, signers: [mockedSigner] }); const output = yield pipeline.execute(mockedItem); expect(MOCKED_SIMULATE_TRANSACTION_PIPELINE).toHaveBeenCalledOnce(); expect(output).toEqual(MOCKED_TRANSACTION_OUTPUT); expect(spyMockSign).toHaveBeenCalledOnce(); })); it('should sign the auth entries if there are auth entries for the transaction only with the required signer', () => tslib_1.__awaiter(void 0, void 0, void 0, function* () { const pipeline = new soroban_auth_1.SorobanAuthPipeline(); const simulation = Object.assign({}, MOCKED_SIMULATION_RESPONSE_SUCCESS); if (simulation.result) simulation.result.auth = [MOCKED_AUTH_ENTRY_A]; const mockedSignerA = (0, transaction_mock_1.mockAccountHandler)({ accountKey: MOCKED_AUTH_ENTRY_A_REQ_SIGNER, outputSignedAuthEntry: MOCKED_AUTH_ENTRY_A, }); const mockedSignerB = (0, transaction_mock_1.mockAccountHandler)({ accountKey: MOCKED_AUTH_ENTRY_B_REQ_SIGNER, outputSignedAuthEntry: MOCKED_AUTH_ENTRY_B, }); const spyMockSignA = jest.spyOn(mockedSignerA, 'signSorobanAuthEntry'); const spyMockSignB = jest.spyOn(mockedSignerB, 'signSorobanAuthEntry'); const mockedItem = Object.assign(Object.assign({}, MOCKED_PIPELINE_ITEM), { transaction: MOCKED_TRANSACTION, simulation, signers: [mockedSignerA] }); const output = yield pipeline.execute(mockedItem); expect(MOCKED_SIMULATE_TRANSACTION_PIPELINE).toHaveBeenCalledOnce(); expect(output).toEqual(MOCKED_TRANSACTION_OUTPUT); expect(spyMockSignA).toHaveBeenCalledOnce(); expect(spyMockSignB).not.toHaveBeenCalled(); })); it('should use the transaction timeout to calculate auth timeout', () => tslib_1.__awaiter(void 0, void 0, void 0, function* () { var _a; const latestLedger = 1000; MOCKED_DEFAULT_RPC_HANDLER.mockImplementationOnce(() => { return { getLatestLedger: jest.fn().mockImplementation(() => { return { sequence: latestLedger, }; }), }; }); const currentTime = 1000; jest.setSystemTime(currentTime); const timeout = Number((_a = MOCKED_TRANSACTION.timeBounds) === null || _a === void 0 ? void 0 : _a.maxTime) - currentTime / 1000; const expectedExpiration = Number((latestLedger + timeout / 5 + 1).toFixed(0)); const pipeline = new soroban_auth_1.SorobanAuthPipeline(); const simulation = Object.assign({}, MOCKED_SIMULATION_RESPONSE_SUCCESS); if (simulation.result) simulation.result.auth = [MOCKED_AUTH_ENTRY_A]; const mockedSigner = (0, transaction_mock_1.mockAccountHandler)({ accountKey: MOCKED_AUTH_ENTRY_A_REQ_SIGNER, outputSignedAuthEntry: MOCKED_AUTH_ENTRY_A, }); const spyMockSign = jest.spyOn(mockedSigner, 'signSorobanAuthEntry'); const mockedItem = Object.assign(Object.assign({}, MOCKED_PIPELINE_ITEM), { transaction: MOCKED_TRANSACTION, simulation, signers: [mockedSigner], rpcHandler: new default_handler_1.DefaultRpcHandler(TESTNET_NETWORK_CONFIG) }); yield pipeline.execute(mockedItem); expect(spyMockSign).toHaveBeenCalledWith(MOCKED_AUTH_ENTRY_A, expectedExpiration, TESTNET_NETWORK_CONFIG.networkPassphrase); })); }); it('should use the default timeout to calculate auth timeout when the transaction has no timeout', () => tslib_1.__awaiter(void 0, void 0, void 0, function* () { const latestLedger = 500; MOCKED_DEFAULT_RPC_HANDLER.mockImplementationOnce(() => { return { getLatestLedger: jest.fn().mockImplementation(() => { return { sequence: latestLedger, }; }), }; }); const currentTime = 1000; jest.useFakeTimers(); jest.setSystemTime(currentTime); const mockedTransactionWithoutTimeout = stellar_sdk_1.TransactionBuilder.cloneFrom(MOCKED_TRANSACTION, { fee: '100', networkPassphrase: TESTNET_NETWORK_CONFIG.networkPassphrase, timebounds: { minTime: 0, maxTime: 0, }, }).build(); const defaultValue = 600; const timeout = Number(defaultValue); const expectedExpiration = Number((latestLedger + timeout / 5 + 1).toFixed(0)); const pipeline = new soroban_auth_1.SorobanAuthPipeline(); const simulation = Object.assign({}, MOCKED_SIMULATION_RESPONSE_SUCCESS); if (simulation.result) simulation.result.auth = [MOCKED_AUTH_ENTRY_A]; const mockedSigner = (0, transaction_mock_1.mockAccountHandler)({ accountKey: MOCKED_AUTH_ENTRY_A_REQ_SIGNER, outputSignedAuthEntry: MOCKED_AUTH_ENTRY_A, }); const spyMockSign = jest.spyOn(mockedSigner, 'signSorobanAuthEntry'); const mockedItem = Object.assign(Object.assign({}, MOCKED_PIPELINE_ITEM), { transaction: mockedTransactionWithoutTimeout, simulation, signers: [mockedSigner], rpcHandler: new default_handler_1.DefaultRpcHandler(TESTNET_NETWORK_CONFIG) }); yield pipeline.execute(mockedItem); expect(spyMockSign).toHaveBeenCalledWith(MOCKED_AUTH_ENTRY_A, expectedExpiration, TESTNET_NETWORK_CONFIG.networkPassphrase); })); describe('Errors', () => { it('should throw if there are entries to sign but no signers are provided', () => tslib_1.__awaiter(void 0, void 0, void 0, function* () { const pipeline = new soroban_auth_1.SorobanAuthPipeline(); const simulation = Object.assign({}, MOCKED_SIMULATION_RESPONSE_SUCCESS); if (simulation.result) simulation.result.auth = [MOCKED_AUTH_ENTRY_A]; const mockedItem = Object.assign(Object.assign({}, MOCKED_PIPELINE_ITEM), { transaction: MOCKED_TRANSACTION, simulation, signers: [], additionalSorobanAuthToSign: [], additionalSignedSorobanAuth: [] }); yield expect(pipeline.execute(mockedItem)).rejects.toThrow(errors_1.PSAError.noSignersProvided(mockConveyorBeltErrorMeta(mockedItem))); })); it('should throw if there are entries to sign but the required signers are not provided', () => tslib_1.__awaiter(void 0, void 0, void 0, function* () { const pipeline = new soroban_auth_1.SorobanAuthPipeline(); const simulation = Object.assign({}, MOCKED_SIMULATION_RESPONSE_SUCCESS); if (simulation.result) simulation.result.auth = [MOCKED_AUTH_ENTRY_B]; const mockedItem = Object.assign(Object.assign({}, MOCKED_PIPELINE_ITEM), { transaction: MOCKED_TRANSACTION, simulation, signers: [MOCKED_ACCOUNT_HANDLER_A], additionalSorobanAuthToSign: [], additionalSignedSorobanAuth: [] }); yield expect(pipeline.execute(mockedItem)).rejects.toThrow(errors_1.PSAError.signerNotFound(mockConveyorBeltErrorMeta(mockedItem), MOCKED_TRANSACTION, [MOCKED_PK_A], MOCKED_AUTH_ENTRY_A_REQ_SIGNER, MOCKED_AUTH_ENTRY_A)); })); it('should throw error if updateTransaction fails', () => tslib_1.__awaiter(void 0, void 0, void 0, function* () { const pipeline = new soroban_auth_1.SorobanAuthPipeline(); const simulation = Object.assign({}, MOCKED_SIMULATION_RESPONSE_SUCCESS); if (simulation.result) simulation.result.auth = [MOCKED_AUTH_ENTRY_A]; const mockedSigner = (0, transaction_mock_1.mockAccountHandler)({ accountKey: MOCKED_AUTH_ENTRY_A_REQ_SIGNER, outputSignedAuthEntry: MOCKED_AUTH_ENTRY_A, }); const spyMockSign = jest.spyOn(mockedSigner, 'signSorobanAuthEntry'); const faultyTransaction = new stellar_sdk_1.TransactionBuilder(MOCKED_ACCOUNT_A, MOCKED_TX_OPTIONS).build(); // Transaction with no operations should cause the updateTransaction to fail const mockedItem = Object.assign(Object.assign({}, MOCKED_PIPELINE_ITEM), { transaction: faultyTransaction, simulation, signers: [mockedSigner] }); yield expect(pipeline.execute(mockedItem)).rejects.toThrow(errors_1.PSAError.couldntUpdateTransaction(new Error('Mocked Error'), mockConveyorBeltErrorMeta(mockedItem), MOCKED_TRANSACTION, [MOCKED_AUTH_ENTRY_A])); expect(spyMockSign).toHaveBeenCalledOnce(); })); it('should throw error if simulation fails', () => tslib_1.__awaiter(void 0, void 0, void 0, function* () { const pipeline = new soroban_auth_1.SorobanAuthPipeline(); const simulation = Object.assign({}, MOCKED_SIMULATION_RESPONSE_SUCCESS); if (simulation.result) simulation.result.auth = [MOCKED_AUTH_ENTRY_A]; const mockedSigner = (0, transaction_mock_1.mockAccountHandler)({ accountKey: MOCKED_AUTH_ENTRY_A_REQ_SIGNER, outputSignedAuthEntry: MOCKED_AUTH_ENTRY_A, }); const mockedItem = Object.assign(Object.assign({}, MOCKED_PIPELINE_ITEM), { transaction: MOCKED_TRANSACTION, simulation, signers: [mockedSigner] }); MOCKED_SIMULATE_TRANSACTION_PIPELINE.mockImplementationOnce(() => { return { execute: () => { throw new Error('Mocked Error'); }, }; }); yield expect(pipeline.execute(mockedItem)).rejects.toThrow(errors_1.PSAError.couldntSimulateAuthorizedTransaction(new Error('Mocked Error'), mockConveyorBeltErrorMeta(mockedItem), MOCKED_TRANSACTION, [MOCKED_AUTH_ENTRY_A])); })); }); });