Skip to content

Commit

Permalink
Add deregistration; fix delegation (#248)
Browse files Browse the repository at this point in the history
* Add deregistration; fix delegation

Also fixes an error message to reduce noise, though soon we'll make this a custom error

* Lint and Fmt

* chore: add tests for deregistering stake

---------

Co-authored-by: Calvin Koepke <hello@calvinkoepke.com>
  • Loading branch information
Quantumplation and cjkoepke authored Feb 19, 2025
1 parent e6659fc commit 4d7bfa6
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 8 deletions.
6 changes: 6 additions & 0 deletions .changeset/calm-boats-fetch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@blaze-cardano/core": patch
"@blaze-cardano/tx": patch
---

Add support for deregistering stake credentials and fixes delegation for scripts
3 changes: 3 additions & 0 deletions packages/blaze-core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,9 @@ export type PoolId = OpaqueString<"PoolId">;
export const StakeRegistration = C.Serialization.StakeRegistration;
export type StakeRegistration = C.Serialization.StakeRegistration;

export const StakeDeregistration = C.Serialization.StakeDeregistration;
export type StakeDeregistration = C.Serialization.StakeDeregistration;

export const StakeDelegation = C.Serialization.StakeDelegation;
export type StakeDelegation = C.Serialization.StakeDelegation;

Expand Down
76 changes: 68 additions & 8 deletions packages/blaze-tx/src/tx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ import {
blake2b_256,
RedeemerTag,
StakeRegistration,
StakeDeregistration,
getBurnAddress,
setInConwayEra,
} from "@blaze-cardano/core";
Expand Down Expand Up @@ -134,8 +135,10 @@ export class TxBuilder {
private additionalSigners = 0;
private evaluator?: Evaluator;

private consumedDelegationHashes: Hash28ByteBase16[] = [];
private consumedMintHashes: Hash28ByteBase16[] = [];
private consumedWithdrawalHashes: Hash28ByteBase16[] = [];
private consumedDeregisterHashes: Hash28ByteBase16[] = [];
private consumedSpendInputs: string[] = [];
private minimumFee: bigint = 0n; // minimum fee for the transaction, in lovelace. For script eval purposes!
private feePadding: bigint = 0n; // A padding to add onto the fee; use only in emergencies, and open a ticket so we can fix the fee calculation please!
Expand Down Expand Up @@ -1698,10 +1701,9 @@ export class TxBuilder {
try {
evaluationFee = await this.evaluate(draft_tx);
} catch (e) {
console.log(
`An error occurred when trying to evaluate this transaction. Full CBOR: ${draft_tx.toCbor()}`,
throw new Error(
`An error occurred when trying to evaluate this transaction. Full CBOR: ${draft_tx.toCbor()}; Error: ${e}`,
);
throw e;
}
tw.setRedeemers(this.redeemers);
draft_tx.setWitnessSet(tw);
Expand Down Expand Up @@ -1820,14 +1822,27 @@ export class TxBuilder {
const vals = [...certs.values(), delegationCertificate];
certs.setValues(vals);
this.body.setCerts(certs);
const credentialHash = delegator.toCore().hash;
const insertIdx = this.insertSorted(
this.consumedDelegationHashes,
credentialHash,
);
const delegatorCredential = delegator.toCore();
if (delegatorCredential.type == CredentialType.ScriptHash) {
if (redeemer) {
this.requiredPlutusScripts.add(delegatorCredential.hash);
const redeemers = [...this.redeemers.values()];
for (const redeemer of redeemers) {
if (
redeemer.tag() == RedeemerTag.Reward &&
redeemer.index() >= BigInt(insertIdx)
) {
redeemer.setIndex(redeemer.index() + 1n);
}
}
redeemers.push(
Redeemer.fromCore({
index: 256, // todo: fix
index: insertIdx,
purpose: RedeemerPurpose["certificate"],
data: redeemer.toCore(),
executionUnits: {
Expand Down Expand Up @@ -1892,11 +1907,56 @@ export class TxBuilder {
}

/**
* Adds a certificate to deregister a staker.
* @throws {Error} Method not implemented.
* Adds a certificate to deregister a stake account.
*
* @param {Credential} credential - The credential to deregister.
* @returns {TxBuilder} The updated transaction builder.
*/
addDeregisterStake() {
throw new Error("Method not implemented.");
addDeregisterStake(credential: Credential, redeemer?: PlutusData) {
const stakeDeregistration: StakeDeregistration = new StakeDeregistration(
credential.toCore(),
);
const deregistrationCertificate: Certificate =
Certificate.newStakeDeregistration(stakeDeregistration);
const certs =
this.body.certs() ?? CborSet.fromCore([], Certificate.fromCore);
const vals = [...certs.values(), deregistrationCertificate];
certs.setValues(vals);
this.body.setCerts(certs);
const credentialHash = credential.toCore().hash;
// TODO: is this insertSorted mechanism a lurking bug, since the order might change?
const insertIdx = this.insertSorted(
this.consumedDeregisterHashes,
credentialHash,
);
// TODO: this should probably be based on whether the credential is a script credential
if (redeemer) {
this.requiredPlutusScripts.add(credentialHash);
const redeemers = [...this.redeemers.values()];
for (const redeemer of redeemers) {
if (
redeemer.tag() == RedeemerTag.Reward &&
redeemer.index() >= BigInt(insertIdx)
) {
redeemer.setIndex(redeemer.index() + 1n);
}
}
// Add the redeemer to the list of redeemers with execution units based on transaction parameters.
redeemers.push(
Redeemer.fromCore({
index: insertIdx,
purpose: RedeemerPurpose["certificate"], // TODO: Confirm the purpose of the redeemer.
data: redeemer.toCore(),
executionUnits: {
memory: this.params.maxExecutionUnitsPerTransaction.memory,
steps: this.params.maxExecutionUnitsPerTransaction.steps,
},
}),
);
// Update the transaction with the new list of redeemers.
this.redeemers.setValues(redeemers);
}
return this;
}

/**
Expand Down
63 changes: 63 additions & 0 deletions packages/blaze-tx/tests/tx/tx.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,13 @@ import {
Metadatum,
Metadata,
Ed25519KeyHashHex,
Credential,
CredentialType,
} from "@blaze-cardano/core";
import * as value from "../../src/value";
import { TxBuilder } from "../../src/tx";
import { makeUplcEvaluator } from "@blaze-cardano/vm";
import { Data } from "../../src";

function flatten<U>(iterator: IterableIterator<U> | undefined): U[] {
if (!iterator) {
Expand Down Expand Up @@ -657,4 +660,64 @@ describe("Transaction Building", () => {
expect(txComplete.body().inputs().values().length).toEqual(1);
expect(txComplete.body().outputs().length).toEqual(2);
});

it("should correctly build a transaction when deregistering stake from a payment credential", async () => {
const testAddress = Address.fromBech32(
"addr1q86ylp637q7hv7a9r387nz8d9zdhem2v06pjyg75fvcmen3rg8t4q3f80r56p93xqzhcup0w7e5heq7lnayjzqau3dfs7yrls5",
);

const credential = Credential.fromCore({
hash: testAddress.getProps().paymentPart!.hash,
type: CredentialType.KeyHash,
});

const tx = await new TxBuilder(hardCodedProtocolParams)
.setNetworkId(NetworkId.Testnet)
.setChangeAddress(testAddress)
.addUnspentOutputs([
new TransactionUnspentOutput(
new TransactionInput(TransactionId("0".repeat(64)), 0n),
new TransactionOutput(testAddress, value.makeValue(50_000_000n)),
),
])
.addDeregisterStake(credential)
.complete();

expect(tx.toCbor()).toEqual(
"84a400d9010281825820000000000000000000000000000000000000000000000000000000000000000000018182583901f44f8751f03d767ba51c4fe988ed289b7ced4c7e832223d44b31bcce2341d750452778e9a0962600af8e05eef6697c83df9f492103bc8b531a0316e8ab021a00028c5504d901028182018200581cf44f8751f03d767ba51c4fe988ed289b7ced4c7e832223d44b31bccea0f5f6",
);
});

it("should correctly build a transaction when deregistering stake from a script credential", async () => {
const testAddress = Address.fromBech32(
"addr1q86ylp637q7hv7a9r387nz8d9zdhem2v06pjyg75fvcmen3rg8t4q3f80r56p93xqzhcup0w7e5heq7lnayjzqau3dfs7yrls5",
);

const alwaysTrueScript = Script.newPlutusV2Script(
new PlutusV2Script(HexBlob("510100003222253330044a229309b2b2b9a1")),
);

const credential = Credential.fromCore({
hash: alwaysTrueScript.hash(),
type: CredentialType.ScriptHash,
});

const tx = await new TxBuilder(hardCodedProtocolParams)
.useEvaluator(makeUplcEvaluator(hardCodedProtocolParams, 1, 1))
.setNetworkId(NetworkId.Testnet)
.setChangeAddress(testAddress)
.addUnspentOutputs([
new TransactionUnspentOutput(
new TransactionInput(TransactionId("0".repeat(64)), 0n),
new TransactionOutput(testAddress, value.makeValue(50_000_000n)),
),
])
.provideScript(alwaysTrueScript)
.addDeregisterStake(credential, Data.void())
.complete();

expect(tx.toCbor()).toEqual(
"84a800d9010281825820000000000000000000000000000000000000000000000000000000000000000000018182583901f44f8751f03d767ba51c4fe988ed289b7ced4c7e832223d44b31bcce2341d750452778e9a0962600af8e05eef6697c83df9f492103bc8b531a0316c7b1021a0002ad4f04d901028182018201581c39c520d0627aafa728f7e4dd10142b77c257813c36f57e2cb88f72a50b5820897a7518496ae87c5925439e33b42d39252da54e79291aea2c831f3012e3564b0dd90102818258200000000000000000000000000000000000000000000000000000000000000000001082583901f44f8751f03d767ba51c4fe988ed289b7ced4c7e832223d44b31bcce2341d750452778e9a0962600af8e05eef6697c83df9f492103bc8b531a02f6ec89111a000403f7a205a182020082d87980821904b01a0002afe406d901028152510100003222253330044a229309b2b2b9a1f5f6",
);
});
});

0 comments on commit 4d7bfa6

Please sign in to comment.