Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
71 changes: 71 additions & 0 deletions pxsol/program.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,77 @@ def extend_program_checked(cls, addi: int) -> bytearray:
return pxsol.bincode.Enum.encode(9) + pxsol.bincode.U32.encode(addi)


class LoaderV4:
# The v4 built-in loader program.
# See: https://github.com/anza-xyz/solana-sdk/tree/master/loader-v4-interface

pubkey = pxsol.core.PubKey.base58_decode('LoaderV411111111111111111111111111111111111')

# Loader v4 account states size.
size_program_data = 8 + 32 + 8

@classmethod
def write(cls, offset: int, data: bytearray) -> bytearray:
# Write elf data into an undeployed program account. Account references:
# 0. -w the program account to write to.
# 1. sr the authority of the program.
return pxsol.bincode.Enum.encode(0) + pxsol.bincode.Struct([
pxsol.bincode.U32,
pxsol.bincode.Slice(pxsol.bincode.U8),
]).encode([offset, data])

@classmethod
def copy(cls, dst_offset: int, src_offset: int, length: int) -> bytearray:
# Copy elf data into an undeployed program account. Account references:
# 0. -w the program account to write to.
# 1. sr the authority of the program.
# 2. -r the program account to copy from.
return pxsol.bincode.Enum.encode(1) + pxsol.bincode.Struct([
pxsol.bincode.U32,
pxsol.bincode.U32,
pxsol.bincode.U32,
]).encode([dst_offset, src_offset, length])

@classmethod
def set_program_length(cls, size: int) -> bytearray:
# Changes the size of an undeployed program account. Account references:
# 0. -w the program account to change the size of.
# 1. sr the authority of the program.
# 2. -w optional, the recipient account.
return pxsol.bincode.Enum.encode(2) + pxsol.bincode.U32.encode(size)

@classmethod
def deploy(cls) -> bytearray:
# Deploy a program account. Account references:
# 0. -w the program account to deploy.
# 1. sr the authority of the program.
# 2. -w optional, an undeployed source program account to take data and lamports from.
return pxsol.bincode.Enum.encode(3)

@classmethod
def retract(cls) -> bytearray:
# Undo the deployment of a program account. Account references:
# 0. -w the program account to retract.
# 1. sr the authority of the program.
return pxsol.bincode.Enum.encode(4)

@classmethod
def transfer_authority(cls) -> bytearray:
# Transfers the authority over a program account. Account references:
# 0. -w the program account to change the authority of.
# 1. sr the current authority of the program.
# 2. sr the new authority of the program.
return pxsol.bincode.Enum.encode(5)

@classmethod
def finalize(cls) -> bytearray:
# Finalizes the program account, rendering it immutable. Account references:
# 0. -w the program account to change the authority of.
# 1. sr the current authority of the program.
# 2. -r the next version of the program (can be itself).
return pxsol.bincode.Enum.encode(6)


LoaderUpgradeable = LoaderV3
Loader = LoaderV3

Expand Down
180 changes: 169 additions & 11 deletions pxsol/wallet.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,13 @@
import typing


class Wallet:
# A built-in solana wallet that can be used to perform most on-chain operations.
class WalletLoaderV3:
# A built-in solana wallet that can be used to perform program loader v3 operations.

def __init__(self, prikey: pxsol.core.PriKey) -> None:
self.prikey = prikey
self.pubkey = prikey.pubkey()

def __repr__(self) -> str:
return json.dumps(self.json())

def json(self) -> typing.Dict:
return {
'prikey': self.prikey.base58(),
'pubkey': self.pubkey.base58(),
}

def program_buffer(self, bincode: bytearray) -> pxsol.core.PubKey:
# Writes a program into a buffer account. The buffer account is randomly generated, and its public key serves
# as the function's return value.
Expand Down Expand Up @@ -156,6 +147,173 @@ def program_update(self, program: pxsol.core.PubKey, bincode: bytearray) -> None
txid = pxsol.rpc.send_transaction(base64.b64encode(tx.serialize()).decode(), {})
pxsol.rpc.wait([txid])


class WalletLoaderV4:
# A built-in solana wallet that can be used to perform program loader v4 operations.

def __init__(self, prikey: pxsol.core.PriKey) -> None:
self.prikey = prikey
self.pubkey = prikey.pubkey()

def program_closed(self, program: pxsol.core.PubKey) -> None:
# Close a program. The sol allocated to the on-chain program can be fully recovered by performing this action.
r0 = pxsol.core.Requisition(pxsol.program.LoaderV4.pubkey, [], bytearray())
r0.account.append(pxsol.core.AccountMeta(program, 1))
r0.account.append(pxsol.core.AccountMeta(self.pubkey, 2))
r0.data = pxsol.program.LoaderV4.retract()
r1 = pxsol.core.Requisition(pxsol.program.LoaderV4.pubkey, [], bytearray())
r1.account.append(pxsol.core.AccountMeta(program, 1))
r1.account.append(pxsol.core.AccountMeta(self.pubkey, 2))
r1.account.append(pxsol.core.AccountMeta(self.pubkey, 1))
r1.data = pxsol.program.LoaderV4.set_program_length(0)
tx = pxsol.core.Transaction.requisition_decode(self.pubkey, [r0, r1])
tx.message.recent_blockhash = pxsol.base58.decode(pxsol.rpc.get_latest_blockhash({})['blockhash'])
tx.sign([self.prikey])
txid = pxsol.rpc.send_transaction(base64.b64encode(tx.serialize()).decode(), {})
pxsol.rpc.wait([txid])

def program_deploy(self, bincode: bytearray) -> pxsol.core.PubKey:
# Deploying a program on solana, returns the program's public key.
tempory_prikey = pxsol.core.PriKey.random()
program = tempory_prikey.pubkey()
pxsol.log.debugln(f'pxsol: program prikey={tempory_prikey}')
pxsol.log.debugln(f'pxsol: program pubkey={program}')
program_real_size = pxsol.program.LoaderV4.size_program_data + len(bincode)
# Create a program account and set its length.
r0 = pxsol.core.Requisition(pxsol.program.System.pubkey, [], bytearray())
r0.account.append(pxsol.core.AccountMeta(self.pubkey, 3))
r0.account.append(pxsol.core.AccountMeta(program, 3))
r0.data = pxsol.program.System.create_account(
pxsol.rpc.get_minimum_balance_for_rent_exemption(program_real_size, {}),
0,
pxsol.program.LoaderV4.pubkey,
)
r1 = pxsol.core.Requisition(pxsol.program.LoaderV4.pubkey, [], bytearray())
r1.account.append(pxsol.core.AccountMeta(program, 1))
r1.account.append(pxsol.core.AccountMeta(self.pubkey, 2))
r1.account.append(pxsol.core.AccountMeta(self.pubkey, 1))
r1.data = pxsol.program.LoaderV4.set_program_length(len(bincode))
tx = pxsol.core.Transaction.requisition_decode(self.pubkey, [r0, r1])
tx.message.recent_blockhash = pxsol.base58.decode(pxsol.rpc.get_latest_blockhash({})['blockhash'])
tx.sign([self.prikey, tempory_prikey])
txid = pxsol.rpc.send_transaction(base64.b64encode(tx.serialize()).decode(), {})
pxsol.rpc.wait([txid])
# Breaks up the program byte-code into ~1KB chunks and sends transactions to write each chunk with the write
# buffer instruction.
size = 1012
hall = []
for i in range(0, len(bincode), size):
elem = bincode[i:i+size]
rq = pxsol.core.Requisition(pxsol.program.LoaderV4.pubkey, [], bytearray())
rq.account.append(pxsol.core.AccountMeta(program, 1))
rq.account.append(pxsol.core.AccountMeta(self.pubkey, 2))
rq.data = pxsol.program.LoaderV4.write(i, elem)
tx = pxsol.core.Transaction.requisition_decode(self.pubkey, [rq])
tx.message.recent_blockhash = pxsol.base58.decode(pxsol.rpc.get_latest_blockhash({})['blockhash'])
tx.sign([self.prikey])
assert len(tx.serialize()) <= 1232
txid = pxsol.rpc.send_transaction(base64.b64encode(tx.serialize()).decode(), {})
hall.append(txid)
pxsol.rpc.wait(hall)
# Deploy.
rq = pxsol.core.Requisition(pxsol.program.LoaderV4.pubkey, [], bytearray())
rq.account.append(pxsol.core.AccountMeta(program, 1))
rq.account.append(pxsol.core.AccountMeta(self.pubkey, 2))
rq.data = pxsol.program.LoaderV4.deploy()
tx = pxsol.core.Transaction.requisition_decode(self.pubkey, [rq])
tx.message.recent_blockhash = pxsol.base58.decode(pxsol.rpc.get_latest_blockhash({})['blockhash'])
tx.sign([self.prikey])
txid = pxsol.rpc.send_transaction(base64.b64encode(tx.serialize()).decode(), {})
pxsol.rpc.wait([txid])
return program

def program_update(self, program: pxsol.core.PubKey, bincode: bytearray) -> None:
# Updating an existing solana program by new program data and the same program id.
program_info = pxsol.rpc.get_account_info(program.base58(), {})
assert len(base64.b64decode(program_info['data'][0])) == program_info['space']
program_real_size = program_info['space']
program_want_size = pxsol.program.LoaderV4.size_program_data + len(bincode)
rs = []
rq = pxsol.core.Requisition(pxsol.program.LoaderV4.pubkey, [], bytearray())
rq.account.append(pxsol.core.AccountMeta(program, 1))
rq.account.append(pxsol.core.AccountMeta(self.pubkey, 2))
rq.data = pxsol.program.LoaderV4.retract()
rs.append(rq)
if program_want_size > program_real_size:
addi = pxsol.rpc.get_minimum_balance_for_rent_exemption(program_want_size, {}) - program_info['lamports']
rq = pxsol.core.Requisition(pxsol.program.System.pubkey, [], bytearray())
rq.account.append(pxsol.core.AccountMeta(self.pubkey, 3))
rq.account.append(pxsol.core.AccountMeta(program, 1))
rq.data = pxsol.program.System.transfer(addi)
rs.append(rq)
rq = pxsol.core.Requisition(pxsol.program.LoaderV4.pubkey, [], bytearray())
rq.account.append(pxsol.core.AccountMeta(program, 1))
rq.account.append(pxsol.core.AccountMeta(self.pubkey, 2))
rq.account.append(pxsol.core.AccountMeta(self.pubkey, 1))
rq.data = pxsol.program.LoaderV4.set_program_length(len(bincode))
rs.append(rq)
tx = pxsol.core.Transaction.requisition_decode(self.pubkey, rs)
tx.message.recent_blockhash = pxsol.base58.decode(pxsol.rpc.get_latest_blockhash({})['blockhash'])
tx.sign([self.prikey])
txid = pxsol.rpc.send_transaction(base64.b64encode(tx.serialize()).decode(), {})
pxsol.rpc.wait([txid])
# Breaks up the program byte-code into ~1KB chunks and sends transactions to write each chunk with the write
# buffer instruction.
size = 1012
hall = []
for i in range(0, len(bincode), size):
elem = bincode[i:i+size]
rq = pxsol.core.Requisition(pxsol.program.LoaderV4.pubkey, [], bytearray())
rq.account.append(pxsol.core.AccountMeta(program, 1))
rq.account.append(pxsol.core.AccountMeta(self.pubkey, 2))
rq.data = pxsol.program.LoaderV4.write(i, elem)
tx = pxsol.core.Transaction.requisition_decode(self.pubkey, [rq])
tx.message.recent_blockhash = pxsol.base58.decode(pxsol.rpc.get_latest_blockhash({})['blockhash'])
tx.sign([self.prikey])
assert len(tx.serialize()) <= 1232
txid = pxsol.rpc.send_transaction(base64.b64encode(tx.serialize()).decode(), {})
hall.append(txid)
pxsol.rpc.wait(hall)
# Deploy
rq = pxsol.core.Requisition(pxsol.program.LoaderV4.pubkey, [], bytearray())
rq.account.append(pxsol.core.AccountMeta(program, 1))
rq.account.append(pxsol.core.AccountMeta(self.pubkey, 2))
rq.data = pxsol.program.LoaderV4.deploy()
tx = pxsol.core.Transaction.requisition_decode(self.pubkey, [rq])
tx.message.recent_blockhash = pxsol.base58.decode(pxsol.rpc.get_latest_blockhash({})['blockhash'])
tx.sign([self.prikey])
txid = pxsol.rpc.send_transaction(base64.b64encode(tx.serialize()).decode(), {})
pxsol.rpc.wait([txid])


class Wallet:
# A built-in solana wallet that can be used to perform most on-chain operations.

def __init__(self, prikey: pxsol.core.PriKey) -> None:
self.prikey = prikey
self.pubkey = prikey.pubkey()

def __repr__(self) -> str:
return json.dumps(self.json())

def json(self) -> typing.Dict:
return {
'prikey': self.prikey.base58(),
'pubkey': self.pubkey.base58(),
}

def program_closed(self, program: pxsol.core.PubKey) -> None:
# Close a program. The sol allocated to the on-chain program can be fully recovered by performing this action.
return WalletLoaderV3(self.prikey).program_closed(program)

def program_deploy(self, bincode: bytearray) -> pxsol.core.PubKey:
# Deploying a program on solana, returns the program's public key.
return WalletLoaderV3(self.prikey).program_deploy(bincode)

def program_update(self, program: pxsol.core.PubKey, bincode: bytearray) -> None:
# Updating an existing solana program by new program data and the same program id.
return WalletLoaderV3(self.prikey).program_update(program, bincode)

def sol_balance(self) -> int:
# Returns the lamport balance of the account.
return pxsol.rpc.get_balance(self.pubkey.base58(), {})
Expand Down
53 changes: 40 additions & 13 deletions test/test_wallet.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,54 @@
import typing


def test_program():
def call_logs(program_pubkey: pxsol.core.PubKey) -> typing.List[str]:
user = pxsol.wallet.Wallet(pxsol.core.PriKey.int_decode(1))
rq = pxsol.core.Requisition(program_pubkey, [], bytearray())
tx = pxsol.core.Transaction.requisition_decode(user.pubkey, [rq])
tx.message.recent_blockhash = pxsol.base58.decode(pxsol.rpc.get_latest_blockhash({})['blockhash'])
tx.sign([user.prikey])
txid = pxsol.rpc.send_transaction(base64.b64encode(tx.serialize()).decode(), {})
pxsol.rpc.wait([txid])
r = pxsol.rpc.get_transaction(txid, {})
return r['meta']['logMessages']


def test_loader_v3():
user = pxsol.wallet.WalletLoaderV3(pxsol.core.PriKey.int_decode(1))
program_hello_solana = bytearray(pathlib.Path('res/hello_solana_program.so').read_bytes())
program_hello_update = bytearray(pathlib.Path('res/hello_update_program.so').read_bytes())
program_pubkey = user.program_deploy(program_hello_solana)
assert call_logs(program_pubkey)[1] == 'Program log: Hello, Solana!'
user.program_update(program_pubkey, program_hello_update)
assert call_logs(program_pubkey)[1] == 'Program log: Hello, Solana! Hello Update!'
user.program_update(program_pubkey, program_hello_solana)
assert call_logs(program_pubkey)[1] == 'Program log: Hello, Solana!'
user.program_closed(program_pubkey)

def call(program_pubkey: pxsol.core.PubKey) -> typing.List[str]:
rq = pxsol.core.Requisition(program_pubkey, [], bytearray())
tx = pxsol.core.Transaction.requisition_decode(user.pubkey, [rq])
tx.message.recent_blockhash = pxsol.base58.decode(pxsol.rpc.get_latest_blockhash({})['blockhash'])
tx.sign([user.prikey])
txid = pxsol.rpc.send_transaction(base64.b64encode(tx.serialize()).decode(), {})
pxsol.rpc.wait([txid])
r = pxsol.rpc.get_transaction(txid, {})
return r['meta']['logMessages']

def test_loader_v4():
user = pxsol.wallet.WalletLoaderV3(pxsol.core.PriKey.int_decode(1))
program_hello_solana = bytearray(pathlib.Path('res/hello_solana_program.so').read_bytes())
program_hello_update = bytearray(pathlib.Path('res/hello_update_program.so').read_bytes())
program_pubkey = user.program_deploy(program_hello_solana)
assert call_logs(program_pubkey)[1] == 'Program log: Hello, Solana!'
user.program_update(program_pubkey, program_hello_update)
assert call_logs(program_pubkey)[1] == 'Program log: Hello, Solana! Hello Update!'
user.program_update(program_pubkey, program_hello_solana)
assert call_logs(program_pubkey)[1] == 'Program log: Hello, Solana!'
user.program_closed(program_pubkey)


def test_program():
user = pxsol.wallet.Wallet(pxsol.core.PriKey.int_decode(1))
program_hello_solana = bytearray(pathlib.Path('res/hello_solana_program.so').read_bytes())
program_hello_update = bytearray(pathlib.Path('res/hello_update_program.so').read_bytes())
program_pubkey = user.program_deploy(program_hello_solana)
assert call(program_pubkey)[1] == 'Program log: Hello, Solana!'
assert call_logs(program_pubkey)[1] == 'Program log: Hello, Solana!'
user.program_update(program_pubkey, program_hello_update)
assert call(program_pubkey)[1] == 'Program log: Hello, Solana! Hello Update!'
assert call_logs(program_pubkey)[1] == 'Program log: Hello, Solana! Hello Update!'
user.program_update(program_pubkey, program_hello_solana)
assert call(program_pubkey)[1] == 'Program log: Hello, Solana!'
assert call_logs(program_pubkey)[1] == 'Program log: Hello, Solana!'
user.program_closed(program_pubkey)


Expand Down