diff --git a/src/integration-test/java/com/lmax/solana4j/programs/BpfLoaderUpgradableProgramIntegrationTest.java b/src/integration-test/java/com/lmax/solana4j/programs/BpfLoaderUpgradableProgramIntegrationTest.java deleted file mode 100644 index 35233e2..0000000 --- a/src/integration-test/java/com/lmax/solana4j/programs/BpfLoaderUpgradableProgramIntegrationTest.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.lmax.solana4j.programs; - -import com.lmax.solana4j.parameterization.ParameterizedMessageEncodingTest; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; - -import static org.junit.jupiter.api.Assertions.fail; - -public class BpfLoaderUpgradableProgramIntegrationTest extends IntegrationTestBase -{ - @BeforeEach - void beforeEachTest() - { - solana.createKeyPair("payer"); - solana.airdrop("payer", "0.01"); - } - - @Disabled - @ParameterizedMessageEncodingTest - void shouldSetUpgradeAuthority(final String messageEncoding) - { - fail(); - } -} diff --git a/src/integration-test/java/com/lmax/solana4j/programs/BpfLoaderUpgradeableProgramIntegrationTest.java b/src/integration-test/java/com/lmax/solana4j/programs/BpfLoaderUpgradeableProgramIntegrationTest.java new file mode 100644 index 0000000..7474d80 --- /dev/null +++ b/src/integration-test/java/com/lmax/solana4j/programs/BpfLoaderUpgradeableProgramIntegrationTest.java @@ -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("", "<13jT1jL8jpTFzFcZATPq9W4gmRB5uZbEYjXxJJbugB1d>"); + + solana.setUpgradeAuthority( + "", + "<13jT1jL8jpTFzFcZATPq9W4gmRB5uZbEYjXxJJbugB1d>", + "", + "newAuthority", + "payer"); + + solana.upgradeAuthority("", "newAuthority"); + } + + @AfterEach + void afterEachTest() + { + solana.setUpgradeAuthority( + "", + "newAuthority", + "newAuthority", + "<13jT1jL8jpTFzFcZATPq9W4gmRB5uZbEYjXxJJbugB1d>", + "payer"); + } +} diff --git a/src/integration-test/resources/solana-run.sh b/src/integration-test/resources/solana-run.sh index 59f3a9a..dbb889d 100644 --- a/src/integration-test/resources/solana-run.sh +++ b/src/integration-test/resources/solana-run.sh @@ -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 diff --git a/src/main/java/com/lmax/solana4j/programs/BpfLoaderUpgradableProgram.java b/src/main/java/com/lmax/solana4j/programs/BpfLoaderUpgradableProgram.java deleted file mode 100644 index 4ec5495..0000000 --- a/src/main/java/com/lmax/solana4j/programs/BpfLoaderUpgradableProgram.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.lmax.solana4j.programs; - -import com.lmax.solana4j.Solana; -import com.lmax.solana4j.api.PublicKey; -import com.lmax.solana4j.api.TransactionBuilder; -import org.bitcoinj.core.Base58; - -import java.nio.ByteOrder; -import java.util.List; -import java.util.Optional; - -final class BpfLoaderUpgradableProgram -{ - private static final byte[] PROGRAM_ID = Base58.decode("BPFLoaderUpgradeab1e11111111111111111111111"); - public static final PublicKey PROGRAM_ACCOUNT = Solana.account(PROGRAM_ID); - - private static final int SET_AUTHORITY_INSTRUCTION = 4; - - private final TransactionBuilder tb; - - public static BpfLoaderUpgradableProgram factory(final TransactionBuilder tb) - { - return new BpfLoaderUpgradableProgram(tb); - } - - BpfLoaderUpgradableProgram(final TransactionBuilder tb) - { - this.tb = tb; - } - - public BpfLoaderUpgradableProgram setUpgradeAuthority( - final PublicKey programAddress, - final PublicKey currentAuthorityAddress, - final Optional maybeNewAuthorityAddress) - { - final PublicKey programDataAddress = Solana.programDerivedAddress(List.of(programAddress.bytes()), PROGRAM_ACCOUNT).address(); - - tb.append(ib -> - { - ib.program(PROGRAM_ACCOUNT) - .account(programDataAddress, false, true) - .account(currentAuthorityAddress, true, false); - maybeNewAuthorityAddress.ifPresent(newAuthorityAddress -> ib.account(newAuthorityAddress, false, false)); - ib.data(4, bb -> bb.order(ByteOrder.LITTLE_ENDIAN).putInt(SET_AUTHORITY_INSTRUCTION)); - }); - - return this; - } -} diff --git a/src/main/java/com/lmax/solana4j/programs/BpfLoaderUpgradeableProgram.java b/src/main/java/com/lmax/solana4j/programs/BpfLoaderUpgradeableProgram.java new file mode 100644 index 0000000..224992f --- /dev/null +++ b/src/main/java/com/lmax/solana4j/programs/BpfLoaderUpgradeableProgram.java @@ -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 currentAuthorityAddress The current authority's public key. + * @param maybeNewAuthorityAddress An optional public key for the new authority. If not present, no new authority is set. + * @return The current instance of {@code BpfLoaderUpgradeableProgram} to allow method chaining. + */ + public BpfLoaderUpgradeableProgram setUpgradeAuthority( + final PublicKey programAddress, + final PublicKey currentAuthorityAddress, + final Optional maybeNewAuthorityAddress) + { + tb.append(ib -> + { + ib.program(PROGRAM_ACCOUNT) + .account(deriveAddress(programAddress).programId(), false, true) + .account(currentAuthorityAddress, true, false); + maybeNewAuthorityAddress.ifPresent(newAuthorityAddress -> ib.account(newAuthorityAddress, 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); + } +} diff --git a/src/test-support/java/com/lmax/solana4j/SolanaDriver.java b/src/test-support/java/com/lmax/solana4j/SolanaDriver.java index cef74f6..79c4c5f 100644 --- a/src/test-support/java/com/lmax/solana4j/SolanaDriver.java +++ b/src/test-support/java/com/lmax/solana4j/SolanaDriver.java @@ -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 oldAuthority, + final PublicKey newAuthority, + final PublicKey payer, + final List signers, + final List addressLookupTables) + { + final Blockhash blockhash = solanaApi.getRecentBlockHash(); + + final String transactionBlob = getTransactionFactory().setUpgradeAuthority( + program, + oldAuthority, + newAuthority, + Solana.blockhash(blockhash.getBytes()), + payer, + signers, + addressLookupTables); + + return solanaApi.sendTransaction(transactionBlob); + } + public void setMessageEncoding(final String messageEncoding) { if (messageEncoding.equals("V0")) diff --git a/src/test-support/java/com/lmax/solana4j/SolanaNodeDsl.java b/src/test-support/java/com/lmax/solana4j/SolanaNodeDsl.java index a86480d..91d9501 100644 --- a/src/test-support/java/com/lmax/solana4j/SolanaNodeDsl.java +++ b/src/test-support/java/com/lmax/solana4j/SolanaNodeDsl.java @@ -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; @@ -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); @@ -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("oldAuthorityPublicKey"), + new RequiredArg("oldAuthorityPrivateKey"), + new RequiredArg("newAuthority"), + new RequiredArg("payer"), + new OptionalArg("addressLookupTables") + ); + + final String program = testContext.lookupOrLiteral(params.value("program"), TestDataType.TEST_PUBLIC_KEY); + final String oldAuthorityPublicKey = testContext.lookupOrLiteral(params.value("oldAuthorityPublicKey"), TestDataType.TEST_PUBLIC_KEY); + final String oldAuthorityPrivateKey = testContext.lookupOrLiteral(params.value("oldAuthorityPrivateKey"), TestDataType.TEST_KEY_PAIR); + + final String newAuthority = testContext.lookupOrLiteral(params.value("newAuthority"), TestDataType.TEST_PUBLIC_KEY); + + final TestKeyPair payer = testContext.data(TestDataType.TEST_KEY_PAIR).lookup(params.value("payer")); + + final List 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(oldAuthorityPublicKey)), + Solana.account(Base58.decode(newAuthority)), + payer.getSolana4jPublicKey(), + List.of(payer, new TestKeyPair(oldAuthorityPublicKey, oldAuthorityPrivateKey)), + 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("authority") + ); + + final String address = testContext.lookupOrLiteral(params.value("address"), TestDataType.TEST_PUBLIC_KEY); + final String authority = testContext.lookupOrLiteral(params.value("authority"), 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(authority); + } + private void waitForSlot(final long slot) { Waiter.waitFor(isTrue(() -> solanaDriver.getSlot() > slot)); diff --git a/src/test-support/java/com/lmax/solana4j/TestContext.java b/src/test-support/java/com/lmax/solana4j/TestContext.java index e61b132..f3e291c 100644 --- a/src/test-support/java/com/lmax/solana4j/TestContext.java +++ b/src/test-support/java/com/lmax/solana4j/TestContext.java @@ -1,5 +1,7 @@ package com.lmax.solana4j; +import com.lmax.solana4j.domain.TestPublicKey; + import java.util.HashMap; import java.util.Map; @@ -12,4 +14,21 @@ public final TestDataStore data(final TestDataType key) { return (TestDataStore) data.computeIfAbsent(key, unused -> new TestDataStore<>()); } + + @SuppressWarnings("unchecked") + public String lookupOrLiteral(final String alias, final TestDataType 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); + } + } } diff --git a/src/test-support/java/com/lmax/solana4j/TestDataType.java b/src/test-support/java/com/lmax/solana4j/TestDataType.java index c5de0bd..b6689d9 100644 --- a/src/test-support/java/com/lmax/solana4j/TestDataType.java +++ b/src/test-support/java/com/lmax/solana4j/TestDataType.java @@ -3,10 +3,25 @@ import com.lmax.solana4j.api.AddressLookupTable; import com.lmax.solana4j.domain.TestKeyPair; import com.lmax.solana4j.domain.TestPublicKey; +import org.bitcoinj.core.Base58; + +import java.util.function.Function; public final class TestDataType { - public static final TestDataType TEST_PUBLIC_KEY = new TestDataType<>(); - public static final TestDataType TEST_KEY_PAIR = new TestDataType<>(); - public static final TestDataType ADDRESS_LOOKUP_TABLE = new TestDataType<>(); + public static final TestDataType TEST_PUBLIC_KEY = new TestDataType<>(TestPublicKey::getPublicKeyBase58); + public static final TestDataType TEST_KEY_PAIR = new TestDataType<>(x -> Base58.encode(x.getPrivateKeyBytes())); + public static final TestDataType ADDRESS_LOOKUP_TABLE = new TestDataType<>(x -> x.getLookupTableAddress().base58()); + + private Function transform; + + public TestDataType(Function transform) + { + this.transform = transform; + } + + public Function getTransform() + { + return transform; + } } diff --git a/src/test-support/java/com/lmax/solana4j/domain/TestKeyPair.java b/src/test-support/java/com/lmax/solana4j/domain/TestKeyPair.java index dc90581..bb1b988 100644 --- a/src/test-support/java/com/lmax/solana4j/domain/TestKeyPair.java +++ b/src/test-support/java/com/lmax/solana4j/domain/TestKeyPair.java @@ -9,10 +9,16 @@ public class TestKeyPair private final byte[] publicKey; private final byte[] privateKey; - public TestKeyPair(final byte[] publicKey, final byte[] privateKey) + public TestKeyPair(final byte[] publicKeyBytes, final byte[] privateKeyBytes) { - this.publicKey = publicKey; - this.privateKey = privateKey; + this.publicKey = publicKeyBytes; + this.privateKey = privateKeyBytes; + } + + public TestKeyPair(final String publicKey, final String privateKey) + { + this.publicKey = Base58.decode(publicKey); + this.privateKey = Base58.decode(privateKey); } public String getPublicKeyBase58() diff --git a/src/test-support/java/com/lmax/solana4j/domain/TestPublicKey.java b/src/test-support/java/com/lmax/solana4j/domain/TestPublicKey.java index 602248e..806015a 100644 --- a/src/test-support/java/com/lmax/solana4j/domain/TestPublicKey.java +++ b/src/test-support/java/com/lmax/solana4j/domain/TestPublicKey.java @@ -7,9 +7,9 @@ public class TestPublicKey { private final byte[] publicKey; - public TestPublicKey(final byte[] publicKey) + public TestPublicKey(final byte[] publicKeyBytes) { - this.publicKey = publicKey; + this.publicKey = publicKeyBytes; } public String getPublicKeyBase58() diff --git a/src/test-support/java/com/lmax/solana4j/transactionblobs/LegacyTransactionBlobFactory.java b/src/test-support/java/com/lmax/solana4j/transactionblobs/LegacyTransactionBlobFactory.java index 27ec3a1..387a1b4 100644 --- a/src/test-support/java/com/lmax/solana4j/transactionblobs/LegacyTransactionBlobFactory.java +++ b/src/test-support/java/com/lmax/solana4j/transactionblobs/LegacyTransactionBlobFactory.java @@ -14,6 +14,7 @@ import com.lmax.solana4j.domain.TokenProgramFactory; import com.lmax.solana4j.programs.AddressLookupTableProgram; import com.lmax.solana4j.programs.AssociatedTokenProgram; +import com.lmax.solana4j.programs.BpfLoaderUpgradeableProgram; import com.lmax.solana4j.programs.ComputeBudgetProgram; import com.lmax.solana4j.util.BouncyCastleSigner; import org.bitcoinj.core.Base58; @@ -399,6 +400,7 @@ public String advanceNonce( final List addressLookupTables) { final ByteBuffer buffer = ByteBuffer.allocate(Solana.MAX_MESSAGE_SIZE); + Solana.builder(buffer) .legacy() .recent(blockhash) @@ -432,6 +434,7 @@ public String createAssociatedTokenAddress( final List addressLookupTables) { final ByteBuffer buffer = ByteBuffer.allocate(Solana.MAX_MESSAGE_SIZE); + Solana.builder(buffer) .legacy() .recent(blockhash) @@ -471,6 +474,7 @@ public String setAuthority( final List addressLookupTables) { final ByteBuffer buffer = ByteBuffer.allocate(Solana.MAX_MESSAGE_SIZE); + Solana.builder(buffer) .legacy() .recent(blockhash) @@ -500,6 +504,7 @@ public String setAuthority( public String setComputeUnits(final int computeUnitLimit, final long computeUnitPrice, final Blockhash blockhash, final PublicKey payer, final List signers) { final ByteBuffer buffer = ByteBuffer.allocate(Solana.MAX_MESSAGE_SIZE); + Solana.builder(buffer) .legacy() .recent(blockhash) @@ -521,6 +526,42 @@ public String setComputeUnits(final int computeUnitLimit, final long computeUnit return base58encode(buffer); } + @Override + public String setUpgradeAuthority( + final PublicKey program, + final PublicKey oldAuthority, + final PublicKey newAuthority, + final Blockhash blockhash, + final PublicKey payer, + final List signers, + final List addressLookupTables) + { + final ByteBuffer buffer = ByteBuffer.allocate(Solana.MAX_MESSAGE_SIZE); + + Solana.builder(buffer) + .legacy() + .recent(blockhash) + .instructions(legacyTransactionBuilder -> BpfLoaderUpgradeableProgram.factory(legacyTransactionBuilder) + .setUpgradeAuthority( + program, + oldAuthority, + Optional.of(newAuthority)) + ) + .payer(payer) + .seal() + .unsigned() + .build(); + + final SignedMessageBuilder signedMessageBuilder = Solana.forSigning(buffer); + for (final TestKeyPair signer : signers) + { + signedMessageBuilder.by(signer.getSolana4jPublicKey(), new BouncyCastleSigner(signer.getPrivateKeyBytes())); + } + signedMessageBuilder.build(); + + return base58encode(buffer); + } + private static String base58encode(final ByteBuffer bytes) { return Base58.encode(ByteBufferPrimitiveArray.copy(bytes)); diff --git a/src/test-support/java/com/lmax/solana4j/transactionblobs/TransactionBlobFactory.java b/src/test-support/java/com/lmax/solana4j/transactionblobs/TransactionBlobFactory.java index 8f82d68..2ff0162 100644 --- a/src/test-support/java/com/lmax/solana4j/transactionblobs/TransactionBlobFactory.java +++ b/src/test-support/java/com/lmax/solana4j/transactionblobs/TransactionBlobFactory.java @@ -145,4 +145,13 @@ String setComputeUnits( Blockhash blockhash, PublicKey payer, List signers); + + String setUpgradeAuthority( + PublicKey program, + PublicKey oldAuthority, + PublicKey newAuthority, + Blockhash blockhash, + PublicKey payer, + List signers, + List addressLookupTables); } diff --git a/src/test-support/java/com/lmax/solana4j/transactionblobs/V0TransactionBlobFactory.java b/src/test-support/java/com/lmax/solana4j/transactionblobs/V0TransactionBlobFactory.java index 43d8db1..d3ac4f8 100644 --- a/src/test-support/java/com/lmax/solana4j/transactionblobs/V0TransactionBlobFactory.java +++ b/src/test-support/java/com/lmax/solana4j/transactionblobs/V0TransactionBlobFactory.java @@ -14,6 +14,7 @@ import com.lmax.solana4j.domain.TokenProgramFactory; import com.lmax.solana4j.programs.AddressLookupTableProgram; import com.lmax.solana4j.programs.AssociatedTokenProgram; +import com.lmax.solana4j.programs.BpfLoaderUpgradeableProgram; import com.lmax.solana4j.programs.ComputeBudgetProgram; import com.lmax.solana4j.util.BouncyCastleSigner; import org.bitcoinj.core.Base58; @@ -28,7 +29,6 @@ public class V0TransactionBlobFactory implements TransactionBlobFactory { - @Override public String solTransfer( final PublicKey from, @@ -528,6 +528,43 @@ public String setComputeUnits( return base58encode(buffer); } + @Override + public String setUpgradeAuthority( + final PublicKey program, + final PublicKey oldAuthority, + final PublicKey newAuthority, + final Blockhash blockhash, + final PublicKey payer, + final List signers, + final List addressLookupTables) + { + final ByteBuffer buffer = ByteBuffer.allocate(Solana.MAX_MESSAGE_SIZE); + + Solana.builder(buffer) + .v0() + .recent(blockhash) + .instructions(versionedTransactionBuilder -> BpfLoaderUpgradeableProgram.factory(versionedTransactionBuilder) + .setUpgradeAuthority( + program, + oldAuthority, + Optional.of(newAuthority)) + ) + .payer(payer) + .lookups(addressLookupTables) + .seal() + .unsigned() + .build(); + + final SignedMessageBuilder signedMessageBuilder = Solana.forSigning(buffer); + for (final TestKeyPair signer : signers) + { + signedMessageBuilder.by(signer.getSolana4jPublicKey(), new BouncyCastleSigner(signer.getPrivateKeyBytes())); + } + signedMessageBuilder.build(); + + return base58encode(buffer); + } + private static String base58encode(final ByteBuffer bytes) { return Base58.encode(ByteBufferPrimitiveArray.copy(bytes));