Skip to content

Commit

Permalink
Add a partial support for vote program (#167)
Browse files Browse the repository at this point in the history
* Add a partial support for vote program

* Add tests

* Add some comments

* Fix linting errors
  • Loading branch information
unordered-set authored Jan 11, 2022
1 parent 4611d3e commit 291286e
Show file tree
Hide file tree
Showing 3 changed files with 193 additions and 0 deletions.
26 changes: 26 additions & 0 deletions src/solana/_layouts/vote_instructions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"""Byte layouts for vote program instructions."""
from enum import IntEnum

from construct import Switch # type: ignore
from construct import Int32ul, Int64ul
from construct import Struct as cStruct


class InstructionType(IntEnum):
"""Instruction types for vote program."""

WITHDRAW_FROM_VOTE_ACCOUNT = 3


_WITHDRAW_FROM_VOTE_ACCOUNT_LAYOUT = cStruct("lamports" / Int64ul)

VOTE_INSTRUCTIONS_LAYOUT = cStruct(
"instruction_type" / Int32ul,
"args"
/ Switch(
lambda this: this.instruction_type,
{
InstructionType.WITHDRAW_FROM_VOTE_ACCOUNT: _WITHDRAW_FROM_VOTE_ACCOUNT_LAYOUT,
},
),
)
58 changes: 58 additions & 0 deletions src/solana/vote_program.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
"""Library to interface with the vote program."""
from __future__ import annotations

from typing import NamedTuple

from solana._layouts.vote_instructions import VOTE_INSTRUCTIONS_LAYOUT, InstructionType
from solana.publickey import PublicKey
from solana.transaction import AccountMeta, TransactionInstruction

VOTE_PROGRAM_ID: PublicKey = PublicKey("Vote111111111111111111111111111111111111111")
"""Public key that identifies the Vote program."""


# Instrection Params
class WithdrawFromVoteAccountParams(NamedTuple):
"""Transfer SOL from vote account to identity."""

vote_account_from_pubkey: PublicKey
""""""
to_pubkey: PublicKey
""""""
lamports: int
""""""
withdrawer: PublicKey
""""""


def withdraw_from_vote_account(params: WithdrawFromVoteAccountParams) -> TransactionInstruction:
"""Generate an instruction that transfers lamports from a vote account to any other.
>>> from solana.publickey import PublicKey
>>> from solana.keypair import Keypair
>>> vote = PublicKey(1)
>>> withdrawer = Keypair.from_seed(bytes([0]*32))
>>> instruction = withdraw_from_vote_account(
... WithdrawFromVoteAccountParams(
... vote_account_from_pubkey=vote,
... to_pubkey=withdrawer,
... withdrawer=withdrawer,
... lamports=3_000_000_000,
... )
... )
>>> type(instruction)
<class 'solana.transaction.TransactionInstruction'>
"""
data = VOTE_INSTRUCTIONS_LAYOUT.build(
dict(instruction_type=InstructionType.WITHDRAW_FROM_VOTE_ACCOUNT, args=dict(lamports=params.lamports))
)

return TransactionInstruction(
keys=[
AccountMeta(pubkey=params.vote_account_from_pubkey, is_signer=False, is_writable=True),
AccountMeta(pubkey=params.to_pubkey, is_signer=False, is_writable=True),
AccountMeta(pubkey=params.withdrawer, is_signer=True, is_writable=True),
],
program_id=VOTE_PROGRAM_ID,
data=data,
)
109 changes: 109 additions & 0 deletions tests/unit/test_vote_program.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
"""Unit tests for solana.vote_program."""
import base64

import solana.vote_program as vp
import solana.transaction as txlib
from solana.keypair import Keypair
from solana.publickey import PublicKey


def test_withdraw_from_vote_account():
withdrawer_keypair = Keypair.from_secret_key(
bytes(
[
134,
123,
27,
208,
227,
175,
253,
99,
4,
81,
170,
231,
186,
141,
177,
142,
197,
139,
94,
6,
157,
2,
163,
89,
150,
121,
235,
86,
185,
22,
1,
233,
58,
133,
229,
39,
212,
71,
254,
72,
246,
45,
160,
156,
129,
199,
18,
189,
53,
143,
98,
72,
182,
106,
69,
29,
38,
145,
119,
190,
13,
105,
157,
112,
]
)
)
vote_account_pubkey = PublicKey("CWqJy1JpmBcx7awpeANfrPk6AsQKkmego8ujjaYPGFEk")
receiver_account_pubkey = PublicKey("A1V5gsis39WY42djdTKUFsgE5oamk4nrtg16WnKTuzZK")

# solana withdraw-from-vote-account --dump-transaction-message \
# CWqJy1JpmBcx7awpeANfrPk6AsQKkmego8ujjaYPGFEk A1V5gsis39WY42djdTKUFsgE5oamk4nrtg16WnKTuzZK \
# --authorized-withdrawer withdrawer.json \
# 2 \
# --blockhash Add1tV7kJgNHhTtx3Dgs6dhC7kyXrGJQZ2tJGW15tLDH \
# --sign-only -k withdrawer.json
wire_msg = base64.b64decode(
b"AQABBDqF5SfUR/5I9i2gnIHHEr01j2JItmpFHSaRd74NaZ1wqxUGDtH5ah3TqEKWjcTmfHkpZC1h57NJL8Sx7Q6Olm2F2O70oOvzt1HgIVu+nySaSrWtJiK1eDacPPDWRxCwFgdhSB01dHS7fE12JOvTvbPYNV5z0RBD/A2jU4AAAAAAjxrQaMS7FjmaR++mvFr3XE6XbzMUTMJUIpITrUWBzGwBAwMBAgAMAwAAAACUNXcAAAAA" # noqa: E501 pylint: disable=line-too-long
)

txn = txlib.Transaction(fee_payer=withdrawer_keypair.public_key)
txn.recent_blockhash = "Add1tV7kJgNHhTtx3Dgs6dhC7kyXrGJQZ2tJGW15tLDH"

txn.add(
vp.withdraw_from_vote_account(
vp.WithdrawFromVoteAccountParams(
vote_account_from_pubkey=vote_account_pubkey,
to_pubkey=receiver_account_pubkey,
withdrawer=withdrawer_keypair.public_key,
lamports=2_000_000_000,
)
)
)

serialized_message = txn.serialize_message()
assert serialized_message == wire_msg

0 comments on commit 291286e

Please sign in to comment.