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
2 changes: 2 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
target-branch: "preprod"
schedule:
interval: "weekly"
day: "monday"
Expand Down Expand Up @@ -33,6 +34,7 @@ updates:

- package-ecosystem: "github-actions"
directory: "/"
target-branch: "preprod"
schedule:
interval: "monthly"
labels:
Expand Down
11 changes: 11 additions & 0 deletions src/__tests__/tokenFlowCertificates.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,17 @@ describe("draftCertificateToBadge", () => {
color: "text-blue-500 dark:text-blue-400",
});
});

it("maps a vote-delegation DRep id", () => {
const badge = draftCertificateToBadge({
id: "c-1",
kind: "VoteDelegation",
dRepId: "Always Abstain",
origin: "user",
});
expect(badge.label).toBe("Vote Delegation");
expect(badge.detail).toBe("Always Abstain");
});
});

describe("meshVoteToBadge", () => {
Expand Down
22 changes: 22 additions & 0 deletions src/__tests__/txBuilderStore.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,28 @@ describe("tx-builder store creating stake actions and votes", () => {
expect(state.touched[outputId]).toBe(true);
});

it("register and a DRep id stay separate certificates", () => {
useTxBuilderStore.getState().addStakeAction({ type: "register" });
useTxBuilderStore.getState().addStakeAction({
type: "voteDelegation",
dRepId: "Always Abstain",
});
expect(
useTxBuilderStore.getState().draft.certificates.map((c) => ({
kind: c.kind,
dRepId: c.dRepId,
pairId: c.pairId,
})),
).toEqual([
{ kind: "RegisterStake", dRepId: undefined, pairId: undefined },
{
kind: "VoteDelegation",
dRepId: "Always Abstain",
pairId: undefined,
},
]);
});

it("removeCertificate drops a user-added pair atomically", () => {
useTxBuilderStore.getState().addStakeAction({
type: "registerAndDelegate",
Expand Down
32 changes: 30 additions & 2 deletions src/__tests__/txDraftFromTxJson.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,12 +133,13 @@ describe("isDraftCompatible", () => {
},
],
[
"unsupported certificate types (VoteDelegation)",
"unsupported certificate types (StakeAndVoteDelegation)",
{
certificates: [
stakeCert({
type: "VoteDelegation",
type: "StakeAndVoteDelegation",
stakeKeyAddress: STAKE_ADDRESS,
poolKeyHash: "aa".repeat(28),
drep: { dRepId: "drep1abc" },
}),
],
Expand Down Expand Up @@ -481,6 +482,33 @@ describe("staking certificate transactions", () => {
expect(isDraftCompatible(deregister).compatible).toBe(true);
});

test("accepts VoteDelegation including Always Abstain", () => {
const drep = certBody([
stakeCert({
type: "VoteDelegation",
stakeKeyAddress: STAKE_ADDRESS,
drep: { dRepId: "drep1abc" },
}),
]);
expect(isDraftCompatible(drep).compatible).toBe(true);

const abstain = certBody([
stakeCert({
type: "VoteDelegation",
stakeKeyAddress: STAKE_ADDRESS,
drep: { alwaysAbstain: null },
}),
]);
expect(isDraftCompatible(abstain).compatible).toBe(true);

const { draft } = txJsonToDraft(abstain, { walletAddress: WALLET_ADDRESS });
expect(draft.certificates[0]).toMatchObject({
kind: "VoteDelegation",
dRepId: "Always Abstain",
originalStakeAddress: STAKE_ADDRESS,
});
});

test("cert-only body does not trip the no-outputs gate", () => {
const body = certBody([delegateCert()], { outputs: [] });
expect(isDraftCompatible(body).compatible).toBe(true);
Expand Down
30 changes: 30 additions & 0 deletions src/__tests__/txDraftMutations.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,36 @@ describe("tx-draft stake action mutations", () => {
expect(delegate!.pairId).toBe(register!.pairId);
});

test("register adds a single unpaired RegisterStake with no pool", () => {
const { draft, certificateIds } = addStakeAction(createDraft("d1"), {
type: "register",
});
expect(certificateIds).toHaveLength(1);
expect(draft.certificates).toEqual([
{
id: certificateIds[0],
kind: "RegisterStake",
origin: "user",
},
]);
});

test("voteDelegation stores the DRep id on its own certificate", () => {
const { draft, certificateIds } = addStakeAction(createDraft("d1"), {
type: "voteDelegation",
dRepId: "Always Abstain",
});
expect(certificateIds).toHaveLength(1);
expect(draft.certificates).toEqual([
{
id: certificateIds[0],
kind: "VoteDelegation",
dRepId: "Always Abstain",
origin: "user",
},
]);
});

test("delegate adds a single unpaired user cert with the pool", () => {
const { draft, certificateIds } = addStakeAction(createDraft("d1"), {
type: "delegate",
Expand Down
20 changes: 20 additions & 0 deletions src/__tests__/txDraftToTxBuilder.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,26 @@ describe("applyDraftToTxBuilder certificates", () => {
"DeregisterStake",
);
});

test("VoteDelegation Always Abstain is a separate cert from RegisterStake", () => {
const { draft } = addStakeAction(
addStakeAction(createDraft("d1"), { type: "register" }).draft,
{ type: "voteDelegation", dRepId: "Always Abstain" },
);
const built = body(applyDraftToTxBuilder(bareTxBuilder(), draft, certCtx));
expect(built.certificates.map((c: any) => c.certType.type)).toEqual([
"RegisterStake",
"VoteDelegation",
]);
expect((built.certificates[1] as any).certType.drep).toEqual({
alwaysAbstain: null,
});
expect((built.certificates[1] as any).certType.poolId).toBeUndefined();
for (const cert of built.certificates) {
expect(cert.type).toBe("SimpleScriptCertificate");
expect((cert as any).certType.stakeKeyAddress).toBe(REWARD_ADDRESS);
}
});
});

describe("applyDraftToTxBuilder rationale edits", () => {
Expand Down
51 changes: 51 additions & 0 deletions src/__tests__/txDraftValidate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,57 @@ describe("validateDraft certificates", () => {
expect(validateDraft(deregisterOnly, active)).toEqual([]);
});

test("vote-delegation-only on an unregistered credential is an error", () => {
const voteOnly = addCertificate(createDraft("d1"), {
kind: "VoteDelegation",
dRepId: "Always Abstain",
}).draft;
expect(codes(validateDraft(voteOnly, inactive))).toEqual([
"cert-vote-unregistered",
]);
});

test("register + Always Abstain on an unregistered credential passes", () => {
let { draft } = addCertificate(createDraft("d1"), {
kind: "RegisterStake",
});
({ draft } = addCertificate(draft, {
kind: "VoteDelegation",
dRepId: "Always Abstain",
}));
expect(validateDraft(draft, inactive)).toEqual([]);
});

test("Always Abstain on a registered credential passes", () => {
const voteOnly = addCertificate(createDraft("d1"), {
kind: "VoteDelegation",
dRepId: "Always Abstain",
}).draft;
expect(validateDraft(voteOnly, active)).toEqual([]);
});

test("vote delegation without a target is an error", () => {
const missing = addCertificate(createDraft("d1"), {
kind: "VoteDelegation",
}).draft;
expect(codes(validateDraft(missing, { network: 0 }))).toEqual([
"cert-drep-missing",
]);
});

test("vote delegation accepts a real drep id and rejects junk", () => {
const drep = (dRepId: string) =>
addCertificate(createDraft("d1"), { kind: "VoteDelegation", dRepId })
.draft;
const valid = "drep1yw42424242424242424242424242424242424242424242sd640tm";
expect(validateDraft(drep(valid), active)).toEqual([]);
for (const bad of ["drep1abc", "aa".repeat(28), "Auto Abstain"]) {
expect(codes(validateDraft(drep(bad), active))).toEqual([
"cert-drep-missing",
]);
}
});

test("unknown state skips every registration check", () => {
const unknown = { network: 0, hasStakeContext: true };
expect(validateDraft(delegationDraft(POOL_ID), unknown)).toEqual([]);
Expand Down
Loading
Loading