diff --git a/src/main/java/org/p2p/solanaj/core/AccountMeta.java b/src/main/java/org/p2p/solanaj/core/AccountMeta.java index bfd6e525..0faa7aca 100644 --- a/src/main/java/org/p2p/solanaj/core/AccountMeta.java +++ b/src/main/java/org/p2p/solanaj/core/AccountMeta.java @@ -13,4 +13,22 @@ public class AccountMeta { private boolean isWritable; + /** + * Sorting based on isSigner and isWritable cannot fully meet the requirements. This value can be used for custom sorting, because if the order is incorrect during serialization, it may lead to failed method calls. + */ + private int sort = Integer.MAX_VALUE; + + public AccountMeta(PublicKey publicKey, boolean isSigner, boolean isWritable) { + this.publicKey = publicKey; + this.isSigner = isSigner; + this.isWritable = isWritable; + } + + public void setSigner(boolean signer) { + isSigner = signer; + } + + public void setWritable(boolean writable) { + isWritable = writable; + } } \ No newline at end of file diff --git a/src/main/java/org/p2p/solanaj/core/Message.java b/src/main/java/org/p2p/solanaj/core/Message.java index b9ca7687..71904be2 100644 --- a/src/main/java/org/p2p/solanaj/core/Message.java +++ b/src/main/java/org/p2p/solanaj/core/Message.java @@ -1,28 +1,38 @@ package org.p2p.solanaj.core; import org.bitcoinj.core.Base58; +import org.p2p.solanaj.utils.ArrayUtils; import org.p2p.solanaj.utils.ShortvecEncoding; import java.nio.ByteBuffer; import java.util.ArrayList; +import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.stream.Collectors; public class Message { - private class MessageHeader { + private static class MessageHeader { static final int HEADER_LENGTH = 3; byte numRequiredSignatures = 0; byte numReadonlySignedAccounts = 0; byte numReadonlyUnsignedAccounts = 0; + public MessageHeader(){} + + MessageHeader(byte[] byteArray) { + numRequiredSignatures = byteArray[0]; + numReadonlySignedAccounts = byteArray[1]; + numReadonlyUnsignedAccounts = byteArray[2]; + } + byte[] toByteArray() { return new byte[] { numRequiredSignatures, numReadonlySignedAccounts, numReadonlyUnsignedAccounts }; } } - private class CompiledInstruction { + private static class CompiledInstruction { byte programIdIndex; byte[] keyIndicesCount; byte[] keyIndices; @@ -39,20 +49,29 @@ int getLength() { private MessageHeader messageHeader; private String recentBlockhash; - private AccountKeysList accountKeys; - private List instructions; - private Account feePayer; + private final AccountKeysList accountKeys; + private final List instructions; + private PublicKey feePayer; public Message() { this.accountKeys = new AccountKeysList(); - this.instructions = new ArrayList(); + this.instructions = new ArrayList<>(); + } + + public Message(MessageHeader messageHeader, String recentBlockhash, AccountKeysList accountKeys, + List compiledInstructions) { + this.messageHeader = messageHeader; + this.recentBlockhash = recentBlockhash; + this.accountKeys = accountKeys; + this.instructions = compiledInstructions; + + this.feePayer = accountKeys.getList().get(0).getPublicKey(); } public Message addInstruction(TransactionInstruction instruction) { accountKeys.addAll(instruction.getKeys()); accountKeys.add(new AccountMeta(instruction.getProgramId(), false, false)); instructions.add(instruction); - return this; } @@ -60,25 +79,55 @@ public void setRecentBlockHash(String recentBlockhash) { this.recentBlockhash = recentBlockhash; } + public String getRecentBlockhash() { + return recentBlockhash; + } + + public MessageHeader getMessageHeader() { + return messageHeader; + } + + public List getInstructions() { + return instructions; + } + public byte[] serialize() { if (recentBlockhash == null) { throw new IllegalArgumentException("recentBlockhash required"); } - if (instructions.size() == 0) { + if (instructions.isEmpty()) { throw new IllegalArgumentException("No instructions provided"); } messageHeader = new MessageHeader(); List keysList = getAccountKeys(); + /** + * ################################################# + * ########## Here's the change. sort ############## + * ################################################# + * + */ + Collections.sort(keysList, new Comparator() { + @Override + public int compare(AccountMeta o1, AccountMeta o2) { + if(o2.isSigner()){ + return 1; + }else if(o1.isSigner()){ + return -1; + }else{ + return 0; + } + } + }); int accountKeysSize = keysList.size(); byte[] accountAddressesLength = ShortvecEncoding.encodeLength(accountKeysSize); int compiledInstructionsLength = 0; - List compiledInstructions = new ArrayList(); + List compiledInstructions = new ArrayList<>(); for (TransactionInstruction instruction : instructions) { int keysSize = instruction.getKeys().size(); @@ -143,18 +192,37 @@ public byte[] serialize() { return out.array(); } - protected void setFeePayer(Account feePayer) { + protected void setFeePayer(PublicKey feePayer) { this.feePayer = feePayer; } + public PublicKey getFeePayer() { + return feePayer; + } + public List getAccountKeys() { - AccountKeysList accounts = new AccountKeysList(); - accounts.add(new AccountMeta(feePayer.getPublicKey(), true, true)); - accounts.addAll(accountKeys); - return accounts.getList(); + List keysList = accountKeys.getList(); + + // Check whether custom sorting is needed. The `getAccountKeys()` method returns a reversed list of accounts, with signable and mutable accounts at the end, but the fee is placed first. When a transaction involves multiple accounts that need signing, an incorrect order can cause bugs. Change to custom sorting based on the contract order. + boolean needSort = keysList.stream().anyMatch(accountMeta -> accountMeta.getSort() < Integer.MAX_VALUE); + if (needSort) { + // Sort in ascending order based on the `sort` field. + return keysList.stream() + .sorted(Comparator.comparingInt(AccountMeta::getSort)) + .collect(Collectors.toList()); + } + + int feePayerIndex = findAccountIndex(keysList, feePayer); + List newList = new ArrayList(); + AccountMeta feePayerMeta = keysList.get(feePayerIndex); + newList.add(new AccountMeta(feePayerMeta.getPublicKey(), true, true)); + keysList.remove(feePayerIndex); + newList.addAll(keysList); + + return newList; } - private int findAccountIndex(List accountMetaList, PublicKey key) { + public int findAccountIndex(List accountMetaList, PublicKey key) { for (int i = 0; i < accountMetaList.size(); i++) { if (accountMetaList.get(i).getPublicKey().equals(key)) { return i; @@ -163,4 +231,105 @@ private int findAccountIndex(List accountMetaList, PublicKey key) { throw new RuntimeException("unable to find account index"); } + + /** + * deserialize Message + * @param serializedMessageList message serialize byte array + * @return Message + * @author jc0803kevin + */ + public static Message deserialize(List serializedMessageList) { + // Remove the byte as it is used to indicate legacy Transaction. +// GuardedArrayUtils.guardedShift(serializedMessageList); + + // Remove three bytes for header + byte[] messageHeaderBytes = ArrayUtils.guardedSplice(serializedMessageList, 0, MessageHeader.HEADER_LENGTH); + MessageHeader messageHeader = new MessageHeader(messageHeaderBytes); + + // Total static account keys + int accountKeysSize = ShortvecEncoding.decodeLength(serializedMessageList); + List accountKeys = new ArrayList<>(accountKeysSize); + for (int i = 0; i < accountKeysSize; i++) { + byte[] accountMetaPublicKeyByteArray = ArrayUtils.guardedSplice(serializedMessageList, 0, + PublicKey.PUBLIC_KEY_LENGTH); + PublicKey publicKey = new PublicKey(accountMetaPublicKeyByteArray); + accountKeys.add(new AccountMeta(publicKey, false, false)); + } + + // setSigner VS setWritable + for (AccountMeta accountKey : accountKeys) { + PublicKey publicKey = accountKey.getPublicKey(); + boolean isSigner = isSigner(accountKeys, publicKey, messageHeader); + boolean isWriter = isWriter(accountKeys, publicKey, messageHeader); + accountKey.setSigner(isSigner); + accountKey.setWritable(isWriter); + } + + AccountKeysList accountKeysList = new AccountKeysList(); + accountKeysList.addAll(accountKeys); + + // recent_blockhash + String recentBlockHash = Base58.encode(ArrayUtils.guardedSplice(serializedMessageList, 0, + PublicKey.PUBLIC_KEY_LENGTH)); + + // Deserialize instructions + int instructionsLength = ShortvecEncoding.decodeLength(serializedMessageList); + List instructions = new ArrayList<>(); + List compiledInstructions = new ArrayList<>(instructionsLength); + for (int i = 0; i < instructionsLength; i++) { + CompiledInstruction compiledInstruction = new CompiledInstruction(); + compiledInstruction.programIdIndex = ArrayUtils.guardedShift(serializedMessageList); + int keysSize = ShortvecEncoding.decodeLength(serializedMessageList); // keysSize + compiledInstruction.keyIndicesCount = ShortvecEncoding.encodeLength(keysSize); + compiledInstruction.keyIndices = ArrayUtils.guardedSplice(serializedMessageList, 0, keysSize); + var dataLength = ShortvecEncoding.decodeLength(serializedMessageList); + compiledInstruction.dataLength = ShortvecEncoding.encodeLength(dataLength); + compiledInstruction.data = ArrayUtils.guardedSplice(serializedMessageList, 0, dataLength); + + compiledInstructions.add(compiledInstruction); + + PublicKey programId = accountKeys.get(compiledInstruction.programIdIndex).getPublicKey(); + List keys = new ArrayList<>(); + for (int i1 = 0; i1 < compiledInstruction.keyIndices.length; i1++) { + keys.add(accountKeys.get(compiledInstruction.keyIndices[i1])); + } + instructions.add(new TransactionInstruction(programId, keys, compiledInstruction.data)); + } + + return new Message(messageHeader, recentBlockHash, accountKeysList, instructions); + } + + private static boolean isWriter(List accountKeys, PublicKey account, MessageHeader messageHeader){ + + int index = indexOf(accountKeys, account); + if(index == -1){ + return false; + } + boolean isSignerWriter= index < messageHeader.numRequiredSignatures - messageHeader.numReadonlySignedAccounts; + boolean isNonSigner = index >= messageHeader.numRequiredSignatures; + boolean isNonSignerReadonly = index >= (accountKeys.size() - messageHeader.numReadonlyUnsignedAccounts); + boolean isNonSignerWriter = isNonSigner && !isNonSignerReadonly; + return isSignerWriter || isNonSignerWriter; + } + + private static boolean isSigner(List accountKeys, PublicKey account, MessageHeader messageHeader) { + int index = indexOf(accountKeys, account); + + if (index == -1) { + return false; + } + + return index < messageHeader.numRequiredSignatures; + } + + private static int indexOf(List accountKeys, PublicKey account){ + for (int i = 0; i < accountKeys.size(); i++) { + if(account.toBase58().equals(accountKeys.get(i).getPublicKey().toBase58())){ + return i; + } + } + + return -1; + } + } diff --git a/src/main/java/org/p2p/solanaj/core/Transaction.java b/src/main/java/org/p2p/solanaj/core/Transaction.java index bab10710..55ff6de8 100644 --- a/src/main/java/org/p2p/solanaj/core/Transaction.java +++ b/src/main/java/org/p2p/solanaj/core/Transaction.java @@ -2,11 +2,12 @@ import java.nio.ByteBuffer; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.Objects; import org.bitcoinj.core.Base58; +import org.p2p.solanaj.utils.ByteUtils; +import org.p2p.solanaj.utils.ArrayUtils; import org.p2p.solanaj.utils.ShortvecEncoding; import org.p2p.solanaj.utils.TweetNaclFast; @@ -30,6 +31,12 @@ public Transaction() { this.signatures = new ArrayList<>(); // Use diamond operator } + public Transaction(Message message, List signatures) { + this.message = message; + this.signatures = signatures; + this.serializedMessage = message.serialize(); + } + /** * Adds an instruction to the transaction. * @@ -61,7 +68,7 @@ public void setRecentBlockHash(String recentBlockhash) { * @throws NullPointerException if the signer is null */ public void sign(Account signer) { - sign(Arrays.asList(Objects.requireNonNull(signer, "Signer cannot be null"))); // Add input validation + sign(List.of(Objects.requireNonNull(signer, "Signer cannot be null"))); // Add input validation } /** @@ -76,7 +83,7 @@ public void sign(List signers) { } Account feePayer = signers.get(0); - message.setFeePayer(feePayer); + message.setFeePayer(feePayer.getPublicKey()); serializedMessage = message.serialize(); @@ -91,6 +98,13 @@ public void sign(List signers) { } } + public String getTxHash(){ + if (signatures == null || signatures.isEmpty()){ + return null; + } + return signatures.get(0); + } + /** * Serializes the transaction into a byte array. * @@ -115,4 +129,26 @@ public byte[] serialize() { return out.array(); } + + /** + * deserialize Transaction + * @param serializedTransaction transaction serialize byte array + * @return + * @author jc080kevin + */ + public static Transaction deserialize(byte[] serializedTransaction) { + List serializedTransactionList = ByteUtils.toByteList(serializedTransaction); + + int signaturesSize = ShortvecEncoding.decodeLength(serializedTransactionList); + List signatures = new ArrayList<>(signaturesSize); + + for (int i = 0; i < signaturesSize; i++) { + + byte[] signatureBytes = ArrayUtils.guardedSplice(serializedTransactionList, 0, SIGNATURE_LENGTH); + signatures.add(Base58.encode(signatureBytes)); + } + + Message message = Message.deserialize(serializedTransactionList); + return new Transaction(message, signatures); + } } diff --git a/src/main/java/org/p2p/solanaj/utils/ArrayUtils.java b/src/main/java/org/p2p/solanaj/utils/ArrayUtils.java new file mode 100644 index 00000000..8544550d --- /dev/null +++ b/src/main/java/org/p2p/solanaj/utils/ArrayUtils.java @@ -0,0 +1,49 @@ +package org.p2p.solanaj.utils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class ArrayUtils { + + public static Byte guardedShift(List byteArray) { + if (byteArray.isEmpty()) { + throw new IllegalArgumentException("Byte array length is 0"); + } + return byteArray.remove(0); + } + + /** + * @param byteList array source + * @param start array start index + * @param deleteCount fetch count + * @param items + * @return + */ + public static byte[] guardedSplice( + List byteList, + Integer start, + Integer deleteCount, + byte... items) { + List removedItems; + if (deleteCount != null) { + if (start + deleteCount > byteList.size()) { + throw new Error("Reached end of bytes"); + } + removedItems = new ArrayList<>(byteList.subList(start, start + deleteCount)); + byteList.subList(start, start + deleteCount).clear(); + } else { + if (start > byteList.size()) { + throw new Error("Reached end of bytes"); + } + removedItems = Collections.emptyList(); + } + List itemsToAdd = ByteUtils.toByteList(items); + byteList.addAll(start, itemsToAdd); + + if (!removedItems.isEmpty()) { + return ByteUtils.toByteArray(removedItems); + } + return ByteUtils.emptyByteArray(); + } +} diff --git a/src/main/java/org/p2p/solanaj/utils/ByteUtils.java b/src/main/java/org/p2p/solanaj/utils/ByteUtils.java index 60ff25d6..9c6426ac 100644 --- a/src/main/java/org/p2p/solanaj/utils/ByteUtils.java +++ b/src/main/java/org/p2p/solanaj/utils/ByteUtils.java @@ -1,11 +1,16 @@ package org.p2p.solanaj.utils; +import com.google.common.primitives.Bytes; + import static org.bitcoinj.core.Utils.*; import java.io.IOException; import java.io.OutputStream; import java.math.BigInteger; import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; public class ByteUtils { public static final int UINT_32_LENGTH = 4; @@ -72,4 +77,27 @@ public static int getBit(byte[] data, int pos) { return valInt; } + public static byte[] toByteArray(List byteList) { + return Bytes.toArray(byteList); + } + + public static List toByteList(byte[] bytes) { + return IntStream.range(0, bytes.length) + .mapToObj(i -> bytes[i]) + .collect(Collectors.toList()); + } + + public static byte[] emptyByteArray() { + return new byte[]{}; + } + + public static byte[] intToByteArray(int[] in) { + byte[] out = new byte[in.length]; + for (int i = 0; i < in.length; i++) { + out[i] = (byte) (in[i] & 0xff); + } + return out; + } + + } diff --git a/src/main/java/org/p2p/solanaj/utils/ShortvecEncoding.java b/src/main/java/org/p2p/solanaj/utils/ShortvecEncoding.java index 4a1d9080..5e7d1d36 100644 --- a/src/main/java/org/p2p/solanaj/utils/ShortvecEncoding.java +++ b/src/main/java/org/p2p/solanaj/utils/ShortvecEncoding.java @@ -1,6 +1,8 @@ package org.p2p.solanaj.utils; -import static org.bitcoinj.core.Utils.*; +import java.util.List; + +import static org.bitcoinj.core.Utils.uint16ToByteArrayLE; public class ShortvecEncoding { @@ -27,4 +29,20 @@ public static byte[] encodeLength(int len) { return bytes; } + + public static int decodeLength(List dataBytesList) { + int len = 0; + int size = 0; + + for (;;) { + int elem = (int) dataBytesList.remove(0); + len |= (elem & 0x7f) << (size * 7); + size += 1; + if ((elem & 0x80) == 0) { + break; + } + } + return len; + } + } diff --git a/src/test/java/org/p2p/solanaj/core/MessageTest.java b/src/test/java/org/p2p/solanaj/core/MessageTest.java index 05db0fda..0b35a568 100644 --- a/src/test/java/org/p2p/solanaj/core/MessageTest.java +++ b/src/test/java/org/p2p/solanaj/core/MessageTest.java @@ -3,23 +3,32 @@ import org.bitcoinj.core.Base58; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; + import org.p2p.solanaj.programs.SystemProgram; +import org.p2p.solanaj.utils.ByteUtils; + +import java.util.Arrays; +import java.util.List; public class MessageTest { + Account signer = new Account(Base58 + .decode("4Z7cXSyeFR8wNGMVXUE1TwtKn5D5Vu7FzEv69dokLv7KrQk7h6pu4LF8ZRR9yQBhc7uSM6RTTZtU1fmaxiNrxXrs")); - @Test - public void serializeMessage() { + private TransactionInstruction transfer(){ PublicKey fromPublicKey = new PublicKey("QqCCvshxtqMAL2CVALqiJB7uEeE5mjSPsseQdDzsRUo"); PublicKey toPublickKey = new PublicKey("GrDMoeqMLFjeXQ24H56S1RLgT4R76jsuWCd6SvXyGPQ5"); int lamports = 3000; - Account signer = new Account(Base58 - .decode("4Z7cXSyeFR8wNGMVXUE1TwtKn5D5Vu7FzEv69dokLv7KrQk7h6pu4LF8ZRR9yQBhc7uSM6RTTZtU1fmaxiNrxXrs")); + return SystemProgram.transfer(fromPublicKey, toPublickKey, lamports); + } + @Test + public void serializeMessage() { Message message = new Message(); - message.addInstruction(SystemProgram.transfer(fromPublicKey, toPublickKey, lamports)); + message.setFeePayer(signer.getPublicKey()); + message.addInstruction(transfer()); message.setRecentBlockHash("Eit7RCyhUixAe2hGBS8oqnw59QK3kgMMjfLME5bm9wRn"); - message.setFeePayer(signer); assertArrayEquals(new int[] { 1, 0, 1, 3, 6, 26, 217, 208, 83, 135, 21, 72, 83, 126, 222, 62, 38, 24, 73, 163, 223, 183, 253, 2, 250, 188, 117, 178, 35, 200, 228, 106, 219, 133, 61, 12, 235, 122, 188, 208, 216, 117, @@ -30,6 +39,56 @@ public void serializeMessage() { 0, 0, 0, 0, 0 }, toUnsignedByteArray(message.serialize())); } + @Test + public void deserialize(){ + int[] serialize = new int[] { 1, 0, 1, 3, 6, 26, 217, 208, 83, 135, 21, 72, 83, 126, 222, 62, 38, 24, 73, 163, + 223, 183, 253, 2, 250, 188, 117, 178, 35, 200, 228, 106, 219, 133, 61, 12, 235, 122, 188, 208, 216, 117, + 235, 194, 109, 161, 177, 129, 163, 51, 155, 62, 242, 163, 22, 149, 187, 122, 189, 188, 103, 130, 115, + 188, 173, 205, 229, 170, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 203, 226, 136, 193, 153, 148, 240, 50, 230, 98, 9, 79, 221, 179, 243, 174, 90, 67, + 104, 169, 6, 187, 165, 72, 36, 156, 19, 57, 132, 38, 69, 245, 1, 2, 2, 0, 1, 12, 2, 0, 0, 0, 184, 11, 0, + 0, 0, 0, 0, 0 }; + List bytes = ByteUtils.toByteList(ByteUtils.intToByteArray(serialize)); + Message message = Message.deserialize(bytes); + assertEquals("Eit7RCyhUixAe2hGBS8oqnw59QK3kgMMjfLME5bm9wRn", message.getRecentBlockhash()); +// assertArrayEquals(new int[]{1, 0, 1}, toUnsignedByteArray(message.getMessageHeader().toByteArray())); + assertEquals(1, message.getInstructions().size()); + + TransactionInstruction transfer = transfer(); + TransactionInstruction transferNew = message.getInstructions().get(0); + assertEquals(transfer.getProgramId(), transferNew.getProgramId()); + assertArrayEquals(transfer.getData(), transferNew.getData()); + + for (int i = 0; i < transfer.getKeys().size(); i++) { + assertEquals(transfer.getKeys().get(i).getPublicKey().toBase58(), transferNew.getKeys().get(i).getPublicKey().toBase58()); + } + int[] serializeNew = toUnsignedByteArray(message.serialize()); + System.out.println(Arrays.toString(serializeNew)); + + assertArrayEquals(serialize, serializeNew); + } + + + @Test + public void int_byte_convert(){ + int[] serialize = new int[] { 1, 0, 1, 3, 6, 26, 217, 208, 83, 135, 21, 72, 83, 126, 222, 62, 38, 24, 73, 163, + 223, 183, 253, 2, 250, 188, 117, 178, 35, 200, 228, 106, 219, 133, 61, 12, 235, 122, 188, 208, 216, 117, + 235, 194, 109, 161, 177, 129, 163, 51, 155, 62, 242, 163, 22, 149, 187, 122, 189, 188, 103, 130, 115, + 188, 173, 205, 229, 170, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 203, 226, 136, 193, 153, 148, 240, 50, 230, 98, 9, 79, 221, 179, 243, 174, 90, 67, + 104, 169, 6, 187, 165, 72, 36, 156, 19, 57, 132, 38, 69, 245, 1, 2, 2, 0, 1, 12, 2, 0, 0, 0, 184, 11, 0, + 0, 0, 0, 0, 0 }; + + byte[] bytes = ByteUtils.intToByteArray(serialize); + + int[] serializeNew = toUnsignedByteArray(bytes); + + byte[] bytesNew = ByteUtils.intToByteArray(serializeNew); + + assertArrayEquals(serialize, serializeNew); + assertArrayEquals(bytes, bytesNew); + } + int[] toUnsignedByteArray(byte[] in) { int[] out = new int[in.length]; @@ -39,4 +98,5 @@ int[] toUnsignedByteArray(byte[] in) { return out; } + } diff --git a/src/test/java/org/p2p/solanaj/core/TransactionTest.java b/src/test/java/org/p2p/solanaj/core/TransactionTest.java index eae14086..e2253050 100644 --- a/src/test/java/org/p2p/solanaj/core/TransactionTest.java +++ b/src/test/java/org/p2p/solanaj/core/TransactionTest.java @@ -1,15 +1,14 @@ package org.p2p.solanaj.core; +import org.bitcoinj.core.Base58; +import org.junit.jupiter.api.Test; import org.p2p.solanaj.programs.MemoProgram; import org.p2p.solanaj.programs.SystemProgram; -import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.*; - import java.util.Base64; import java.util.List; -import org.bitcoinj.core.Base58; +import static org.junit.jupiter.api.Assertions.assertEquals; public class TransactionTest { @@ -28,11 +27,27 @@ public void signAndSerialize() { transaction.sign(signer); byte[] serializedTransaction = transaction.serialize(); + assertEquals("nXkZvmiP3kzZbR7u95NSoK78Y3YqgSSthseuba99uBGsEBnR4RXugEhrAFmqhvWiN8k9aZNTZTE22NH6nBX3B7T", transaction.getTxHash()); + assertEquals( "ASdDdWBaKXVRA+6flVFiZokic9gK0+r1JWgwGg/GJAkLSreYrGF4rbTCXNJvyut6K6hupJtm72GztLbWNmRF1Q4BAAEDBhrZ0FOHFUhTft4+JhhJo9+3/QL6vHWyI8jkatuFPQzrerzQ2HXrwm2hsYGjM5s+8qMWlbt6vbxngnO8rc3lqgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAy+KIwZmU8DLmYglP3bPzrlpDaKkGu6VIJJwTOYQmRfUBAgIAAQwCAAAAuAsAAAAAAAA=", Base64.getEncoder().encodeToString(serializedTransaction)); } + + @Test + public void deserialize() { + String serializedTxBase64 = "ASdDdWBaKXVRA+6flVFiZokic9gK0+r1JWgwGg/GJAkLSreYrGF4rbTCXNJvyut6K6hupJtm72GztLbWNmRF1Q4BAAEDBhrZ0FOHFUhTft4+JhhJo9+3/QL6vHWyI8jkatuFPQzrerzQ2HXrwm2hsYGjM5s+8qMWlbt6vbxngnO8rc3lqgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAy+KIwZmU8DLmYglP3bPzrlpDaKkGu6VIJJwTOYQmRfUBAgIAAQwCAAAAuAsAAAAAAAA="; + byte[] serializedTransaction = Base64.getDecoder().decode(serializedTxBase64); + Transaction transaction = Transaction.deserialize(serializedTransaction); + + assertEquals("nXkZvmiP3kzZbR7u95NSoK78Y3YqgSSthseuba99uBGsEBnR4RXugEhrAFmqhvWiN8k9aZNTZTE22NH6nBX3B7T", transaction.getTxHash()); + + byte[] serializedNew = transaction.serialize(); + + assertEquals(Base64.getEncoder().encodeToString(serializedNew), serializedTxBase64); + } + @Test public void transactionBuilderTest() { final String memo = "Test memo"; diff --git a/src/test/java/org/p2p/solanaj/utils/ArrayUtilsTest.java b/src/test/java/org/p2p/solanaj/utils/ArrayUtilsTest.java new file mode 100644 index 00000000..dbb1f8b2 --- /dev/null +++ b/src/test/java/org/p2p/solanaj/utils/ArrayUtilsTest.java @@ -0,0 +1,33 @@ +package org.p2p.solanaj.utils; + +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class ArrayUtilsTest { + + @Test + public void guardedSplice(){ + int[] serialize = new int[] { 1, 0, 1, 3, 6, 26, 217, 208, 83, 135, 21, 72, 83, 126, 222, 62, 38, 24, 73, 163, + 223, 183, 253, 2, 250, 188, 117, 178, 35, 200, 228, 106, 219, 133, 61, 12, 235, 122, 188, 208, 216, 117, + 235, 194, 109, 161, 177, 129, 163, 51, 155, 62, 242, 163, 22, 149, 187, 122, 189, 188, 103, 130, 115, + 188, 173, 205, 229, 170, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 203, 226, 136, 193, 153, 148, 240, 50, 230, 98, 9, 79, 221, 179, 243, 174, 90, 67, + 104, 169, 6, 187, 165, 72, 36, 156, 19, 57, 132, 38, 69, 245, 1, 2, 2, 0, 1, 12, 2, 0, 0, 0, 184, 11, 0, + 0, 0, 0, 0, 0 }; + List bytes = ByteUtils.toByteList(ByteUtils.intToByteArray(serialize)); + + byte[] splice = ArrayUtils.guardedSplice(bytes, 1, 5); + System.out.println(Arrays.toString(splice)); + + assertEquals(bytes.size(), serialize.length - 5); + assertArrayEquals(splice, new byte[]{0, 1, 3, 6, 26}); + + } + + +} diff --git a/src/test/java/org/p2p/solanaj/utils/ShortvecEncodingTest.java b/src/test/java/org/p2p/solanaj/utils/ShortvecEncodingTest.java index 4d6ee62b..91724b9f 100644 --- a/src/test/java/org/p2p/solanaj/utils/ShortvecEncodingTest.java +++ b/src/test/java/org/p2p/solanaj/utils/ShortvecEncodingTest.java @@ -14,8 +14,39 @@ public void encodeLength() { assertArrayEquals(new byte[] { -128, 1 }/* [0x80, 0x01] */, ShortvecEncoding.encodeLength(128)); // 0x80 assertArrayEquals(new byte[] { -1, 1 } /* [0xff, 0x01] */, ShortvecEncoding.encodeLength(255)); // 0xff assertArrayEquals(new byte[] { -128, 2 } /* [0x80, 0x02] */, ShortvecEncoding.encodeLength(256)); // 0x100 + assertArrayEquals(new byte[]{ (byte) 0b10101100, (byte) 0b00000010}, ShortvecEncoding.encodeLength(300)); // assertArrayEquals(new byte[] { -1, -1, 1 } /* [0xff, 0xff, 0x01] */, ShortvecEncoding.encodeLength(32767)); // 0x7fff assertArrayEquals(new byte[] { -128, -128, -128, 1 } /* [0x80, 0x80, 0x80, 0x01] */, ShortvecEncoding.encodeLength(2097152)); // 0x200000 } + + @Test + public void decodeLength(){ + +// assertEquals(0, ShortvecEncoding.decodeLength(new byte[]{0})); +// assertEquals(1, ShortvecEncoding.decodeLength(new byte[]{1})); +// assertEquals(5, ShortvecEncoding.decodeLength(new byte[]{5})); +// assertEquals(127, ShortvecEncoding.decodeLength(new byte[]{127})); +// assertEquals(128, ShortvecEncoding.decodeLength(new byte[]{-128, 1})); +// assertEquals(255, ShortvecEncoding.decodeLength(new byte[]{-1, 1})); +// assertEquals(256, ShortvecEncoding.decodeLength(new byte[]{-128, 2})); +// assertEquals(32767, ShortvecEncoding.decodeLength(new byte[]{-1, -1, 1})); +// assertEquals(2097152, ShortvecEncoding.decodeLength(new byte[]{-128, -128, -128, 1})); + + } + @Test + public void decodeLength2(){ + + assertEquals(0, ShortvecEncoding.decodeLength(ByteUtils.toByteList(new byte[]{0}))); + assertEquals(1, ShortvecEncoding.decodeLength(ByteUtils.toByteList(new byte[]{1}))); + assertEquals(5, ShortvecEncoding.decodeLength(ByteUtils.toByteList(new byte[]{5}))); + assertEquals(127, ShortvecEncoding.decodeLength(ByteUtils.toByteList(new byte[]{127}))); + assertEquals(128, ShortvecEncoding.decodeLength(ByteUtils.toByteList(new byte[]{-128, 1}))); + assertEquals(255, ShortvecEncoding.decodeLength(ByteUtils.toByteList(new byte[]{-1, 1}))); + assertEquals(256, ShortvecEncoding.decodeLength(ByteUtils.toByteList(new byte[]{-128, 2}))); + assertEquals(300, ShortvecEncoding.decodeLength(ByteUtils.toByteList(new byte[]{ (byte) 0b10101100, (byte) 0b00000010}))); + assertEquals(32767, ShortvecEncoding.decodeLength(ByteUtils.toByteList(new byte[]{-1, -1, 1}))); + assertEquals(2097152, ShortvecEncoding.decodeLength(ByteUtils.toByteList(new byte[]{-128, -128, -128, 1}))); + + } }