stellar-plus
Version:
beta version of stellar-plus, an all-in-one sdk for the Stellar blockchain
302 lines (301 loc) • 19.9 kB
JavaScript
"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]));
}));
});
});