@reclaimprotocol/attestor-core
Version:
<div> <div> <img src="https://raw.githubusercontent.com/reclaimprotocol/.github/main/assets/banners/Attestor-Core.png" /> </div> </div>
313 lines • 26 kB
JavaScript
/**
* This file tests the operator registration and task creation.
* The tests were initially written using Node's own testing framework, but
* later switched to Jest. Thus, the tests were initially written in a nested
* format, but were later refactored to use Jest's `describe` and `it` functions.
* Apologies for the hence resulting inconsistency in the code style.
*
* The nesting of tests is helpful as the tests logically depend on each other,
* and the nesting helps save time by not repeating the same setup code.
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const ethers_1 = require("ethers");
const utils_1 = require("ethers/lib/utils");
const node_assert_1 = __importDefault(require("node:assert"));
const create_claim_on_avs_1 = require("../../avs/client/create-claim-on-avs");
const utils_2 = require("../../avs/tests/utils");
const contracts_1 = require("../../avs/utils/contracts");
const register_1 = require("../../avs/utils/register");
const tasks_1 = require("../../avs/utils/tasks");
const describe_with_server_1 = require("../../tests/describe-with-server");
const utils_3 = require("../../utils");
const contracts = (0, contracts_1.getContracts)();
jest.setTimeout(60000);
describe('Operators', () => {
let shutdownChain;
let operators = [];
const createClaimFn = jest.fn(() => {
throw new Error('Not implemented');
});
let registeredFirstOperator = false;
let registeredSecondOperator = false;
beforeAll(async () => {
shutdownChain = await (0, utils_2.runFreshChain)();
operators = [{ wallet: contracts.wallet, url: 'ws://example.com' }];
createClaimFn.mockImplementation(async ({ ownerPrivateKey, name, params, context, client, timestampS }) => {
if (!('url' in client)) {
throw new Error('Invalid client');
}
const op = operators
.find(op => op.url === client.url.toString());
if (!op) {
throw new Error('Operator not found: ' + client.url);
}
const userWallet = new ethers_1.Wallet(ownerPrivateKey, contracts.provider);
const data = (0, utils_3.createSignDataForClaim)({
provider: name,
parameters: (0, utils_3.canonicalStringify)(params),
context: context
? (0, utils_3.canonicalStringify)(context)
: '',
timestampS: timestampS,
owner: userWallet.address,
epoch: 1
});
const signData = await op.wallet.signMessage(data);
const signArray = (0, utils_1.arrayify)(signData);
return {
signatures: { claimSignature: signArray }
};
});
});
afterAll(async () => {
await (shutdownChain === null || shutdownChain === void 0 ? void 0 : shutdownChain());
});
it('should prevent registration of non-whitelisted operator', async () => {
const op = randomWallet();
const url = 'ws://abcd.com/ws';
await (0, utils_2.sendGasToAddress)(op.address);
// using try-catch since jest.rejects.toMatchObject wasn't
// working as expected
try {
await (0, register_1.registerOperator)({
wallet: op,
reclaimRpcUrl: url
});
throw new Error('Should have thrown an error');
}
catch (err) {
expect(err.message).toMatch(/Operator not whitelisted/);
}
});
it('should prevent non-admins from modifying internal settings', async () => {
const nonAdmin = randomWallet();
await (0, utils_2.sendGasToAddress)(nonAdmin.address);
const contract = contracts.contract.connect(nonAdmin);
const OPS = [
() => (contract.whitelistAddressAsOperator(nonAdmin.address, true)),
() => (contract.updateTaskCreationMetadata({
minSignaturesPerTask: 2,
maxTaskLifetimeS: 10,
maxTaskCreationDelayS: 0
}))
];
for (const op of OPS) {
try {
await op();
throw new Error('Should have thrown an error');
}
catch (err) {
expect(err.message).toMatch(/Caller is not admin/);
}
}
});
it('should register the operator on chain', async () => {
await registerFirstOperator();
});
it('should not throw an error on repeated registration', async () => {
await registerFirstOperator();
await (0, register_1.registerOperator)();
});
it('should register multiple operators', async () => {
await registerFirstOperator();
await registerSecondOperator();
});
describe('With Task', () => {
let userWallet;
let arg;
beforeAll(async () => {
await registerFirstOperator();
await registerSecondOperator();
userWallet = randomWallet();
await (0, utils_2.sendGasToAddress)(userWallet.address);
});
it('should create a task', async () => {
arg = await createNewTask(userWallet);
});
it('should mark a task as completed', async () => {
if (!arg) {
arg = await createNewTask(userWallet);
}
await markTaskAsCompleted(userWallet, arg);
});
it('should create a task for another wallet', async () => {
const ownerWallet = randomWallet();
const rslt = await createNewTask(userWallet, ownerWallet);
node_assert_1.default.strictEqual(rslt.task.request.owner, ownerWallet.address);
});
});
(0, describe_with_server_1.describeWithServer)('With Task & Attestor Server', opts => {
beforeAll(async () => {
await registerFirstOperator();
await registerSecondOperator();
});
it('should create claim via createClaimOnChain', createClaimViaFn);
it('should make attestor pay for claim', async () => {
const userWallet = randomWallet();
const { object: rslt } = await (0, create_claim_on_avs_1.createClaimOnAvs)({
ownerPrivateKey: userWallet.privateKey,
name: 'http',
params: {
url: 'https://example.com',
method: 'GET',
responseRedactions: [],
responseMatches: [
{
type: 'contains',
value: 'test'
}
]
},
secretParams: {},
payer: { attestor: opts.serverUrl },
createClaimOnAttestor: createClaimFn
});
node_assert_1.default.strictEqual(rslt.task.task.request.owner, userWallet.address);
});
});
async function registerFirstOperator() {
if (registeredFirstOperator) {
return;
}
// fetch address from the env variable, PRIVATE_KEY
const operatorAddress = await contracts.wallet.address;
await (0, utils_2.sendGasToAddress)(operatorAddress);
await contracts.contract.whitelistAddressAsOperator(operatorAddress, true);
await (0, register_1.registerOperator)({
wallet: operators[0].wallet,
reclaimRpcUrl: operators[0].url
});
node_assert_1.default.strictEqual(await contracts.registryContract
.operatorRegistered(operatorAddress), true);
const op = await contracts.contract.registeredOperators(0);
node_assert_1.default.strictEqual(op.addr, operatorAddress);
registeredFirstOperator = true;
}
async function registerSecondOperator() {
if (registeredSecondOperator) {
return;
}
const wallet2 = randomWallet();
const url = 'ws://abcd.com/ws';
await (0, utils_2.sendGasToAddress)(wallet2.address);
await contracts.contract.whitelistAddressAsOperator(wallet2.address, true);
await (0, register_1.registerOperator)({
wallet: wallet2,
reclaimRpcUrl: url
});
const newAddr = wallet2.address;
node_assert_1.default.strictEqual(await contracts.registryContract.operatorRegistered(newAddr), true);
const meta = await contracts.contract
.getMetadataForOperator(newAddr);
node_assert_1.default.strictEqual(meta.url, url);
node_assert_1.default.strictEqual(meta.addr, newAddr);
operators.push({ wallet: wallet2, url });
registeredSecondOperator = true;
}
async function createNewTask(userWallet, claimOwner = userWallet) {
var _a, _b;
const params = makeNewCreateClaimParams();
const { task } = await (0, tasks_1.createNewClaimRequestOnChain)({
request: {
provider: params.provider,
claimUserId: new Uint8Array(32),
claimHash: (0, utils_3.getIdentifierFromClaimInfo)(params),
requestedAt: (0, utils_3.unixTimestampSeconds)(),
},
payer: userWallet,
owner: claimOwner
});
node_assert_1.default.strictEqual(!!task, true);
node_assert_1.default.equal((_b = (_a = task === null || task === void 0 ? void 0 : task.task) === null || _a === void 0 ? void 0 : _a.request) === null || _b === void 0 ? void 0 : _b.provider, params.provider);
return task;
}
async function markTaskAsCompleted(userWallet, { task, taskIndex }) {
var _a;
node_assert_1.default.ok(task.operators.length > 0, 'No operators selected for the task');
const req = task.request;
const signData = (0, utils_3.createSignDataForClaim)({
identifier: req.claimHash,
timestampS: +task.createdAt.toString(),
owner: userWallet.address,
epoch: 1
});
const signatures = [];
for (const { wallet: operator } of operators) {
const opAddr = operator.address;
const selectedOp = task.operators
.some(op => op.addr === opAddr);
if (!selectedOp) {
continue;
}
const signature = await operator
.signMessage(signData);
signatures.push(signature);
}
node_assert_1.default.strictEqual(signatures.length, task.operators.length);
const tx = await contracts.contract
.connect(userWallet)
.taskCompleted({ task, signatures }, taskIndex);
const rslt = await tx.wait();
const events = rslt.events;
const arg = (_a = events === null || events === void 0 ? void 0 : events[0]) === null || _a === void 0 ? void 0 : _a.args;
node_assert_1.default.strictEqual(events === null || events === void 0 ? void 0 : events.length, 1);
node_assert_1.default.ok(arg.task);
}
async function createClaimViaFn() {
const tx = await contracts.contract.updateTaskCreationMetadata({
minSignaturesPerTask: 2,
maxTaskLifetimeS: 0,
maxTaskCreationDelayS: 0
});
await tx.wait();
console.log('min sigs set to 2');
const userWallet = randomWallet();
await (0, utils_2.sendGasToAddress)(userWallet.address);
const { object: rslt } = await (0, create_claim_on_avs_1.createClaimOnAvs)({
ownerPrivateKey: userWallet.privateKey,
name: 'http',
params: {
url: 'https://example.com',
method: 'GET',
responseRedactions: [],
responseMatches: [
{
type: 'contains',
value: 'test'
}
]
},
secretParams: {},
createClaimOnAttestor: createClaimFn
});
// ensure two operators were selected
node_assert_1.default.equal(rslt.task.task.operators.length, 2);
node_assert_1.default.equal(rslt.task.signatures.length, 2);
}
});
function randomWallet() {
return ethers_1.Wallet.createRandom()
.connect(contracts.provider);
}
function makeNewCreateClaimParams() {
return {
provider: 'http',
parameters: (0, utils_3.canonicalStringify)({
url: 'https://example.com',
method: 'GET',
responseRedactions: [],
responseMatches: [
{
type: 'contains',
value: 'test'
}
]
}),
context: ''
};
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidGVzdC5vcGVyYXRvci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9hdnMvdGVzdHMvdGVzdC5vcGVyYXRvci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7Ozs7Ozs7OztHQVNHOzs7OztBQUVILG1DQUErQjtBQUMvQiw0Q0FBMkM7QUFDM0MsOERBQWdDO0FBQ2hDLDRFQUFxRTtBQUVyRSwrQ0FBcUU7QUFDckUsdURBQXNEO0FBQ3RELHFEQUF5RDtBQUN6RCwrQ0FBa0U7QUFFbEUseUVBQW1FO0FBRW5FLHFDQUF3SDtBQUV4SCxNQUFNLFNBQVMsR0FBRyxJQUFBLHdCQUFZLEdBQUUsQ0FBQTtBQUVoQyxJQUFJLENBQUMsVUFBVSxDQUFDLEtBQU0sQ0FBQyxDQUFBO0FBRXZCLFFBQVEsQ0FBQyxXQUFXLEVBQUUsR0FBRyxFQUFFO0lBRTFCLElBQUksYUFBeUIsQ0FBQTtJQUM3QixJQUFJLFNBQVMsR0FHUCxFQUFFLENBQUE7SUFDUixNQUFNLGFBQWEsR0FBRyxJQUFJLENBQUMsRUFBRSxDQUczQixHQUFHLEVBQUU7UUFDTixNQUFNLElBQUksS0FBSyxDQUFDLGlCQUFpQixDQUFDLENBQUE7SUFDbkMsQ0FBQyxDQUFDLENBQUE7SUFFRixJQUFJLHVCQUF1QixHQUFHLEtBQUssQ0FBQTtJQUNuQyxJQUFJLHdCQUF3QixHQUFHLEtBQUssQ0FBQTtJQUVwQyxTQUFTLENBQUMsS0FBSyxJQUFHLEVBQUU7UUFDbkIsYUFBYSxHQUFHLE1BQU0sSUFBQSxxQkFBYSxHQUFFLENBQUE7UUFDckMsU0FBUyxHQUFHLENBQUMsRUFBRSxNQUFNLEVBQUUsU0FBUyxDQUFDLE1BQU8sRUFBRSxHQUFHLEVBQUUsa0JBQWtCLEVBQUUsQ0FBQyxDQUFBO1FBRXBFLGFBQWEsQ0FBQyxrQkFBa0IsQ0FBQyxLQUFLLEVBQUMsRUFDdEMsZUFBZSxFQUFFLElBQUksRUFBRSxNQUFNLEVBQUUsT0FBTyxFQUFFLE1BQU0sRUFBRSxVQUFVLEVBQzFELEVBQUUsRUFBRTtZQUNKLElBQUcsQ0FBQyxDQUFDLEtBQUssSUFBSSxNQUFNLENBQUMsRUFBRSxDQUFDO2dCQUN2QixNQUFNLElBQUksS0FBSyxDQUFDLGdCQUFnQixDQUFDLENBQUE7WUFDbEMsQ0FBQztZQUVELE1BQU0sRUFBRSxHQUFHLFNBQVM7aUJBQ2xCLElBQUksQ0FBQyxFQUFFLENBQUMsRUFBRSxDQUFDLEVBQUUsQ0FBQyxHQUFHLEtBQUssTUFBTSxDQUFDLEdBQUcsQ0FBQyxRQUFRLEVBQUUsQ0FBQyxDQUFBO1lBQzlDLElBQUcsQ0FBQyxFQUFFLEVBQUUsQ0FBQztnQkFDUixNQUFNLElBQUksS0FBSyxDQUFDLHNCQUFzQixHQUFHLE1BQU0sQ0FBQyxHQUFHLENBQUMsQ0FBQTtZQUNyRCxDQUFDO1lBRUQsTUFBTSxVQUFVLEdBQUcsSUFBSSxlQUFNLENBQUMsZUFBZSxFQUFFLFNBQVMsQ0FBQyxRQUFRLENBQUMsQ0FBQTtZQUVsRSxNQUFNLElBQUksR0FBRyxJQUFBLDhCQUFzQixFQUFDO2dCQUNuQyxRQUFRLEVBQUUsSUFBSTtnQkFDZCxVQUFVLEVBQUUsSUFBQSwwQkFBa0IsRUFBQyxNQUFNLENBQUM7Z0JBQ3RDLE9BQU8sRUFBRSxPQUFPO29CQUNmLENBQUMsQ0FBQyxJQUFBLDBCQUFrQixFQUFDLE9BQU8sQ0FBQztvQkFDN0IsQ0FBQyxDQUFDLEVBQUU7Z0JBQ0wsVUFBVSxFQUFFLFVBQVc7Z0JBQ3ZCLEtBQUssRUFBRSxVQUFVLENBQUMsT0FBTztnQkFDekIsS0FBSyxFQUFFLENBQUM7YUFDUixDQUFDLENBQUE7WUFFRixNQUFNLFFBQVEsR0FBRyxNQUFNLEVBQUUsQ0FBQyxNQUFNLENBQUMsV0FBVyxDQUFDLElBQUksQ0FBQyxDQUFBO1lBQ2xELE1BQU0sU0FBUyxHQUFHLElBQUEsZ0JBQVEsRUFBQyxRQUFRLENBQUMsQ0FBQTtZQUVwQyxPQUFPO2dCQUNOLFVBQVUsRUFBRSxFQUFFLGNBQWMsRUFBRSxTQUFTLEVBQUU7YUFDbEMsQ0FBQTtRQUNULENBQUMsQ0FBQyxDQUFBO0lBQ0gsQ0FBQyxDQUFDLENBQUE7SUFFRixRQUFRLENBQUMsS0FBSyxJQUFHLEVBQUU7UUFDbEIsTUFBTSxDQUFBLGFBQWEsYUFBYixhQUFhLHVCQUFiLGFBQWEsRUFBSSxDQUFBLENBQUE7SUFDeEIsQ0FBQyxDQUFDLENBQUE7SUFFRixFQUFFLENBQUMseURBQXlELEVBQUUsS0FBSyxJQUFHLEVBQUU7UUFDdkUsTUFBTSxFQUFFLEdBQUcsWUFBWSxFQUFFLENBQUE7UUFDekIsTUFBTSxHQUFHLEdBQUcsa0JBQWtCLENBQUE7UUFDOUIsTUFBTSxJQUFBLHdCQUFnQixFQUFDLEVBQUUsQ0FBQyxPQUFPLENBQUMsQ0FBQTtRQUVsQywwREFBMEQ7UUFDMUQsc0JBQXNCO1FBQ3RCLElBQUksQ0FBQztZQUNKLE1BQU0sSUFBQSwyQkFBZ0IsRUFBQztnQkFDdEIsTUFBTSxFQUFFLEVBQUU7Z0JBQ1YsYUFBYSxFQUFFLEdBQUc7YUFDbEIsQ0FBQyxDQUFBO1lBQ0YsTUFBTSxJQUFJLEtBQUssQ0FBQyw2QkFBNkIsQ0FBQyxDQUFBO1FBQy9DLENBQUM7UUFBQyxPQUFNLEdBQUcsRUFBRSxDQUFDO1lBQ2IsTUFBTSxDQUFDLEdBQUcsQ0FBQyxPQUFPLENBQUMsQ0FBQyxPQUFPLENBQUMsMEJBQTBCLENBQUMsQ0FBQTtRQUN4RCxDQUFDO0lBQ0YsQ0FBQyxDQUFDLENBQUE7SUFFRixFQUFFLENBQUMsNERBQTRELEVBQUUsS0FBSyxJQUFHLEVBQUU7UUFDMUUsTUFBTSxRQUFRLEdBQUcsWUFBWSxFQUFFLENBQUE7UUFDL0IsTUFBTSxJQUFBLHdCQUFnQixFQUFDLFFBQVEsQ0FBQyxPQUFPLENBQUMsQ0FBQTtRQUV4QyxNQUFNLFFBQVEsR0FBRyxTQUFTLENBQUMsUUFBUSxDQUFDLE9BQU8sQ0FBQyxRQUFRLENBQUMsQ0FBQTtRQUVyRCxNQUFNLEdBQUcsR0FBRztZQUNYLEdBQUcsRUFBRSxDQUFDLENBQ0wsUUFBUSxDQUFDLDBCQUEwQixDQUNsQyxRQUFRLENBQUMsT0FBTyxFQUNoQixJQUFJLENBQ0osQ0FDRDtZQUNELEdBQUcsRUFBRSxDQUFDLENBQ0wsUUFBUSxDQUFDLDBCQUEwQixDQUFDO2dCQUNuQyxvQkFBb0IsRUFBRSxDQUFDO2dCQUN2QixnQkFBZ0IsRUFBRSxFQUFFO2dCQUNwQixxQkFBcUIsRUFBRSxDQUFDO2FBQ3hCLENBQUMsQ0FDRjtTQUNELENBQUE7UUFFRCxLQUFJLE1BQU0sRUFBRSxJQUFJLEdBQUcsRUFBRSxDQUFDO1lBQ3JCLElBQUksQ0FBQztnQkFDSixNQUFNLEVBQUUsRUFBRSxDQUFBO2dCQUNWLE1BQU0sSUFBSSxLQUFLLENBQUMsNkJBQTZCLENBQUMsQ0FBQTtZQUMvQyxDQUFDO1lBQUMsT0FBTSxHQUFHLEVBQUUsQ0FBQztnQkFDYixNQUFNLENBQUMsR0FBRyxDQUFDLE9BQU8sQ0FBQyxDQUFDLE9BQU8sQ0FBQyxxQkFBcUIsQ0FBQyxDQUFBO1lBQ25ELENBQUM7UUFDRixDQUFDO0lBQ0YsQ0FBQyxDQUFDLENBQUE7SUFFRixFQUFFLENBQUMsdUNBQXVDLEVBQUUsS0FBSyxJQUFHLEVBQUU7UUFDckQsTUFBTSxxQkFBcUIsRUFBRSxDQUFBO0lBQzlCLENBQUMsQ0FBQyxDQUFBO0lBRUYsRUFBRSxDQUFDLG9EQUFvRCxFQUFFLEtBQUssSUFBRyxFQUFFO1FBQ2xFLE1BQU0scUJBQXFCLEVBQUUsQ0FBQTtRQUM3QixNQUFNLElBQUEsMkJBQWdCLEdBQUUsQ0FBQTtJQUN6QixDQUFDLENBQUMsQ0FBQTtJQUVGLEVBQUUsQ0FBQyxvQ0FBb0MsRUFBRSxLQUFLLElBQUcsRUFBRTtRQUNsRCxNQUFNLHFCQUFxQixFQUFFLENBQUE7UUFDN0IsTUFBTSxzQkFBc0IsRUFBRSxDQUFBO0lBQy9CLENBQUMsQ0FBQyxDQUFBO0lBRUYsUUFBUSxDQUFDLFdBQVcsRUFBRSxHQUFHLEVBQUU7UUFFMUIsSUFBSSxVQUFrQixDQUFBO1FBQ3RCLElBQUksR0FBOEIsQ0FBQTtRQUVsQyxTQUFTLENBQUMsS0FBSyxJQUFHLEVBQUU7WUFDbkIsTUFBTSxxQkFBcUIsRUFBRSxDQUFBO1lBQzdCLE1BQU0sc0JBQXNCLEVBQUUsQ0FBQTtZQUU5QixVQUFVLEdBQUcsWUFBWSxFQUFFLENBQUE7WUFDM0IsTUFBTSxJQUFBLHdCQUFnQixFQUFDLFVBQVUsQ0FBQyxPQUFPLENBQUMsQ0FBQTtRQUMzQyxDQUFDLENBQUMsQ0FBQTtRQUVGLEVBQUUsQ0FBQyxzQkFBc0IsRUFBRSxLQUFLLElBQUcsRUFBRTtZQUNwQyxHQUFHLEdBQUcsTUFBTSxhQUFhLENBQUMsVUFBVSxDQUFDLENBQUE7UUFDdEMsQ0FBQyxDQUFDLENBQUE7UUFFRixFQUFFLENBQUMsaUNBQWlDLEVBQUUsS0FBSyxJQUFHLEVBQUU7WUFDL0MsSUFBRyxDQUFDLEdBQUcsRUFBRSxDQUFDO2dCQUNULEdBQUcsR0FBRyxNQUFNLGFBQWEsQ0FBQyxVQUFVLENBQUMsQ0FBQTtZQUN0QyxDQUFDO1lBRUQsTUFBTSxtQkFBbUIsQ0FBQyxVQUFVLEVBQUUsR0FBRyxDQUFDLENBQUE7UUFDM0MsQ0FBQyxDQUFDLENBQUE7UUFFRixFQUFFLENBQUMseUNBQXlDLEVBQUUsS0FBSyxJQUFHLEVBQUU7WUFDdkQsTUFBTSxXQUFXLEdBQUcsWUFBWSxFQUFFLENBQUE7WUFDbEMsTUFBTSxJQUFJLEdBQUcsTUFBTSxhQUFhLENBQUMsVUFBVSxFQUFFLFdBQVcsQ0FBQyxDQUFBO1lBQ3pELHFCQUFNLENBQUMsV0FBVyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLEtBQUssRUFBRSxXQUFXLENBQUMsT0FBTyxDQUFDLENBQUE7UUFDakUsQ0FBQyxDQUFDLENBQUE7SUFDSCxDQUFDLENBQUMsQ0FBQTtJQUVGLElBQUEseUNBQWtCLEVBQUMsNkJBQTZCLEVBQUUsSUFBSSxDQUFDLEVBQUU7UUFDeEQsU0FBUyxDQUFDLEtBQUssSUFBRyxFQUFFO1lBQ25CLE1BQU0scUJBQXFCLEVBQUUsQ0FBQTtZQUM3QixNQUFNLHNCQUFzQixFQUFFLENBQUE7UUFDL0IsQ0FBQyxDQUFDLENBQUE7UUFFRixFQUFFLENBQ0QsNENBQTRDLEVBQzVDLGdCQUFnQixDQUNoQixDQUFBO1FBRUQsRUFBRSxDQUFDLG9DQUFvQyxFQUFFLEtBQUssSUFBRyxFQUFFO1lBQ2xELE1BQU0sVUFBVSxHQUFHLFlBQVksRUFBRSxDQUFBO1lBQ2pDLE1BQU0sRUFBRSxNQUFNLEVBQUUsSUFBSSxFQUFFLEdBQUcsTUFBTSxJQUFBLHNDQUFnQixFQUFDO2dCQUMvQyxlQUFlLEVBQUUsVUFBVSxDQUFDLFVBQVU7Z0JBQ3RDLElBQUksRUFBRSxNQUFNO2dCQUNaLE1BQU0sRUFBRTtvQkFDUCxHQUFHLEVBQUUscUJBQXFCO29CQUMxQixNQUFNLEVBQUUsS0FBSztvQkFDYixrQkFBa0IsRUFBRSxFQUFFO29CQUN0QixlQUFlLEVBQUU7d0JBQ2hCOzRCQUNDLElBQUksRUFBRSxVQUFVOzRCQUNoQixLQUFLLEVBQUUsTUFBTTt5QkFDYjtxQkFDRDtpQkFDRDtnQkFDRCxZQUFZLEVBQUUsRUFBRTtnQkFDaEIsS0FBSyxFQUFFLEVBQUUsUUFBUSxFQUFFLElBQUksQ0FBQyxTQUFTLEVBQUU7Z0JBQ25DLHFCQUFxQixFQUFFLGFBQWE7YUFDcEMsQ0FBQyxDQUFBO1lBRUYscUJBQU0sQ0FBQyxXQUFXLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLEtBQUssRUFBRSxVQUFVLENBQUMsT0FBTyxDQUFDLENBQUE7UUFDckUsQ0FBQyxDQUFDLENBQUE7SUFDSCxDQUFDLENBQUMsQ0FBQTtJQUVGLEtBQUssVUFBVSxxQkFBcUI7UUFDbkMsSUFBRyx1QkFBdUIsRUFBRSxDQUFDO1lBQzVCLE9BQU07UUFDUCxDQUFDO1FBRUQsbURBQW1EO1FBQ25ELE1BQU0sZUFBZSxHQUFHLE1BQU0sU0FBUyxDQUFDLE1BQU8sQ0FBQyxPQUFPLENBQUE7UUFDdkQsTUFBTSxJQUFBLHdCQUFnQixFQUFDLGVBQWUsQ0FBQyxDQUFBO1FBRXZDLE1BQU0sU0FBUyxDQUFDLFFBQVEsQ0FBQywwQkFBMEIsQ0FDbEQsZUFBZSxFQUNmLElBQUksQ0FDSixDQUFBO1FBRUQsTUFBTSxJQUFBLDJCQUFnQixFQUFDO1lBQ3RCLE1BQU0sRUFBRSxTQUFTLENBQUMsQ0FBQyxDQUFDLENBQUMsTUFBTTtZQUMzQixhQUFhLEVBQUUsU0FBUyxDQUFDLENBQUMsQ0FBQyxDQUFDLEdBQUc7U0FDL0IsQ0FBQyxDQUFBO1FBRUYscUJBQU0sQ0FBQyxXQUFXLENBQ2pCLE1BQU0sU0FBUyxDQUFDLGdCQUFnQjthQUM5QixrQkFBa0IsQ0FBQyxlQUFlLENBQUMsRUFDckMsSUFBSSxDQUNKLENBQUE7UUFFRCxNQUFNLEVBQUUsR0FBRyxNQUFNLFNBQVMsQ0FBQyxRQUFRLENBQUMsbUJBQW1CLENBQUMsQ0FBQyxDQUFDLENBQUE7UUFDMUQscUJBQU0sQ0FBQyxXQUFXLENBQUMsRUFBRSxDQUFDLElBQUksRUFBRSxlQUFlLENBQUMsQ0FBQTtRQUU1Qyx1QkFBdUIsR0FBRyxJQUFJLENBQUE7SUFDL0IsQ0FBQztJQUVELEtBQUssVUFBVSxzQkFBc0I7UUFDcEMsSUFBRyx3QkFBd0IsRUFBRSxDQUFDO1lBQzdCLE9BQU07UUFDUCxDQUFDO1FBRUQsTUFBTSxPQUFPLEdBQUcsWUFBWSxFQUFFLENBQUE7UUFDOUIsTUFBTSxHQUFHLEdBQUcsa0JBQWtCLENBQUE7UUFDOUIsTUFBTSxJQUFBLHdCQUFnQixFQUFDLE9BQU8sQ0FBQyxPQUFPLENBQUMsQ0FBQTtRQUV2QyxNQUFNLFNBQVMsQ0FBQyxRQUFRLENBQUMsMEJBQTBCLENBQ2xELE9BQU8sQ0FBQyxPQUFPLEVBQ2YsSUFBSSxDQUNKLENBQUE7UUFHRCxNQUFNLElBQUEsMkJBQWdCLEVBQUM7WUFDdEIsTUFBTSxFQUFFLE9BQU87WUFDZixhQUFhLEVBQUUsR0FBRztTQUNsQixDQUFDLENBQUE7UUFFRixNQUFNLE9BQU8sR0FBRyxPQUFPLENBQUMsT0FBTyxDQUFBO1FBRS9CLHFCQUFNLENBQUMsV0FBVyxDQUNqQixNQUFNLFNBQVMsQ0FBQyxnQkFBZ0IsQ0FBQyxrQkFBa0IsQ0FBQyxPQUFPLENBQUMsRUFDNUQsSUFBSSxDQUNKLENBQUE7UUFFRCxNQUFNLElBQUksR0FBRyxNQUFNLFNBQVMsQ0FBQyxRQUFRO2FBQ25DLHNCQUFzQixDQUFDLE9BQU8sQ0FBQyxDQUFBO1FBQ2pDLHFCQUFNLENBQUMsV0FBVyxDQUFDLElBQUksQ0FBQyxHQUFHLEVBQUUsR0FBRyxDQUFDLENBQUE7UUFDakMscUJBQU0sQ0FBQyxXQUFXLENBQUMsSUFBSSxDQUFDLElBQUksRUFBRSxPQUFPLENBQUMsQ0FBQTtRQUV0QyxTQUFTLENBQUMsSUFBSSxDQUFDLEVBQUUsTUFBTSxFQUFFLE9BQU8sRUFBRSxHQUFHLEVBQUUsQ0FBQyxDQUFBO1FBRXhDLHdCQUF3QixHQUFHLElBQUksQ0FBQTtJQUNoQyxDQUFDO0lBRUQsS0FBSyxVQUFVLGFBQWEsQ0FDM0IsVUFBa0IsRUFDbEIsVUFBVSxHQUFHLFVBQVU7O1FBRXZCLE1BQU0sTUFBTSxHQUFHLHdCQUF3QixFQUFFLENBQUE7UUFDekMsTUFBTSxFQUFFLElBQUksRUFBRSxHQUFHLE1BQU0sSUFBQSxvQ0FBNEIsRUFBQztZQUNuRCxPQUFPLEVBQUU7Z0JBQ1IsUUFBUSxFQUFFLE1BQU0sQ0FBQyxRQUFRO2dCQUN6QixXQUFXLEVBQUUsSUFBSSxVQUFVLENBQUMsRUFBRSxDQUFDO2dCQUMvQixTQUFTLEVBQUUsSUFBQSxrQ0FBMEIsRUFBQyxNQUFNLENBQUM7Z0JBQzdDLFdBQVcsRUFBRSxJQUFBLDRCQUFvQixHQUFFO2FBQ25DO1lBQ0QsS0FBSyxFQUFFLFVBQVU7WUFDakIsS0FBSyxFQUFFLFVBQVU7U0FDakIsQ0FBQyxDQUFBO1FBQ0YscUJBQU0sQ0FBQyxXQUFXLENBQUMsQ0FBQyxDQUFDLElBQUksRUFBRSxJQUFJLENBQUMsQ0FBQTtRQUNoQyxxQkFBTSxDQUFDLEtBQUssQ0FBQyxNQUFBLE1BQUEsSUFBSSxhQUFKLElBQUksdUJBQUosSUFBSSxDQUFFLElBQUksMENBQUUsT0FBTywwQ0FBRSxRQUFRLEVBQUUsTUFBTSxDQUFDLFFBQVEsQ0FBQyxDQUFBO1FBRTVELE9BQU8sSUFBSSxDQUFBO0lBQ1osQ0FBQztJQUVELEtBQUssVUFBVSxtQkFBbUIsQ0FDakMsVUFBa0IsRUFDbEIsRUFBRSxJQUFJLEVBQUUsU0FBUyxFQUE2Qjs7UUFFOUMscUJBQU0sQ0FBQyxFQUFFLENBQ1IsSUFBSSxDQUFDLFNBQVMsQ0FBQyxNQUFNLEdBQUcsQ0FBQyxFQUN6QixvQ0FBb0MsQ0FDcEMsQ0FBQTtRQUVELE1BQU0sR0FBRyxHQUFHLElBQUksQ0FBQyxPQUFPLENBQUE7UUFDeEIsTUFBTSxRQUFRLEdBQUcsSUFBQSw4QkFBc0IsRUFBQztZQUN2QyxVQUFVLEVBQUUsR0FBRyxDQUFDLFNBQVM7WUFDekIsVUFBVSxFQUFFLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxRQUFRLEVBQUU7WUFDdEMsS0FBSyxFQUFFLFVBQVUsQ0FBQyxPQUFPO1lBQ3pCLEtBQUssRUFBRSxDQUFDO1NBQ1IsQ0FBQyxDQUFBO1FBQ0YsTUFBTSxVQUFVLEdBQWEsRUFBRSxDQUFBO1FBQy9CLEtBQUksTUFBTSxFQUFFLE1BQU0sRUFBRSxRQUFRLEVBQUUsSUFBSSxTQUFTLEVBQUUsQ0FBQztZQUM3QyxNQUFNLE1BQU0sR0FBRyxRQUFRLENBQUMsT0FBTyxDQUFBO1lBQy9CLE1BQU0sVUFBVSxHQUFHLElBQUksQ0FBQyxTQUFTO2lCQUMvQixJQUFJLENBQUMsRUFBRSxDQUFDLEVBQUUsQ0FBQyxFQUFFLENBQUMsSUFBSSxLQUFLLE1BQU0sQ0FBQyxDQUFBO1lBQ2hDLElBQUcsQ0FBQyxVQUFVLEVBQUUsQ0FBQztnQkFDaEIsU0FBUTtZQUNULENBQUM7WUFFRCxNQUFNLFNBQVMsR0FBRyxNQUFNLFFBQVE7aUJBQzlCLFdBQVcsQ0FBQyxRQUFRLENBQUMsQ0FBQTtZQUN2QixVQUFVLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxDQUFBO1FBQzNCLENBQUM7UUFFRCxxQkFBTSxDQUFDLFdBQVcsQ0FBQyxVQUFVLENBQUMsTUFBTSxFQUFFLElBQUksQ0FBQyxTQUFTLENBQUMsTUFBTSxDQUFDLENBQUE7UUFDNUQsTUFBTSxFQUFFLEdBQUcsTUFBTSxTQUFTLENBQUMsUUFBUTthQUNqQyxPQUFPLENBQUMsVUFBVSxDQUFDO2FBQ25CLGFBQWEsQ0FDYixFQUFFLElBQUksRUFBRSxVQUFVLEVBQUUsRUFDcEIsU0FBUyxDQUNULENBQUE7UUFDRixNQUFNLElBQUksR0FBRyxNQUFNLEVBQUUsQ0FBQyxJQUFJLEVBQUUsQ0FBQTtRQUM1QixNQUFNLE1BQU0sR0FBRyxJQUFJLENBQUMsTUFBTSxDQUFBO1FBQzFCLE1BQU0sR0FBRyxHQUFHLE1BQUEsTUFBTSxhQUFOLE1BQU0sdUJBQU4sTUFBTSxDQUFHLENBQUMsQ0FBQywwQ0FBRSxJQUEyQyxDQUFBO1FBQ3BFLHFCQUFNLENBQUMsV0FBVyxDQUFDLE1BQU0sYUFBTixNQUFNLHVCQUFOLE1BQU0sQ0FBRSxNQUFNLEVBQUUsQ0FBQyxDQUFDLENBQUE7UUFFckMscUJBQU0sQ0FBQyxFQUFFLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxDQUFBO0lBQ3BCLENBQUM7SUFFRCxLQUFLLFVBQVUsZ0JBQWdCO1FBQzlCLE1BQU0sRUFBRSxHQUFHLE1BQU0sU0FBUyxDQUFDLFFBQVEsQ0FBQywwQkFBMEIsQ0FBQztZQUM5RCxvQkFBb0IsRUFBRSxDQUFDO1lBQ3ZCLGdCQUFnQixFQUFFLENBQUM7WUFDbkIscUJBQXFCLEVBQUUsQ0FBQztTQUN4QixDQUFDLENBQUE7UUFDRixNQUFNLEVBQUUsQ0FBQyxJQUFJLEVBQUUsQ0FBQTtRQUNmLE9BQU8sQ0FBQyxHQUFHLENBQUMsbUJBQW1CLENBQUMsQ0FBQTtRQUVoQyxNQUFNLFVBQVUsR0FBRyxZQUFZLEVBQUUsQ0FBQTtRQUNqQyxNQUFNLElBQUEsd0JBQWdCLEVBQUMsVUFBVSxDQUFDLE9BQU8sQ0FBQyxDQUFBO1FBRTFDLE1BQU0sRUFBRSxNQUFNLEVBQUUsSUFBSSxFQUFFLEdBQUcsTUFBTSxJQUFBLHNDQUFnQixFQUFDO1lBQy9DLGVBQWUsRUFBRSxVQUFVLENBQUMsVUFBVTtZQUN0QyxJQUFJLEVBQUUsTUFBTTtZQUNaLE1BQU0sRUFBRTtnQkFDUCxHQUFHLEVBQUUscUJBQXFCO2dCQUMxQixNQUFNLEVBQUUsS0FBSztnQkFDYixrQkFBa0IsRUFBRSxFQUFFO2dCQUN0QixlQUFlLEVBQUU7b0JBQ2hCO3dCQUNDLElBQUksRUFBRSxVQUFVO3dCQUNoQixLQUFLLEVBQUUsTUFBTTtxQkFDYjtpQkFDRDthQUNEO1lBQ0QsWUFBWSxFQUFFLEVBQUU7WUFDaEIscUJBQXFCLEVBQUUsYUFBYTtTQUNwQyxDQUFDLENBQUE7UUFFRixxQ0FBcUM7UUFDckMscUJBQU0sQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLE1BQU0sRUFBRSxDQUFDLENBQUMsQ0FBQTtRQUNoRCxxQkFBTSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLFVBQVUsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxDQUFDLENBQUE7SUFDN0MsQ0FBQztBQUNGLENBQUMsQ0FBQyxDQUFBO0FBRUYsU0FBUyxZQUFZO0lBQ3BCLE9BQU8sZUFBTSxDQUFDLFlBQVksRUFBRTtTQUMxQixPQUFPLENBQUMsU0FBUyxDQUFDLFFBQVEsQ0FBQyxDQUFBO0FBQzlCLENBQUM7QUFFRCxTQUFTLHdCQUF3QjtJQUNoQyxPQUFPO1FBQ04sUUFBUSxFQUFFLE1BQU07UUFDaEIsVUFBVSxFQUFFLElBQUEsMEJBQWtCLEVBQUM7WUFDOUIsR0FBRyxFQUFFLHFCQUFxQjtZQUMxQixNQUFNLEVBQUUsS0FBSztZQUNiLGtCQUFrQixFQUFFLEVBQUU7WUFDdEIsZUFBZSxFQUFFO2dCQUNoQjtvQkFDQyxJQUFJLEVBQUUsVUFBVTtvQkFDaEIsS0FBSyxFQUFFLE1BQU07aUJBQ2I7YUFDRDtTQUNELENBQUM7UUFDRixPQUFPLEVBQUUsRUFBRTtLQUNYLENBQUE7QUFDRixDQUFDIn0=
;