Skip to content

Commit

Permalink
feat: voting delegation
Browse files Browse the repository at this point in the history
  • Loading branch information
slowbackspace committed Oct 8, 2024
1 parent 4c1576a commit 8058784
Show file tree
Hide file tree
Showing 12 changed files with 220 additions and 6 deletions.
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@
},
"typescript.tsdk": ".yarn/sdks/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true,
"cSpell.words": ["preprod"]
"cSpell.words": ["DEREGISTRATION", "drep", "preprod"]
}
8 changes: 8 additions & 0 deletions CHANGELOG.MD
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## Unreleased

### Added

- Support for voting delegation certificates

### Changed

- Upgraded cardano-serialization-lib to Chang-compatible release (v12)

## [2.2.1] - 2023-11-29

### Fixed
Expand Down
2 changes: 1 addition & 1 deletion docs/badge-coverage.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ export const CertificateType = {
STAKE_DEREGISTRATION: 1,
STAKE_DELEGATION: 2,
STAKE_POOL_REGISTRATION: 3,
STAKE_REGISTRATION_CONWAY: 7,
STAKE_DEREGISTRATION_CONWAY: 8,
VOTE_DELEGATION: 9,
} as const;

export const ERROR = {
Expand Down
28 changes: 27 additions & 1 deletion src/types/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,23 @@ export enum CardanoAddressType {
REWARD_SCRIPT = 15,
}

export enum CardanoDRepType {
KEY_HASH = 0,
SCRIPT_HASH = 1,
ABSTAIN = 2,
NO_CONFIDENCE = 3,
}

export type DRep =
| {
type: CardanoDRepType.KEY_HASH | CardanoDRepType.SCRIPT_HASH;
keyHash: string;
}
| {
type: CardanoDRepType.ABSTAIN | CardanoDRepType.NO_CONFIDENCE;
keyHash?: never;
};

export interface CoinSelectionResult {
tx: { body: string; hash: string; size: number };
inputs: Utxo[];
Expand Down Expand Up @@ -116,10 +133,19 @@ export interface CertificateStakePoolRegistration {
pool_parameters: Record<string, unknown>;
}

export interface CertificateVoteDelegation {
type: CertificateTypeType['VOTE_DELEGATION'];
dRep: {
type: CardanoDRepType;
keyHash: string;
};
}

export type Certificate =
| CertificateStakeRegistration
| CertificateStakeDelegation
| CertificateStakePoolRegistration;
| CertificateStakePoolRegistration
| CertificateVoteDelegation;

export interface Options {
feeParams?: { a: string };
Expand Down
30 changes: 30 additions & 0 deletions src/utils/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
UserOutput,
Asset,
ChangeOutput,
CardanoDRepType,
} from '../types/types';
import { CoinSelectionError } from './errors';

Expand Down Expand Up @@ -299,6 +300,34 @@ export const prepareCertificates = (
CardanoWasm.StakeDeregistration.new(stakeCred),
),
);
} else if (cert.type === CertificateType.VOTE_DELEGATION) {
let targetDRep: CardanoWasm.DRep;
switch (cert.dRep.type) {
case CardanoDRepType.ABSTAIN:
targetDRep = CardanoWasm.DRep.new_always_abstain();
break;
case CardanoDRepType.NO_CONFIDENCE:
targetDRep = CardanoWasm.DRep.new_always_no_confidence();
break;
case CardanoDRepType.KEY_HASH:
targetDRep = CardanoWasm.DRep.new_key_hash(
CardanoWasm.Ed25519KeyHash.from_hex(cert.dRep.keyHash),
);
break;
case CardanoDRepType.SCRIPT_HASH:
targetDRep = CardanoWasm.DRep.new_script_hash(
CardanoWasm.ScriptHash.from_hex(cert.dRep.keyHash),
);
break;
}

if (targetDRep) {
preparedCertificates.add(
CardanoWasm.Certificate.new_vote_delegation(
CardanoWasm.VoteDelegation.new(stakeCred, targetDRep),
),
);
}
} else {
throw new CoinSelectionError(ERROR.UNSUPPORTED_CERTIFICATE_TYPE);
}
Expand All @@ -311,6 +340,7 @@ export const calculateRequiredDeposit = (
): number => {
const CertificateDeposit = {
[CertificateType.STAKE_DELEGATION]: 0,
[CertificateType.VOTE_DELEGATION]: 0,
[CertificateType.STAKE_POOL_REGISTRATION]: 500000000,
[CertificateType.STAKE_REGISTRATION]: 2000000,
[CertificateType.STAKE_DEREGISTRATION]: -2000000,
Expand Down
2 changes: 2 additions & 0 deletions src/utils/trezor/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
transformToTokenBundle,
transformToTrezorInputs,
transformToTrezorOutputs,
drepIdToHex,
} from './transformations';
import { signTransaction } from './sign';

Expand All @@ -10,4 +11,5 @@ export {
transformToTrezorInputs,
transformToTrezorOutputs,
signTransaction,
drepIdToHex,
};
37 changes: 36 additions & 1 deletion src/utils/trezor/transformations.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import * as CardanoWasm from '@emurgo/cardano-serialization-lib-nodejs';

import {
CardanoAddressParameters,
CardanoInput,
CardanoOutput,
} from '../../types/trezor';
import { Asset, FinalOutput, Utxo } from '../../types/types';
import { Asset, CardanoDRepType, FinalOutput, Utxo } from '../../types/types';
import { parseAsset } from '../common';

interface AssetInPolicy {
Expand Down Expand Up @@ -92,3 +94,36 @@ export const transformToTrezorOutputs = (
};
});
};

export const drepIdToHex = (
drepId: string,
): {
type: CardanoDRepType.KEY_HASH | CardanoDRepType.SCRIPT_HASH;
hex: string;
} => {
const drep = CardanoWasm.DRep.from_bech32(drepId);
const kind = drep.kind() as unknown as
| CardanoDRepType.KEY_HASH
| CardanoDRepType.SCRIPT_HASH;

let drepHex: string | undefined;
switch (kind) {
case CardanoDRepType.KEY_HASH:
drepHex = drep.to_key_hash()?.to_hex();
break;
case CardanoDRepType.SCRIPT_HASH:
drepHex = drep.to_script_hash()?.to_hex();
break;
}

if (!drepHex) {
throw Error('Invalid drepId');
}

const drepData = {
type: kind,
hex: drepHex,
};
drep.free();
return drepData;
};
87 changes: 86 additions & 1 deletion tests/methods/fixtures/largestFirst.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { Certificate } from '../../../src/types/types';
import {
CardanoDRepType,
Certificate,
CertificateVoteDelegation,
} from '../../../src/types/types';
import {
changeAddress,
setMaxAdaInputs,
Expand Down Expand Up @@ -737,6 +741,87 @@ export const coinSelection = [
],
},
},
{
description:
'withdrawing rewards: 1 ADA only utxo, 1 change output, vote delegation abstain',
utxos: [utxo1],
outputs: [],
changeAddress: changeAddress,
certificates: [
{
type: 9,
dRep: {
type: 2, // abstain
},
} as CertificateVoteDelegation,
],
withdrawals: [
{
amount: '10000000',
stakingPath: "m/1852'/1815'/0'/2/0",
stakeAddress:
'stake1u8yk3dcuj8yylwvnzz953yups6mmuvt0vtjmxl2gmgceqjqz2yfd2',
},
],
accountPubKey:
'ec8fdf616242f430855ad7477acda53395eb30c295f5a7ef038712578877375b5a2f00353c9c5cc88c7ff18e71dc08724d90fc238213b789c0b02438e336be07',
options: {},
result: {
totalSpent: '177425',
fee: '177425',
deposit: '0',
inputs: [utxo1],
outputs: [
{
isChange: true,
address: changeAddress,
amount: '14822575',
assets: [],
},
],
},
},
{
description:
'withdrawing rewards: 1 ADA only utxo, 1 change output, vote delegation drep id',
utxos: [utxo1],
outputs: [],
changeAddress: changeAddress,
certificates: [
{
type: 9,
dRep: {
type: CardanoDRepType.KEY_HASH,
keyHash: '3a7f09d3df4cf66a7399c2b05bfa234d5a29560c311fc5db4c490711',
},
} as CertificateVoteDelegation,
],
withdrawals: [
{
amount: '10000000',
stakingPath: "m/1852'/1815'/0'/2/0",
stakeAddress:
'stake1u8yk3dcuj8yylwvnzz953yups6mmuvt0vtjmxl2gmgceqjqz2yfd2',
},
],
accountPubKey:
'ec8fdf616242f430855ad7477acda53395eb30c295f5a7ef038712578877375b5a2f00353c9c5cc88c7ff18e71dc08724d90fc238213b789c0b02438e336be07',
options: {},
result: {
totalSpent: '178745',
fee: '178745',
deposit: '0',
inputs: [utxo1],
outputs: [
{
isChange: true,
address: changeAddress,
amount: '14821255',
assets: [],
},
],
},
},
{
description:
'withdrawing rewards: multiple utxos, multiple withdrawals, 1 change output',
Expand Down
2 changes: 1 addition & 1 deletion tests/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const sanityCheck = (res: CoinSelectionResult): void => {
(acc, input) =>
acc.checked_add(
BigNum.from_str(
input.amount.find(a => a.unit === 'lovelace').quantity || '0',
input.amount.find(a => a.unit === 'lovelace')?.quantity || '0',
),
),
BigNum.from_str('0'),
Expand Down
19 changes: 19 additions & 0 deletions tests/utils/trezor/fixtures/transformations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,22 @@ export const transformToTrezorOutputs = [
],
},
];

export const drepIdToHex = [
{
description: 'keyHash drep',
drepId: 'drep16pxnn38ykshfahwmkaqmke3kdqaksg4w935d7uztvh8y5l48pxv',
result: {
type: 0,
hex: 'd04d39c4e4b42e9edddbb741bb6636683b6822ae2c68df704b65ce4a',
},
},
{
description: 'scriptHash drep',
drepId: 'drep_script16pxnn38ykshfahwmkaqmke3kdqaksg4w935d7uztvh8y5sh6f6d',
result: {
type: 1,
hex: 'd04d39c4e4b42e9edddbb741bb6636683b6822ae2c68df704b65ce4a',
},
},
];
6 changes: 6 additions & 0 deletions tests/utils/trezor/transformations.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,10 @@ describe('trezor transformation utils', () => {
).toMatchObject(f.result);
});
});

fixtures.drepIdToHex.forEach(f => {
test(f.description, () => {
expect(utils.drepIdToHex(f.drepId)).toStrictEqual(f.result);
});
});
});

0 comments on commit 8058784

Please sign in to comment.