Skip to content

Commit

Permalink
stake-pool-py: Update to newer solana-py / solders (#7050)
Browse files Browse the repository at this point in the history
* stake-pool-py: Update to newer solana-py / solders

* Harden tests further
  • Loading branch information
joncinque authored Jul 25, 2024
1 parent 2d2c4bb commit 5f0190c
Show file tree
Hide file tree
Showing 28 changed files with 788 additions and 792 deletions.
34 changes: 17 additions & 17 deletions stake-pool/py/bot/rebalance.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
import asyncio
import json

from solana.keypair import Keypair
from solana.publickey import PublicKey
from solders.keypair import Keypair
from solders.pubkey import Pubkey
from solana.rpc.async_api import AsyncClient
from solana.rpc.commitment import Confirmed

Expand All @@ -27,30 +27,30 @@ async def get_client(endpoint: str) -> AsyncClient:
return async_client


async def rebalance(endpoint: str, stake_pool_address: PublicKey, staker: Keypair, retained_reserve_amount: float):
async def rebalance(endpoint: str, stake_pool_address: Pubkey, staker: Keypair, retained_reserve_amount: float):
async_client = await get_client(endpoint)

resp = await async_client.get_epoch_info(commitment=Confirmed)
epoch = resp['result']['epoch']
epoch_resp = await async_client.get_epoch_info(commitment=Confirmed)
epoch = epoch_resp.value.epoch
resp = await async_client.get_account_info(stake_pool_address, commitment=Confirmed)
data = resp['result']['value']['data']
stake_pool = StakePool.decode(data[0], data[1])
data = resp.value.data if resp.value else bytes()
stake_pool = StakePool.decode(data)

print(f'Stake pool last update epoch {stake_pool.last_update_epoch}, current epoch {epoch}')
if stake_pool.last_update_epoch != epoch:
print('Updating stake pool')
await update_stake_pool(async_client, staker, stake_pool_address)
resp = await async_client.get_account_info(stake_pool_address, commitment=Confirmed)
data = resp['result']['value']['data']
stake_pool = StakePool.decode(data[0], data[1])
data = resp.value.data if resp.value else bytes()
stake_pool = StakePool.decode(data)

resp = await async_client.get_minimum_balance_for_rent_exemption(STAKE_LEN)
stake_rent_exemption = resp['result']
rent_resp = await async_client.get_minimum_balance_for_rent_exemption(STAKE_LEN)
stake_rent_exemption = rent_resp.value
retained_reserve_lamports = int(retained_reserve_amount * LAMPORTS_PER_SOL)

resp = await async_client.get_account_info(stake_pool.validator_list, commitment=Confirmed)
data = resp['result']['value']['data']
validator_list = ValidatorList.decode(data[0], data[1])
val_resp = await async_client.get_account_info(stake_pool.validator_list, commitment=Confirmed)
data = val_resp.value.data if val_resp.value else bytes()
validator_list = ValidatorList.decode(data)

print('Stake pool stats:')
print(f'* {stake_pool.total_lamports} total lamports')
Expand Down Expand Up @@ -107,7 +107,7 @@ def keypair_from_file(keyfile_name: str) -> Keypair:
data = keyfile.read()
int_list = json.loads(data)
bytes_list = [value.to_bytes(1, 'little') for value in int_list]
return Keypair.from_secret_key(b''.join(bytes_list))
return Keypair.from_seed(b''.join(bytes_list))


if __name__ == "__main__":
Expand All @@ -124,9 +124,9 @@ def keypair_from_file(keyfile_name: str) -> Keypair:
help='RPC endpoint to use, e.g. https://api.mainnet-beta.solana.com')

args = parser.parse_args()
stake_pool = PublicKey(args.stake_pool)
stake_pool = Pubkey(args.stake_pool)
staker = keypair_from_file(args.staker)
print(f'Rebalancing stake pool {stake_pool}')
print(f'Staker public key: {staker.public_key}')
print(f'Staker public key: {staker.pubkey()}')
print(f'Amount to leave in the reserve: {args.reserve_amount} SOL')
asyncio.run(rebalance(args.endpoint, stake_pool, staker, args.reserve_amount))
24 changes: 10 additions & 14 deletions stake-pool/py/optional-requirements.txt
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
attrs==22.1.0
flake8==5.0.3
iniconfig==1.1.1
flake8==7.1.0
iniconfig==2.0.0
mccabe==0.7.0
mypy==0.971
mypy-extensions==0.4.3
packaging==21.3
pluggy==1.0.0
py==1.11.0
pycodestyle==2.9.0
pyflakes==2.5.0
pyparsing==3.0.9
pytest==7.1.2
pytest-asyncio==0.19.0
tomli==2.0.1
mypy==1.11.0
mypy-extensions==1.0.0
packaging==24.1
pluggy==1.5.0
pycodestyle==2.12.0
pyflakes==3.2.0
pytest==8.3.1
pytest-asyncio==0.23.8
27 changes: 11 additions & 16 deletions stake-pool/py/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,19 +1,14 @@
anyio==3.6.1
base58==2.1.1
cachetools==4.2.4
anyio==4.4.0
certifi==2024.7.4
cffi==1.15.1
charset-normalizer==2.1.0
construct==2.10.68
h11==0.12.0
httpcore==0.15.0
httpx==0.23.0
construct-typing==0.5.6
h11==0.14.0
httpcore==1.0.5
httpx==0.27.0
idna==3.7
pycparser==2.21
PyNaCl==1.5.0
requests==2.32.0
rfc3986==1.5.0
sniffio==1.2.0
solana==0.18.1
typing_extensions==4.3.0
urllib3==1.26.19
jsonalias==0.1.1
sniffio==1.3.1
solana==0.34.2
solders==0.21.0
typing_extensions==4.12.2
websockets==11.0.3
41 changes: 23 additions & 18 deletions stake-pool/py/spl_token/actions.py
Original file line number Diff line number Diff line change
@@ -1,57 +1,62 @@
from solana.publickey import PublicKey
from solana.keypair import Keypair
from solders.pubkey import Pubkey
from solders.keypair import Keypair
from solana.rpc.async_api import AsyncClient
from solana.rpc.commitment import Confirmed
from solana.rpc.types import TxOpts
from solana.transaction import Transaction
import solana.system_program as sys
import solders.system_program as sys

from spl.token.constants import TOKEN_PROGRAM_ID
from spl.token.async_client import AsyncToken
from spl.token._layouts import MINT_LAYOUT
import spl.token.instructions as spl_token


OPTS = TxOpts(skip_confirmation=False, preflight_commitment=Confirmed)


async def create_associated_token_account(
client: AsyncClient,
payer: Keypair,
owner: PublicKey,
mint: PublicKey
) -> PublicKey:
txn = Transaction()
owner: Pubkey,
mint: Pubkey
) -> Pubkey:
txn = Transaction(fee_payer=payer.pubkey())
create_txn = spl_token.create_associated_token_account(
payer=payer.public_key, owner=owner, mint=mint
payer=payer.pubkey(), owner=owner, mint=mint
)
txn.add(create_txn)
await client.send_transaction(txn, payer, opts=TxOpts(skip_confirmation=False, preflight_commitment=Confirmed))
return create_txn.keys[1].pubkey
recent_blockhash = (await client.get_latest_blockhash()).value.blockhash
await client.send_transaction(txn, payer, recent_blockhash=recent_blockhash, opts=OPTS)
return create_txn.accounts[1].pubkey


async def create_mint(client: AsyncClient, payer: Keypair, mint: Keypair, mint_authority: PublicKey):
async def create_mint(client: AsyncClient, payer: Keypair, mint: Keypair, mint_authority: Pubkey):
mint_balance = await AsyncToken.get_min_balance_rent_for_exempt_for_mint(client)
print(f"Creating pool token mint {mint.public_key}")
txn = Transaction()
print(f"Creating pool token mint {mint.pubkey()}")
txn = Transaction(fee_payer=payer.pubkey())
txn.add(
sys.create_account(
sys.CreateAccountParams(
from_pubkey=payer.public_key,
new_account_pubkey=mint.public_key,
from_pubkey=payer.pubkey(),
to_pubkey=mint.pubkey(),
lamports=mint_balance,
space=MINT_LAYOUT.sizeof(),
program_id=TOKEN_PROGRAM_ID,
owner=TOKEN_PROGRAM_ID,
)
)
)
txn.add(
spl_token.initialize_mint(
spl_token.InitializeMintParams(
program_id=TOKEN_PROGRAM_ID,
mint=mint.public_key,
mint=mint.pubkey(),
decimals=9,
mint_authority=mint_authority,
freeze_authority=None,
)
)
)
recent_blockhash = (await client.get_latest_blockhash()).value.blockhash
await client.send_transaction(
txn, payer, mint, opts=TxOpts(skip_confirmation=False, preflight_commitment=Confirmed))
txn, payer, mint, recent_blockhash=recent_blockhash, opts=OPTS)
66 changes: 35 additions & 31 deletions stake-pool/py/stake/actions.py
Original file line number Diff line number Diff line change
@@ -1,87 +1,91 @@
from solana.publickey import PublicKey
from solana.keypair import Keypair
from solders.pubkey import Pubkey
from solders.keypair import Keypair
import solders.system_program as sys
from solana.constants import SYSTEM_PROGRAM_ID
from solana.rpc.async_api import AsyncClient
from solana.rpc.commitment import Confirmed
from solana.rpc.types import TxOpts
from solana.sysvar import SYSVAR_CLOCK_PUBKEY, SYSVAR_STAKE_HISTORY_PUBKEY
from solders.sysvar import CLOCK, STAKE_HISTORY
from solana.transaction import Transaction
import solana.system_program as sys

from stake.constants import STAKE_LEN, STAKE_PROGRAM_ID, SYSVAR_STAKE_CONFIG_ID
from stake.state import Authorized, Lockup, StakeAuthorize
import stake.instructions as st


async def create_stake(client: AsyncClient, payer: Keypair, stake: Keypair, authority: PublicKey, lamports: int):
print(f"Creating stake {stake.public_key}")
OPTS = TxOpts(skip_confirmation=False, preflight_commitment=Confirmed)


async def create_stake(client: AsyncClient, payer: Keypair, stake: Keypair, authority: Pubkey, lamports: int):
print(f"Creating stake {stake.pubkey()}")
resp = await client.get_minimum_balance_for_rent_exemption(STAKE_LEN)
txn = Transaction()
txn = Transaction(fee_payer=payer.pubkey())
txn.add(
sys.create_account(
sys.CreateAccountParams(
from_pubkey=payer.public_key,
new_account_pubkey=stake.public_key,
lamports=resp['result'] + lamports,
from_pubkey=payer.pubkey(),
to_pubkey=stake.pubkey(),
lamports=resp.value + lamports,
space=STAKE_LEN,
program_id=STAKE_PROGRAM_ID,
owner=STAKE_PROGRAM_ID,
)
)
)
txn.add(
st.initialize(
st.InitializeParams(
stake=stake.public_key,
stake=stake.pubkey(),
authorized=Authorized(
staker=authority,
withdrawer=authority,
),
lockup=Lockup(
unix_timestamp=0,
epoch=0,
custodian=sys.SYS_PROGRAM_ID,
custodian=SYSTEM_PROGRAM_ID,
)
)
)
)
await client.send_transaction(
txn, payer, stake, opts=TxOpts(skip_confirmation=False, preflight_commitment=Confirmed))
recent_blockhash = (await client.get_latest_blockhash()).value.blockhash
await client.send_transaction(txn, payer, stake, recent_blockhash=recent_blockhash, opts=OPTS)


async def delegate_stake(client: AsyncClient, payer: Keypair, staker: Keypair, stake: PublicKey, vote: PublicKey):
txn = Transaction()
async def delegate_stake(client: AsyncClient, payer: Keypair, staker: Keypair, stake: Pubkey, vote: Pubkey):
txn = Transaction(fee_payer=payer.pubkey())
txn.add(
st.delegate_stake(
st.DelegateStakeParams(
stake=stake,
vote=vote,
clock_sysvar=SYSVAR_CLOCK_PUBKEY,
stake_history_sysvar=SYSVAR_STAKE_HISTORY_PUBKEY,
clock_sysvar=CLOCK,
stake_history_sysvar=STAKE_HISTORY,
stake_config_id=SYSVAR_STAKE_CONFIG_ID,
staker=staker.public_key,
staker=staker.pubkey(),
)
)
)
signers = [payer, staker] if payer != staker else [payer]
await client.send_transaction(
txn, *signers, opts=TxOpts(skip_confirmation=False, preflight_commitment=Confirmed))
signers = [payer, staker] if payer.pubkey() != staker.pubkey() else [payer]
recent_blockhash = (await client.get_latest_blockhash()).value.blockhash
await client.send_transaction(txn, *signers, recent_blockhash=recent_blockhash, opts=OPTS)


async def authorize(
client: AsyncClient, payer: Keypair, authority: Keypair, stake: PublicKey,
new_authority: PublicKey, stake_authorize: StakeAuthorize
client: AsyncClient, payer: Keypair, authority: Keypair, stake: Pubkey,
new_authority: Pubkey, stake_authorize: StakeAuthorize
):
txn = Transaction()
txn = Transaction(fee_payer=payer.pubkey())
txn.add(
st.authorize(
st.AuthorizeParams(
stake=stake,
clock_sysvar=SYSVAR_CLOCK_PUBKEY,
authority=authority.public_key,
clock_sysvar=CLOCK,
authority=authority.pubkey(),
new_authority=new_authority,
stake_authorize=stake_authorize,
)
)
)
signers = [payer, authority] if payer != authority else [payer]
await client.send_transaction(
txn, *signers, opts=TxOpts(skip_confirmation=False, preflight_commitment=Confirmed))
signers = [payer, authority] if payer.pubkey() != authority.pubkey() else [payer]
recent_blockhash = (await client.get_latest_blockhash()).value.blockhash
await client.send_transaction(txn, *signers, recent_blockhash=recent_blockhash, opts=OPTS)
6 changes: 3 additions & 3 deletions stake-pool/py/stake/constants.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
"""Stake Program Constants."""

from solana.publickey import PublicKey
from solders.pubkey import Pubkey

STAKE_PROGRAM_ID: PublicKey = PublicKey("Stake11111111111111111111111111111111111111")
STAKE_PROGRAM_ID = Pubkey.from_string("Stake11111111111111111111111111111111111111")
"""Public key that identifies the Stake program."""

SYSVAR_STAKE_CONFIG_ID: PublicKey = PublicKey("StakeConfig11111111111111111111111111111111")
SYSVAR_STAKE_CONFIG_ID = Pubkey.from_string("StakeConfig11111111111111111111111111111111")
"""Public key that identifies the Stake config sysvar."""

STAKE_LEN: int = 200
Expand Down
Loading

0 comments on commit 5f0190c

Please sign in to comment.