UNPKG

@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
"use strict"; /** * 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,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidGVzdC5vcGVyYXRvci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9hdnMvdGVzdHMvdGVzdC5vcGVyYXRvci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7Ozs7Ozs7OztHQVNHOzs7OztBQUVILG1DQUErQjtBQUMvQiw0Q0FBMkM7QUFDM0MsOERBQWdDO0FBQ2hDLDRFQUFxRTtBQUVyRSwrQ0FBcUU7QUFDckUsdURBQXNEO0FBQ3RELHFEQUF5RDtBQUN6RCwrQ0FBa0U7QUFFbEUseUVBQW1FO0FBRW5FLHFDQUF3SDtBQUV4SCxNQUFNLFNBQVMsR0FBRyxJQUFBLHdCQUFZLEdBQUUsQ0FBQTtBQUVoQyxJQUFJLENBQUMsVUFBVSxDQUFDLEtBQU0sQ0FBQyxDQUFBO0FBRXZCLFFBQVEsQ0FBQyxXQUFXLEVBQUUsR0FBRyxFQUFFO0lBRTFCLElBQUksYUFBeUIsQ0FBQTtJQUM3QixJQUFJLFNBQVMsR0FHUCxFQUFFLENBQUE7SUFDUixNQUFNLGFBQWEsR0FBRyxJQUFJLENBQUMsRUFBRSxDQUczQixHQUFHLEVBQUU7UUFDTixNQUFNLElBQUksS0FBSyxDQUFDLGlCQUFpQixDQUFDLENBQUE7SUFDbkMsQ0FBQyxDQUFDLENBQUE7SUFFRixJQUFJLHVCQUF1QixHQUFHLEtBQUssQ0FBQTtJQUNuQyxJQUFJLHdCQUF3QixHQUFHLEtBQUssQ0FBQTtJQUVwQyxTQUFTLENBQUMsS0FBSyxJQUFHLEVBQUU7UUFDbkIsYUFBYSxHQUFHLE1BQU0sSUFBQSxxQkFBYSxHQUFFLENBQUE7UUFDckMsU0FBUyxHQUFHLENBQUMsRUFBRSxNQUFNLEVBQUUsU0FBUyxDQUFDLE1BQU8sRUFBRSxHQUFHLEVBQUUsa0JBQWtCLEVBQUUsQ0FBQyxDQUFBO1FBRXBFLGFBQWEsQ0FBQyxrQkFBa0IsQ0FBQyxLQUFLLEVBQUMsRUFDdEMsZUFBZSxFQUFFLElBQUksRUFBRSxNQUFNLEVBQUUsT0FBTyxFQUFFLE1BQU0sRUFBRSxVQUFVLEVBQzFE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