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

Add proposals operation support #2112

Merged
merged 6 commits into from
Nov 15, 2022
Merged
Show file tree
Hide file tree
Changes from 3 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
44 changes: 44 additions & 0 deletions docs/amendment_and_voting.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
---
title: Amendment and Voting
id: amendment_and_voting
author: Davis Sawali
---

In Tezos, the economic protocol can be amended by proposing and voting for changes. The protocol change will happen depending on the result of the votes.

## Proposals
A `Proposals` operation can be injected during a **Proposal Period**. It allows a delegate to submit a proposal identified by a protocol hash. The proposal with the most support is selected and will move on to the **Exploration Period**.
dsawali marked this conversation as resolved.
Show resolved Hide resolved

:::info
Note: Each delegate can submit a maximum of 20 proposals
:::

### Example
The `Proposals` operation is currently available in the Contract API, and can be used as such:
```typescript
const op = await Tezos.contract.proposals({
proposals: ['PROTOCOL_HASH1', 'PROTOCOL_HASH2']
});

await op.confirmation();
```
- `proposals` parameter takes in a list of Protocol hash(es) you would like to submit.

## Ballot
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** and the **Promotion period**

### Example
The `Ballot` operation is currently available in the Contract API, and can be used as such:
```typescript
const op = await Tezos.contract.ballot({
proposal: 'PROTOCOL_HASH',
ballot: 'BALLOT_VOTE_STRING'
});

await op.confirmation();
```
- `proposal` is the string that you (a delegate) would like to point your ballot towards. Information on the current proposal can be obtained by calling [this RPC endpoint](https://tezos.gitlab.io/alpha/rpc.html#get-block-id-votes-current-proposal). Alternatively, you could also get the protocol hash by using Taquito's RPC Client method `RpcClient.getCurrentProposal`. For more information on the `RpcClient` refer to [this document](https://tezostaquito.io/docs/rpc_package/)
- `ballot` is your ballot vote (`yay`, `nay`, or `pass`)


For more information in regards to the Amendment & Voting Process, refer to [this document](https://tezos.gitlab.io/alpha/voting.html)
23 changes: 0 additions & 23 deletions docs/ballot.md

This file was deleted.

12 changes: 12 additions & 0 deletions packages/taquito/src/contract/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,14 @@ import {
IncreasePaidStorageParams,
TransferTicketParams,
BallotParams,
ProposalsParams,
} 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';
import { ProposalsOperation } from '../operations/proposals-operation';

export type ContractSchema = Schema | unknown;

Expand Down Expand Up @@ -226,4 +228,14 @@ export interface ContractProvider extends StorageProvider {
* @param BallotParams Ballot operation parameter
*/
ballot(params: BallotParams): Promise<BallotOperation>;

/**
*
* @description Submit proposal
*
* @returns An operation handle with the result from the RPC node
*
* @param ProposalsParams Proposals operation parameter
*/
proposals(params: ProposalsParams): Promise<ProposalsOperation>;
}
10 changes: 10 additions & 0 deletions packages/taquito/src/contract/prepare.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import {
RPCIncreasePaidStorageOperation,
BallotParams,
RPCBallotOperation,
ProposalsParams,
RPCProposalsOperation,
} from '../operations/types';
import { DEFAULT_FEE, DEFAULT_GAS_LIMIT, DEFAULT_STORAGE_LIMIT } from '../constants';
import { format } from '@taquito/utils';
Expand Down Expand Up @@ -275,3 +277,11 @@ export const createBallotOperation = async ({ source, proposal, ballot }: Ballot
ballot,
} as RPCBallotOperation;
};

export const createProposalsOperation = async ({ source, proposals }: ProposalsParams) => {
return {
kind: OpKind.PROPOSALS,
source,
proposals,
} as RPCProposalsOperation;
};
28 changes: 28 additions & 0 deletions packages/taquito/src/contract/rpc-contract-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
TransferTicketParams,
IncreasePaidStorageParams,
BallotParams,
ProposalsParams,
} from '../operations/types';
import { DefaultContractType, ContractStorageType, ContractAbstraction } from './contract';
import { InvalidDelegationSource, RevealOperationError } from './errors';
Expand All @@ -42,6 +43,7 @@ import {
createTransferTicketOperation,
createIncreasePaidStorageOperation,
createBallotOperation,
createProposalsOperation,
} from './prepare';
import { smartContractAbstractionSemantic } from './semantic';
import {
Expand All @@ -57,6 +59,7 @@ 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';
import { ProposalsOperation } from '../operations/proposals-operation';

export class RpcContractProvider
extends OperationEmitter
Expand Down Expand Up @@ -613,6 +616,31 @@ export class RpcContractProvider
return new BallotOperation(hash, operation, publicKeyHash, forgedBytes, opResponse, context);
}

/**
*
* @description Submit a proposal to be voted on
dsawali marked this conversation as resolved.
Show resolved Hide resolved
*
* @returns An operation handle with the result from the rpc node
*
* @param BallotParams Proposals operation parameter
*/
async proposals(params: ProposalsParams) {
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 createProposalsOperation({
...params,
source: source,
});
const prepared = await this.prepareOperation({ operation, source: publicKeyHash });
dsawali marked this conversation as resolved.
Show resolved Hide resolved
const opBytes = await this.forge(prepared);
const { hash, context, forgedBytes, opResponse } = await this.signAndInject(opBytes);
return new ProposalsOperation(hash, operation, publicKeyHash, forgedBytes, opResponse, context);
}

async at<T extends DefaultContractType = DefaultContractType>(
address: string,
contractAbstractionComposer: ContractAbstractionComposer<T> = (x) => x as any
Expand Down
10 changes: 9 additions & 1 deletion packages/taquito/src/operations/operation-emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ export abstract class OperationEmitter {
let currentVotingPeriodPromise: Promise<VotingPeriodBlockResult | undefined> =
Promise.resolve(undefined);
ops.find(async (op) => {
if (op.kind === 'ballot') {
if (op.kind === 'ballot' || op.kind === 'proposals') {
try {
currentVotingPeriodPromise = this.rpc.getCurrentPeriod();
} catch (e) {
Expand Down Expand Up @@ -193,6 +193,14 @@ export abstract class OperationEmitter {
...op,
period: currentVotingPeriod?.voting_period.index,
};
case OpKind.PROPOSALS:
if (currentVotingPeriod === undefined) {
throw new RPCResponseError(`Failed to get the current voting period index`);
}
return {
...op,
period: currentVotingPeriod?.voting_period.index,
};

default:
throw new InvalidOperationKindError((op as any).kind);
Expand Down
39 changes: 39 additions & 0 deletions packages/taquito/src/operations/proposals-operation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { OperationContentsAndResult, OperationContentsAndResultProposals } from '@taquito/rpc';
import { Context } from '../context';
import { Operation } from './operations';
import { ForgedBytes, RPCProposalsOperation } from './types';

/**
*
* @description ProposalsOperation provides utility functions to fetch a new operation of kind proposals
*
*/
export class ProposalsOperation extends Operation {
constructor(
hash: string,
private readonly params: RPCProposalsOperation,
public readonly source: string,
raw: ForgedBytes,
results: OperationContentsAndResult[],
context: Context
) {
super(hash, raw, results, context);
}

get operationResults() {
const proposalsOp =
Array.isArray(this.results) &&
(this.results.find((op) => op.kind === 'proposals') as OperationContentsAndResultProposals);
const result = proposalsOp;

return result ? result : undefined;
}

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

get period() {
return this.operationResults?.period;
}
}
15 changes: 14 additions & 1 deletion packages/taquito/src/operations/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,18 @@ export interface RPCBallotOperation {
ballot: BallotEnum;
}

export interface ProposalsParams {
source?: string;
proposals: string[];
}

export interface RPCProposalsOperation {
kind: OpKind.PROPOSALS;
source: string;
period: number;
proposals: string[];
}

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

export type PrepareOperationParams = {
operation: RPCOperation | RPCOperation[];
Expand Down
28 changes: 28 additions & 0 deletions packages/taquito/test/contract/rpc-contract-provider.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1706,6 +1706,34 @@ describe('RpcContractProvider test', () => {
});
});

describe('proposals', () => {
it('should produce a proposals operation', async (done) => {
const result = await rpcContractProvider.proposals({
proposals: ['PtKathmankSpLLDALzWw7CGD2j2MtyveTwboEYokqUCP4a1LxMg'],
});

expect(result.raw).toEqual({
opbytes: 'test',
opOb: {
branch: 'test',
contents: [
{
source: 'test_pub_key_hash',
kind: 'proposals',
period: 1,
proposals: ['PtKathmankSpLLDALzWw7CGD2j2MtyveTwboEYokqUCP4a1LxMg'],
},
],
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
74 changes: 74 additions & 0 deletions packages/taquito/test/operations/proposals-operation.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { ProposalsOperation } from '../../src/operations/proposals-operation';
import { OperationContentsAndResult } from '@taquito/rpc';
import { ForgedBytes } from '../../src/operations/types';
import { defaultConfigConfirmation } from '../../src/context';

describe('Proposals operation', () => {
let fakeContext: any;

const fakeForgedBytes = {} as ForgedBytes;

const successfulResult = [
{
kind: 'proposals',
source: 'tz1YPSCGWXwBdTncK2aCctSZAXWvGsGwVJqU',
period: 2,
proposals: ['PtKathmankSpLLDALzWw7CGD2j2MtyveTwboEYokqUCP4a1LxMg'],
},
] as OperationContentsAndResult[];

beforeEach(() => {
fakeContext = {
rpc: {
getBlock: jest.fn(),
},
config: { ...defaultConfigConfirmation },
};

fakeContext.rpc.getBlock.mockResolvedValue({
operations: [[{ hash: 'ooBghN2ok5EpgEuMqYWqvfwNLBiK9eNFoPai91iwqk2nRCyUKgE' }], [], [], []],
header: {
level: 1,
},
});
});

it('should return source of Proposals operation', () => {
const op = new ProposalsOperation(
'ooBghN2ok5EpgEuMqYWqvfwNLBiK9eNFoPai91iwqk2nRCyUKgE',
{} as any,
'tz1YPSCGWXwBdTncK2aCctSZAXWvGsGwVJqU',
fakeForgedBytes,
successfulResult,
fakeContext
);

expect(op.source).toEqual('tz1YPSCGWXwBdTncK2aCctSZAXWvGsGwVJqU');
});

it('should return period of Proposals operation', () => {
const op = new ProposalsOperation(
'ooBghN2ok5EpgEuMqYWqvfwNLBiK9eNFoPai91iwqk2nRCyUKgE',
{} as any,
'tz1YPSCGWXwBdTncK2aCctSZAXWvGsGwVJqU',
fakeForgedBytes,
successfulResult,
fakeContext
);

expect(op.period).toEqual(2);
});

it('should return proposal hash of Proposals operation', () => {
const op = new ProposalsOperation(
'ooBghN2ok5EpgEuMqYWqvfwNLBiK9eNFoPai91iwqk2nRCyUKgE',
{ proposals: ['PtKathmankSpLLDALzWw7CGD2j2MtyveTwboEYokqUCP4a1LxMg'] } as any,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this possible to be typed as something other than any?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's a good question, but I suspect the reason we use any for all of the params in the test is because we might or might not pass anything at all. So any gives us the flexibility.

In general I'd stay away from the use of any as you're probably suggesting, but I think this usage in tests is probably okay.

'tz1YPSCGWXwBdTncK2aCctSZAXWvGsGwVJqU',
fakeForgedBytes,
successfulResult,
fakeContext
);

expect(op.proposals).toEqual(['PtKathmankSpLLDALzWw7CGD2j2MtyveTwboEYokqUCP4a1LxMg']);
});
});
2 changes: 1 addition & 1 deletion website/sidebars.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ const sidebars = {
items: [
'quick_start',
'react-template',
'amendment_and_voting',
'batch_API',
'ballot',
'beaconwallet-singleton',
'confirmation_event_stream',
'set_delegate',
Expand Down
2 changes: 1 addition & 1 deletion website/sidebars.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
"items": [
"quick_start",
"boilerplate",
"amendment_and_voting",
"batch_API",
"ballot",
"beaconwallet-singleton",
"failwith_errors",
"confirmation_event_stream",
Expand Down
Loading