Skip to content

Commit

Permalink
Write final test testing BpfUpgradableProgram
Browse files Browse the repository at this point in the history
and its Upgradable not Upgradeable
  • Loading branch information
ml-james committed Sep 18, 2024
1 parent 473fc5a commit 82c4c25
Show file tree
Hide file tree
Showing 17 changed files with 391 additions and 84 deletions.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package com.lmax.solana4j.programs;

import com.lmax.solana4j.parameterization.ParameterizedMessageEncodingTest;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.parallel.Execution;
import org.junit.jupiter.api.parallel.ExecutionMode;

@Execution(ExecutionMode.SAME_THREAD)
public class BpfLoaderUpgradeableProgramIntegrationTest extends IntegrationTestBase
{
@BeforeEach
void beforeEachTest()
{
solana.createKeyPair("payer");
solana.airdrop("payer", "0.01");
}

@ParameterizedMessageEncodingTest
void shouldSetUpgradeAuthority(final String messageEncoding)
{
solana.setMessageEncoding(messageEncoding);

solana.createKeyPair("newAuthority");

// see solana-run.sh for where these values come from
solana.upgradeAuthority("<CxmAHzszTVSWmtBnCXda7eUTemd8DGyax88yYk54A2PT>", "<13jT1jL8jpTFzFcZATPq9W4gmRB5uZbEYjXxJJbugB1d>");

solana.setUpgradeAuthority(
"<CxmAHzszTVSWmtBnCXda7eUTemd8DGyax88yYk54A2PT>",
"<13jT1jL8jpTFzFcZATPq9W4gmRB5uZbEYjXxJJbugB1d>",
"<Ed4gbZARwspMoyVRPU3GvCCARFtRSPSw2TqtHvH6vvj8>",
"newAuthority",
"payer");

solana.upgradeAuthority("<CxmAHzszTVSWmtBnCXda7eUTemd8DGyax88yYk54A2PT>", "newAuthority");
}

@AfterEach
void afterEachTest()
{
solana.setUpgradeAuthority(
"<CxmAHzszTVSWmtBnCXda7eUTemd8DGyax88yYk54A2PT>",
"newAuthority",
"newAuthority",
"<13jT1jL8jpTFzFcZATPq9W4gmRB5uZbEYjXxJJbugB1d>",
"payer");
}
}
2 changes: 2 additions & 0 deletions src/integration-test/resources/solana-run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ while true; do
done
set -e

# bpf_program: CxmAHzszTVSWmtBnCXda7eUTemd8DGyax88yYk54A2PT (public key) 5A7mHgUWzqpb9sbyHMzn7dLvdfHGswk5PHxfUu2PTEKR (private key)
# upgrade_authority: 13jT1jL8jpTFzFcZATPq9W4gmRB5uZbEYjXxJJbugB1d (public key) Ed4gbZARwspMoyVRPU3GvCCARFtRSPSw2TqtHvH6vvj8 (private key)
solana program deploy /lmax_multisig.so --program-id /bpf_program.json --upgrade-authority /upgrade_authority.json
#solana program show CxmAHzszTVSWmtBnCXda7eUTemd8DGyax88yYk54A2PT

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package com.lmax.solana4j.programs;

import com.lmax.solana4j.Solana;
import com.lmax.solana4j.api.ProgramDerivedAddress;
import com.lmax.solana4j.api.PublicKey;
import com.lmax.solana4j.api.TransactionBuilder;
import com.lmax.solana4j.encoding.SolanaEncoding;
import org.bitcoinj.core.Base58;

import java.nio.ByteOrder;
import java.util.List;
import java.util.Optional;

/**
* Represents a program for interacting with the BPF Loader Upgradeable program on the Solana blockchain.
* This program allows for managing upgradeable programs, such as setting a new upgrade authority.
*/
public final class BpfLoaderUpgradeableProgram
{
/**
* The Program ID for the BPF Loader Upgradeable program.
*/
private static final byte[] PROGRAM_ID = Base58.decode("BPFLoaderUpgradeab1e11111111111111111111111");

/**
* The public key associated with the BPF Loader Upgradeable program.
*/
public static final PublicKey PROGRAM_ACCOUNT = Solana.account(PROGRAM_ID);

/**
* The instruction code for setting a new upgrade authority.
*/
private static final int SET_AUTHORITY_INSTRUCTION = 4;

/**
* The transaction builder used to construct and manage Solana transactions.
*/
private final TransactionBuilder tb;

/**
* Factory method to create an instance of {@code BpfLoaderUpgradeableProgram} with a specified transaction builder.
*
* @param tb The transaction builder to use for constructing transactions.
* @return A new instance of {@code BpfLoaderUpgradeableProgram}.
*/
public static BpfLoaderUpgradeableProgram factory(final TransactionBuilder tb)
{
return new BpfLoaderUpgradeableProgram(tb);
}

/**
* Constructs a new instance of {@code BpfLoaderUpgradeableProgram} with the specified transaction builder.
*
* @param tb The transaction builder to use for constructing transactions.
*/
BpfLoaderUpgradeableProgram(final TransactionBuilder tb)
{
this.tb = tb;
}

/**
* Sets the upgrade authority for a given program address.
*
* @param programAddress The public key of the program whose upgrade authority is being changed.
* @param currentUpgradeAuthorityAddress The current upgrade authority's public key.
* @param maybeNewUpgradeAuthorityAddress An optional public key for the new upgrade authority. If not present, no new upgrade authority is set.
* @return The current instance of {@code BpfLoaderUpgradeableProgram} to allow method chaining.
*/
public BpfLoaderUpgradeableProgram setUpgradeAuthority(
final PublicKey programAddress,
final PublicKey currentUpgradeAuthorityAddress,
final Optional<PublicKey> maybeNewUpgradeAuthorityAddress)
{
tb.append(ib ->
{
ib.program(PROGRAM_ACCOUNT)
.account(deriveAddress(programAddress).address(), false, true)
.account(currentUpgradeAuthorityAddress, true, false);
maybeNewUpgradeAuthorityAddress.ifPresent(newUpgradeAuthorityAddress -> ib.account(newUpgradeAuthorityAddress, false, false));
ib.data(4, bb -> bb.order(ByteOrder.LITTLE_ENDIAN).putInt(SET_AUTHORITY_INSTRUCTION));
});

return this;
}

/**
* Derives a program-derived address for the specified program public key.
*
* @param program The public key of the program.
* @return The derived program address.
*/
public static ProgramDerivedAddress deriveAddress(final PublicKey program)
{
return SolanaEncoding.deriveProgramAddress(List.of(program.bytes()), PROGRAM_ACCOUNT);
}
}
22 changes: 22 additions & 0 deletions src/test-support/java/com/lmax/solana4j/SolanaDriver.java
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,28 @@ public String setComputeUnits(final int computeUnitLimit, final long computeUnit
return solanaApi.sendTransaction(transactionBlob);
}

public String setUpgradeAuthority(
final PublicKey program,
final PublicKey oldUpgradeAuthority,
final PublicKey newUpgradeAuthority,
final PublicKey payer,
final List<TestKeyPair> signers,
final List<AddressLookupTable> addressLookupTables)
{
final Blockhash blockhash = solanaApi.getRecentBlockHash();

final String transactionBlob = getTransactionFactory().setUpgradeAuthority(
program,
oldUpgradeAuthority,
newUpgradeAuthority,
Solana.blockhash(blockhash.getBytes()),
payer,
signers,
addressLookupTables);

return solanaApi.sendTransaction(transactionBlob);
}

public void setMessageEncoding(final String messageEncoding)
{
if (messageEncoding.equals("V0"))
Expand Down
63 changes: 61 additions & 2 deletions src/test-support/java/com/lmax/solana4j/SolanaNodeDsl.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,17 @@
import com.lmax.solana4j.domain.TokenProgram;
import com.lmax.solana4j.encoding.SolanaEncoding;
import com.lmax.solana4j.programs.AddressLookupTableProgram;
import com.lmax.solana4j.programs.BpfLoaderUpgradeableProgram;
import com.lmax.solana4j.programs.SystemProgram;
import com.lmax.solana4j.solanaclient.api.AccountInfo;
import com.lmax.solana4j.solanaclient.api.TransactionData;
import com.lmax.solana4j.solanaclient.jsonrpc.SolanaClient;
import com.lmax.solana4j.util.TestKeyPairGenerator;
import org.bitcoinj.core.Base58;
import org.bouncycastle.util.encoders.Base64;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
Expand Down Expand Up @@ -76,10 +79,10 @@ public void airdrop(final String... args)
new RequiredArg("address"),
new RequiredArg("amountSol"));

final TestPublicKey address = testContext.data(TestDataType.TEST_PUBLIC_KEY).lookup(params.value("address"));
final String address = testContext.lookupOrLiteral(params.value("address"), TestDataType.TEST_PUBLIC_KEY);
final Sol sol = new Sol(params.valueAsBigDecimal("amountSol"));

final String transactionSignature = solanaDriver.requestAirdrop(address, sol.lamports());
final String transactionSignature = solanaDriver.requestAirdrop(new TestPublicKey(Base58.decode(address)), sol.lamports());

final long recentBlockHeight = solanaDriver.getBlockHeight();
waitForBlockHeight(recentBlockHeight + 1);
Expand Down Expand Up @@ -600,6 +603,62 @@ public void setComputeUnits(final String... args)
Waiter.waitFor(isNotNull(() -> solanaDriver.getTransactionResponse(transactionSignature).getTransaction()));
}

public void setUpgradeAuthority(final String... args)
{
final DslParams params = DslParams.create(
args,
new RequiredArg("program"),
new RequiredArg("oldUpgradeAuthorityPublicKey"),
new RequiredArg("oldUpgradeAuthorityPrivateKey"),
new RequiredArg("newUpgradeAuthority"),
new RequiredArg("payer"),
new OptionalArg("addressLookupTables")
);

final String program = testContext.lookupOrLiteral(params.value("program"), TestDataType.TEST_PUBLIC_KEY);
final String oldUpgradeAuthorityPublicKey = testContext.lookupOrLiteral(params.value("oldUpgradeAuthorityPublicKey"), TestDataType.TEST_PUBLIC_KEY);
final String oldUpgradeAuthorityPrivateKey = testContext.lookupOrLiteral(params.value("oldUpgradeAuthorityPrivateKey"), TestDataType.TEST_KEY_PAIR);

final String newUpgradeAuthority = testContext.lookupOrLiteral(params.value("newUpgradeAuthority"), TestDataType.TEST_PUBLIC_KEY);

final TestKeyPair payer = testContext.data(TestDataType.TEST_KEY_PAIR).lookup(params.value("payer"));

final List<AddressLookupTable> addressLookupTables = params.valuesAsList("addressLookupTables").stream()
.map(testContext.data(TestDataType.ADDRESS_LOOKUP_TABLE)::lookup)
.filter(Objects::nonNull)
.toList();

final String transactionSignature = solanaDriver.setUpgradeAuthority(
Solana.account(Base58.decode(program)),
Solana.account(Base58.decode(oldUpgradeAuthorityPublicKey)),
Solana.account(Base58.decode(newUpgradeAuthority)),
payer.getSolana4jPublicKey(),
List.of(payer, new TestKeyPair(oldUpgradeAuthorityPublicKey, oldUpgradeAuthorityPrivateKey)),
addressLookupTables);

Waiter.waitFor(isNotNull(() -> solanaDriver.getTransactionResponse(transactionSignature).getTransaction()));
}

public void upgradeAuthority(final String... args)
{
final DslParams params = DslParams.create(
args,
new RequiredArg("address"),
new OptionalArg("upgradeAuthority")
);

final String address = testContext.lookupOrLiteral(params.value("address"), TestDataType.TEST_PUBLIC_KEY);
final String upgradeAuthority = testContext.lookupOrLiteral(params.value("upgradeAuthority"), TestDataType.TEST_PUBLIC_KEY);

final ProgramDerivedAddress programDataAddress = BpfLoaderUpgradeableProgram.deriveAddress(Solana.account(Base58.decode(address)));

final AccountInfo accountInfo = Waiter.waitFor(isNotNull(() -> solanaDriver.getAccountInfo(new TestPublicKey(programDataAddress.address().bytes()))));

final byte[] accountInfoBytes = Base64.decode(accountInfo.getData().get(0));

assertThat(Base58.encode(Arrays.copyOfRange(accountInfoBytes, 13, 45))).isEqualTo(upgradeAuthority);
}

private void waitForSlot(final long slot)
{
Waiter.waitFor(isTrue(() -> solanaDriver.getSlot() > slot));
Expand Down
17 changes: 17 additions & 0 deletions src/test-support/java/com/lmax/solana4j/TestContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,21 @@ public final <T> TestDataStore<T> data(final TestDataType<T> key)
{
return (TestDataStore<T>) data.computeIfAbsent(key, unused -> new TestDataStore<>());
}

@SuppressWarnings("unchecked")
public <T> String lookupOrLiteral(final String alias, final TestDataType<T> testDataType)
{
if (alias.startsWith("<") && alias.endsWith(">"))
{
return alias.substring(1, alias.length() - 1);
}
else
{
final TestDataStore<?> testDataStore = data.get(testDataType);

final T lookup = (T) testDataStore.lookup(alias);

return testDataType.getTransform().apply(lookup);
}
}
}
Loading

0 comments on commit 82c4c25

Please sign in to comment.