Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

1630 ballot operation support in contract API #2050

Merged
merged 12 commits into from
Oct 30, 2022
26 changes: 26 additions & 0 deletions docs/ballot.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
title: Ballot Operation
id: ballot
author: Davis Sawali
---


The `Ballot` operation allows delegates to cast one `Yay`, `Nay`, or `Pass` ballot on a selected proposal. Delegates are only able to cast their votes during the **Exploration period**.
dsawali marked this conversation as resolved.
Show resolved Hide resolved
dsawali marked this conversation as resolved.
Show resolved Hide resolved


## Examples
The `Ballot` operation is currently available in the Contract API, and can be used as such:
```typescript
const op = await Tezos.contract.ballot({
period: 1,
dsawali marked this conversation as resolved.
Show resolved Hide resolved
proposal: 'PROPOSAL_HASH',
ballot: 'BALLOT_VOTE_STRING'
});

await op.confirmation();
```
- `period` is the voting period of the current protocol
dsawali marked this conversation as resolved.
Show resolved Hide resolved
- `proposal` is the proposal hash string that you (a delegate) would like to point your ballot towards
dsawali marked this conversation as resolved.
Show resolved Hide resolved
- `ballot` is your ballot vote (`yay`, `nay`, or `pass`)

For more information in regards to the Amendment (and Voting) Process refer to [this document](https://tezos.gitlab.io/alpha/voting.html)
10 changes: 8 additions & 2 deletions packages/taquito-ledger-signer/src/taquito-ledger-signer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,10 @@ export class LedgerSigner implements Signer {
messageToSend = chunkOperation(messageToSend, watermarkedBytes2buff);
const ledgerResponse = await this.signWithLedger(messageToSend);
let signature;
if (this.derivationType === DerivationType.ED25519 || this.derivationType === DerivationType.BIP32_ED25519) {
if (
this.derivationType === DerivationType.ED25519 ||
this.derivationType === DerivationType.BIP32_ED25519
) {
dsawali marked this conversation as resolved.
Show resolved Hide resolved
signature = ledgerResponse.slice(0, ledgerResponse.length - 2).toString('hex');
} else {
if (!validateResponse(ledgerResponse)) {
Expand Down Expand Up @@ -224,7 +227,10 @@ export class LedgerSigner implements Signer {
}

private getPrefixes() {
if (this.derivationType === DerivationType.ED25519 || this.derivationType === DerivationType.BIP32_ED25519) {
if (
this.derivationType === DerivationType.ED25519 ||
this.derivationType === DerivationType.BIP32_ED25519
) {
return {
prefPk: prefix[Prefix.EDPK],
prefPkh: prefix[Prefix.TZ1],
Expand Down
15 changes: 11 additions & 4 deletions packages/taquito-ledger-signer/test/taquito-ledger-signer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,17 +185,24 @@ describe('LedgerSigner test', () => {
});

it('Should sign Operation for tz1 bip32', async () => {
const signer = new LedgerSigner(mockTransport, "44'/1729'/1'/0'", true, DerivationType.BIP32_ED25519);
const signer = new LedgerSigner(
mockTransport,
"44'/1729'/1'/0'",
true,
DerivationType.BIP32_ED25519
);
dsawali marked this conversation as resolved.
Show resolved Hide resolved
const mocksig = Buffer.from(
'35c1f3340121965a1350af2082af3c83d4338c23c254591ec7a12fef5d4e9fc2a63f7051508cc41255894fe511cfd11af827e8f8e6c3730c3dd0775aff33dc029000',
'hex'
);
mockTransport.send.mockResolvedValue(mocksig);
const signature = await signer.sign('367325bbba406bc3f8c1bf12b27b6e8081064722d3342e34142c172b322ba0426b00c9fc72e8491bd2973e196f04ec6918ad5bcee22d8c0bbcb98d01e85200006760ff228c2c16cbca18bb782a106e51c43a131776f5dfad30ecb5d5e43eccbd6c00c9fc72e8491bd2973e196f04ec6918ad5bcee22dea0abdb98d01c35000a0c21e0000eadc0855adb415fa69a76fc10397dc2fb37039a000');
const signature = await signer.sign(
'367325bbba406bc3f8c1bf12b27b6e8081064722d3342e34142c172b322ba0426b00c9fc72e8491bd2973e196f04ec6918ad5bcee22d8c0bbcb98d01e85200006760ff228c2c16cbca18bb782a106e51c43a131776f5dfad30ecb5d5e43eccbd6c00c9fc72e8491bd2973e196f04ec6918ad5bcee22dea0abdb98d01c35000a0c21e0000eadc0855adb415fa69a76fc10397dc2fb37039a000'
);
const path = "44'/1729'/1'/0'";
const buff = transformPathToBuffer(path);
expect(mockTransport.send).toHaveBeenCalledTimes(2);
expect(mockTransport.send).toHaveBeenCalledWith(0x80, 0x04, 0x00, 0x03, buff)
expect(mockTransport.send).toHaveBeenCalledWith(0x80, 0x04, 0x00, 0x03, buff);
expect(signature).toEqual({
bytes:
'367325bbba406bc3f8c1bf12b27b6e8081064722d3342e34142c172b322ba0426b00c9fc72e8491bd2973e196f04ec6918ad5bcee22d8c0bbcb98d01e85200006760ff228c2c16cbca18bb782a106e51c43a131776f5dfad30ecb5d5e43eccbd6c00c9fc72e8491bd2973e196f04ec6918ad5bcee22dea0abdb98d01c35000a0c21e0000eadc0855adb415fa69a76fc10397dc2fb37039a000',
Expand All @@ -205,5 +212,5 @@ describe('LedgerSigner test', () => {
'367325bbba406bc3f8c1bf12b27b6e8081064722d3342e34142c172b322ba0426b00c9fc72e8491bd2973e196f04ec6918ad5bcee22d8c0bbcb98d01e85200006760ff228c2c16cbca18bb782a106e51c43a131776f5dfad30ecb5d5e43eccbd6c00c9fc72e8491bd2973e196f04ec6918ad5bcee22dea0abdb98d01c35000a0c21e0000eadc0855adb415fa69a76fc10397dc2fb37039a00035c1f3340121965a1350af2082af3c83d4338c23c254591ec7a12fef5d4e9fc2a63f7051508cc41255894fe511cfd11af827e8f8e6c3730c3dd0775aff33dc02',
sig: 'sigV2DADKhiwmvCaRS8QoxhM6DgXF8hTPbUBDbCd7vxkx5Do3rbJ8ZceS59b4c69z1XbtishJzit2RjorEpf6DpfS4paStBK',
});
})
});
});
12 changes: 12 additions & 0 deletions packages/taquito/src/contract/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,13 @@ import {
TxRollupBatchParams,
IncreasePaidStorageParams,
TransferTicketParams,
BallotParams,
} from '../operations/types';
import { ContractAbstraction, ContractStorageType, DefaultContractType } from './contract';
import { TxRollupBatchOperation } from '../operations/tx-rollup-batch-operation';
import { IncreasePaidStorageOperation } from '../operations/increase-paid-storage-operation';
import { TransferTicketOperation } from '../operations/transfer-ticket-operation';
import { BallotOperation } from '../operations';

export type ContractSchema = Schema | unknown;

Expand Down Expand Up @@ -214,4 +216,14 @@ export interface ContractProvider extends StorageProvider {
* @param TxRollupBatchParams Batch tx rollup operation parameter
*/
txRollupSubmitBatch(params: TxRollupBatchParams): Promise<TxRollupBatchOperation>;

/**
*
* @description Submit ballot for an ongoing proposal
*
* @returns An operation handle with the result from the RPC node
*
* @param BallotParams Ballot operation parameter
*/
ballot(params: BallotParams): Promise<BallotOperation>;
}
12 changes: 12 additions & 0 deletions packages/taquito/src/contract/prepare.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import {
RPCTransferTicketOperation,
IncreasePaidStorageParams,
RPCIncreasePaidStorageOperation,
BallotParams,
RPCBallotOperation,
} from '../operations/types';
import { DEFAULT_FEE, DEFAULT_GAS_LIMIT, DEFAULT_STORAGE_LIMIT } from '../constants';
import { format } from '@taquito/utils';
Expand Down Expand Up @@ -264,3 +266,13 @@ export const createIncreasePaidStorageOperation = async ({
destination,
} as RPCIncreasePaidStorageOperation;
};

export const createBallotOperation = async ({ source, period, proposal, ballot }: BallotParams) => {
return {
kind: OpKind.BALLOT,
source,
period,
proposal,
ballot,
} as RPCBallotOperation;
};
21 changes: 21 additions & 0 deletions packages/taquito/src/contract/rpc-contract-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
TxRollupBatchParams,
TransferTicketParams,
IncreasePaidStorageParams,
BallotParams,
} from '../operations/types';
import { DefaultContractType, ContractStorageType, ContractAbstraction } from './contract';
import { InvalidDelegationSource, RevealOperationError } from './errors';
Expand All @@ -40,6 +41,7 @@ import {
createTxRollupBatchOperation,
createTransferTicketOperation,
createIncreasePaidStorageOperation,
createBallotOperation,
} from './prepare';
import { smartContractAbstractionSemantic } from './semantic';
import {
Expand All @@ -54,6 +56,7 @@ import { TxRollupOriginationOperation } from '../operations/tx-rollup-originatio
import { TxRollupBatchOperation } from '../operations/tx-rollup-batch-operation';
import { TransferTicketOperation } from '../operations/transfer-ticket-operation';
import { IncreasePaidStorageOperation } from '../operations/increase-paid-storage-operation';
import { BallotOperation } from '../operations';

export class RpcContractProvider
extends OperationEmitter
Expand Down Expand Up @@ -585,6 +588,24 @@ export class RpcContractProvider
);
}

async ballot(params: BallotParams) {
dsawali marked this conversation as resolved.
Show resolved Hide resolved
const publicKeyHash = await this.signer.publicKeyHash();

if (params.source && validateAddress(params.source) !== ValidationResult.VALID) {
throw new InvalidAddressError(params.source);
}
const source = params.source ?? publicKeyHash;
const operation = await createBallotOperation({
...params,
source: source,
});
const ops = await this.addRevealOperationIfNeeded(operation, publicKeyHash);
dsawali marked this conversation as resolved.
Show resolved Hide resolved
const prepared = await this.prepareOperation({ operation: ops, source: publicKeyHash });
const opBytes = await this.forge(prepared);
const { hash, context, forgedBytes, opResponse } = await this.signAndInject(opBytes);
return new BallotOperation(hash, operation, publicKeyHash, forgedBytes, opResponse, context);
}

async at<T extends DefaultContractType = DefaultContractType>(
address: string,
contractAbstractionComposer: ContractAbstractionComposer<T> = (x) => x as any
Expand Down
44 changes: 44 additions & 0 deletions packages/taquito/src/operations/ballot-operation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { OperationContentsAndResult, OperationContentsAndResultBallot } from '@taquito/rpc';
import { Context } from '../context';
import { Operation } from './operations';
import { ForgedBytes, RPCBallotOperation } from './types';

/**
*
* @description BallotOperation provides utility functions to fetch a new operation of kind ballot
*
*/

dsawali marked this conversation as resolved.
Show resolved Hide resolved
export class BallotOperation extends Operation {
constructor(
hash: string,
private readonly params: RPCBallotOperation,
public readonly source: string,
raw: ForgedBytes,
results: OperationContentsAndResult[],
context: Context
) {
super(hash, raw, results, context);
}

get operationResults() {
const ballotOp =
Array.isArray(this.results) &&
(this.results.find((op) => op.kind === 'ballot') as OperationContentsAndResultBallot);
const result = ballotOp;

return result ? result : undefined;
}

get period() {
return this.params.period;
}

get proposal() {
return this.params.proposal;
}

get ballot() {
return this.params.ballot;
}
}
49 changes: 26 additions & 23 deletions packages/taquito/src/operations/index.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,29 @@
export {
OpKind,
withKind,
ParamsWithKind,
RPCOpWithFee,
RPCOpWithSource,
SourceKinds,
GasConsumingOperation,
StorageConsumingOperation,
FeeConsumingOperation,
OriginateParamsBase,
OriginateParams,
ActivationParams,
RPCOriginationOperation,
RPCRevealOperation,
ForgedBytes,
DelegateParams,
RegisterDelegateParams,
RPCDelegateOperation,
TransferParams,
RPCTransferOperation,
RPCActivateOperation,
RPCOperation,
PrepareOperationParams
OpKind,
withKind,
ParamsWithKind,
RPCOpWithFee,
RPCOpWithSource,
SourceKinds,
GasConsumingOperation,
StorageConsumingOperation,
FeeConsumingOperation,
OriginateParamsBase,
OriginateParams,
ActivationParams,
RPCOriginationOperation,
RPCRevealOperation,
ForgedBytes,
DelegateParams,
RegisterDelegateParams,
RPCDelegateOperation,
TransferParams,
RPCTransferOperation,
RPCActivateOperation,
RPCOperation,
PrepareOperationParams,
BallotParams,
RPCBallotOperation,
} from './types';
export {
TezosOperationError,
Expand All @@ -32,4 +34,5 @@ export { BatchOperation } from './batch-operation';
export { DelegateOperation } from './delegate-operation';
export { OriginationOperation } from './origination-operation';
export { TransactionOperation } from './transaction-operation';
export { BallotOperation } from './ballot-operation';
export { Operation } from './operations';
4 changes: 4 additions & 0 deletions packages/taquito/src/operations/operation-emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,10 @@ export abstract class OperationEmitter {
...getSource(op),
...getFee(op),
};
case OpKind.BALLOT:
return {
...op,
};

default:
throw new InvalidOperationKindError((op as any).kind);
Expand Down
22 changes: 21 additions & 1 deletion packages/taquito/src/operations/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
OpKind,
TransactionOperationParameter,
MichelsonV1Expression,
OperationContentsBallotEnum,
} from '@taquito/rpc';

export { OpKind } from '@taquito/rpc';
Expand Down Expand Up @@ -425,6 +426,24 @@ export interface RPCIncreasePaidStorageOperation {
destination: string;
}

/**
* @description Ballot operation params
*/
export interface BallotParams {
source?: string;
period: number;
proposal: string;
ballot: OperationContentsBallotEnum;
dsawali marked this conversation as resolved.
Show resolved Hide resolved
}

export interface RPCBallotOperation {
kind: OpKind.BALLOT;
source: string;
period: number;
proposal: string;
ballot: OperationContentsBallotEnum;
}

export type RPCOperation =
| RPCOriginationOperation
| RPCTransferOperation
Expand All @@ -435,7 +454,8 @@ export type RPCOperation =
| RPCTxRollupOriginationOperation
| RPCTxRollupBatchOperation
| RPCTransferTicketOperation
| RPCIncreasePaidStorageOperation;
| RPCIncreasePaidStorageOperation
| RPCBallotOperation;

export type PrepareOperationParams = {
operation: RPCOperation | RPCOperation[];
Expand Down
33 changes: 33 additions & 0 deletions packages/taquito/test/contract/rpc-contract-provider.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ describe('RpcContractProvider test', () => {
txRollupSubmitBatch: jest.Mock<any, any>;
transferTicket: jest.Mock<any, any>;
increasePaidStorage: jest.Mock<any, any>;
ballot: jest.Mock<any, any>;
};

const revealOp = (source: string) => ({
Expand Down Expand Up @@ -137,6 +138,7 @@ describe('RpcContractProvider test', () => {
txRollupSubmitBatch: jest.fn(),
transferTicket: jest.fn(),
increasePaidStorage: jest.fn(),
ballot: jest.fn(),
};

// Required for operations confirmation polling
Expand Down Expand Up @@ -1664,6 +1666,37 @@ describe('RpcContractProvider test', () => {
});
});

describe('ballot', () => {
it('should produce a ballot operation', async (done) => {
const result = await rpcContractProvider.ballot({
period: 2,
proposal: 'PtKathmankSpLLDALzWw7CGD2j2MtyveTwboEYokqUCP4a1LxMg',
ballot: 'yay',
});

expect(result.raw).toEqual({
opbytes: 'test',
opOb: {
branch: 'test',
contents: [
{
source: 'test_pub_key_hash',
kind: 'ballot',
period: 2,
proposal: 'PtKathmankSpLLDALzWw7CGD2j2MtyveTwboEYokqUCP4a1LxMg',
ballot: 'yay',
},
],
protocol: 'test_proto',
signature: 'test_sig',
},
counter: 0,
});

done();
});
});

describe('txRollupOriginate', () => {
it('should produce a reveal and txRollupOriginate operation', async (done) => {
const result = await rpcContractProvider.txRollupOriginate({
Expand Down
Loading