Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,13 @@ The tests will spin up the local nodes automatically.

To run the tests:

Build the application image:
```bash
yarn build:docker
```

Then run the tests:

```bash
yarn test:integration
```
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
"rimraf": "^3.0.2",
"rxjs": "^7.8.1",
"smee-client": "^1.2.2",
"testcontainers": "^10.2.1",
"testcontainers": "^10.13.0",
"ts-jest": "^29.2.4",
"typescript": "^5.4.5"
},
Expand Down
23 changes: 11 additions & 12 deletions src/bot-handle-comment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { ss58Address } from "@polkadot-labs/hdkd-helpers";
import { updateBalance } from "./balance";
import { matrixNotifyOnFailure, matrixNotifyOnNewTip } from "./matrix";
import { recordTip } from "./metrics";
import { tipUser } from "./tip";
import { tipUser, tipUserLink } from "./tip";
import { updatePolkassemblyPost } from "./tip-opengov";
import { GithubReactionType, State, TipRequest, TipResult } from "./types";
import { formatTipSize, getTipSize, parseContributorAccount } from "./util";
Expand Down Expand Up @@ -167,17 +167,16 @@ export const handleTipRequest = async (
))
) {
let createReferendumLink: string | undefined;
// TODO: Broken after PAPI migration. https://github.com/paritytech/substrate-tip-bot/issues/170
// try {
// const tipLink = await tipUserLink(state, tipRequest);
// if (!tipLink.success) {
// throw new Error(tipLink.errorMessage);
// }
// createReferendumLink = tipLink.extrinsicCreationLink;
// } catch (e) {
// bot.log.error("Failed to encode and create a link to tip referendum creation.");
// bot.log.error(e.message);
// }
try {
const tipLink = await tipUserLink(state, tipRequest);
if (!tipLink.success) {
throw new Error(tipLink.errorMessage);
}
createReferendumLink = tipLink.extrinsicCreationLink;
} catch (e) {
bot.log.error("Failed to encode and create a link to tip referendum creation.");
bot.log.error(e.message);
}

let message =
`Only members of \`${allowedGitHubOrg}/${allowedGitHubTeam}\` ` +
Expand Down
6 changes: 3 additions & 3 deletions src/tip-opengov.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
} from "@polkadot-api/descriptors";
import { ss58Address } from "@polkadot-labs/hdkd-helpers";
import { getDescriptor } from "#src/chain-config";
import { Binary, PolkadotClient, TxPromise } from "polkadot-api";
import { Binary, PolkadotClient, Transaction } from "polkadot-api";
import { Probot } from "probot";

import { Polkassembly } from "./polkassembly/polkassembly";
Expand All @@ -20,7 +20,7 @@ export async function tipOpenGovReferendumExtrinsic(opts: { client: PolkadotClie
| Exclude<TipResult, { success: true }>
| {
success: true;
referendumExtrinsic: { signAndSubmit: TxPromise<"promise"> };
referendumExtrinsic: Transaction<object, "Referenda", "submit", unknown>;
proposalByteSize: number;
encodedProposal: Binary;
track: { track: OpenGovTrack; value: bigint };
Expand All @@ -42,7 +42,7 @@ export async function tipOpenGovReferendumExtrinsic(opts: { client: PolkadotClie

const enactMoment = TraitsScheduleDispatchTime.After(10);

let referendumExtrinsic: { signAndSubmit: TxPromise<"promise"> };
let referendumExtrinsic: Transaction<object, "Referenda", "submit", unknown>;
const network: TipNetwork = tipRequest.contributor.account.network;
if (network === "westend" || network === "localwestend" || network === "rococo" || network === "localrococo") {
const api = client.getTypedApi(getDescriptor(network));
Expand Down
74 changes: 63 additions & 11 deletions src/tip.integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,31 @@ describe("tip", () => {
return data.free;
};

const expectTipperMembership = async () => {
await gitHub.forGet("/orgs/tip-bot-org/teams/tip-bot-approvers/memberships/tipper").thenReply(
200,
JSON.stringify(
fixtures.github.getOrgMembershipPayload({
login: "tipper",
org: "tip-bot-approvers",
}),
),
jsonResponseHeaders,
);
};

const expectNoTipperMembership = async () => {
await gitHub.forGet("/orgs/tip-bot-org/teams/tip-bot-approvers/memberships/tipper").thenReply(
404,
JSON.stringify({
message: "Not Found",
documentation_url: "https://docs.github.com/rest/teams/members#get-team-membership-for-a-user",
status: "404",
}),
jsonResponseHeaders,
);
};

beforeAll(async () => {
await fs.mkdir(containterLogsDir, { recursive: true });

Expand All @@ -81,6 +106,7 @@ describe("tip", () => {
.withNetwork(containerNetwork)
.withNetworkAliases("localrococo")
.withExposedPorts(9945)
.withPlatform("linux/amd64")
.start(),
new GenericContainer(`parity/polkadot:${POLKADOT_VERSION}`)
.withWaitStrategy(Wait.forListeningPorts())
Expand All @@ -90,6 +116,7 @@ describe("tip", () => {
.withNetwork(containerNetwork)
.withNetworkAliases("localwestend")
.withExposedPorts(9945)
.withPlatform("linux/amd64")
.start(),
mockServer.startMockServer({ name: "GitHub", port: gitHubPort, testCaCertPath }),
]);
Expand Down Expand Up @@ -199,17 +226,6 @@ describe("tip", () => {
await gitHub
.forPost("/app/installations/155/access_tokens")
.thenReply(200, JSON.stringify(fixtures.github.getAppInstallationTokenPayload()), jsonResponseHeaders);

await gitHub.forGet("/orgs/tip-bot-org/teams/tip-bot-approvers/memberships/tipper").thenReply(
200,
JSON.stringify(
fixtures.github.getOrgMembershipPayload({
login: "tipper",
org: "tip-bot-approvers",
}),
),
jsonResponseHeaders,
);
});

afterAll(async () => {
Expand All @@ -235,6 +251,7 @@ describe("tip", () => {
});

test.each(tipSizes)("tips a user (%s)", async (tipSize) => {
await expectTipperMembership();
const api = network === "localrococo" ? rococoApi : westendApi;
const nextFreeReferendumId = await api.query.Referenda.ReferendumCount.getValue();
await tipUser(appPort, tipSize);
Expand Down Expand Up @@ -272,6 +289,7 @@ describe("tip", () => {
});

test(`huge tip in ${network}`, async () => {
await expectTipperMembership();
const successEndpoint = await gitHub.forPost("/repos/paritytech-stg/testre/issues/4/comments").thenReply(
200,
JSON.stringify(
Expand All @@ -297,6 +315,40 @@ describe("tip", () => {
`The requested tip value of '1000000 ${currency}' exceeds the BigTipper track maximum`,
);
});

test(`tip link in ${network}`, async () => {
await expectNoTipperMembership();
const successEndpoint = await gitHub.forPost("/repos/paritytech-stg/testre/issues/4/comments").thenReply(
200,
JSON.stringify(
fixtures.github.getIssueCommentPayload({
org: "paritytech-stg",
repo: "testre",
comment: {
author: "substrate-tip-bot",
body: "",
id: 4,
},
}),
),
);

await tipUser(appPort, "small");

await until(async () => !(await successEndpoint.isPending()), 500, 50);

const [request] = await successEndpoint.getSeenRequests();
const body = (await request.body.getJson()) as { body: string };
expect(body.body).toContain(
"Only members of `tip-bot-org/tip-bot-approvers` have permission to request the creation of the tip referendum from the bot.",
);
expect(body.body).toContain(`https://polkadot.js.org/apps/?rpc=ws://localrococo:9945#/`);

const extrinsicHex = body.body.match(/decode\/(\w+)/)?.[1];
expect(extrinsicHex).toBeDefined();

// TODO: validate the contents of the extrinsic, when such functionality will be available in PAPI
});
});
});

Expand Down
7 changes: 4 additions & 3 deletions src/tip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,13 @@ export async function tipUserLink(
return preparedExtrinsic;
}

const { botTipAccount } = state;
const transactionHex = (await preparedExtrinsic.referendumExtrinsic.getEncodedData()).asHex();

const { txHash } = await preparedExtrinsic.referendumExtrinsic.signAndSubmit(botTipAccount);
const polkadotAppsUrl = `https://polkadot.js.org/apps/?rpc=${papiConfig.entries[network].wsUrl}#/`;
const extrinsicCreationLink = `${polkadotAppsUrl}extrinsics/decode/${txHash}`;
const extrinsicCreationLink = `${polkadotAppsUrl}extrinsics/decode/${transactionHex}`;
return { success: true, extrinsicCreationLink };
} catch (e) {
return { success: false, errorMessage: e instanceof Error ? e.stack ?? e.message : String(e) };
} finally {
client.destroy();
}
Expand Down
Loading