Skip to content

Commit bcb3f29

Browse files
committed
v4.1.0
- Fix Solana pre-instruction serialization issues
1 parent 1dde91e commit bcb3f29

File tree

515 files changed

+3037
-1428
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

515 files changed

+3037
-1428
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
# 4.1.0
2+
3+
- Fix Solana pre-instruction serialization issues
4+
15
## 4.0.1
26

37
- Resolve issues with TRON model deserialization on the web.

example/lib/example/solana/quick_wallet_for_testing/quick_wallet.dart

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ class QuickWalletForTest {
3434
return th;
3535
}
3636

37-
Future<Map<String, dynamic>> simulateTR(String digest,
37+
Future<SimulateTranasctionResponse> simulateTR(String digest,
3838
{bool signVerify = false, RPCAccountConfig? config}) async {
3939
final th = await rpc.request(SolanaRPCSimulateTransaction(
4040
encodedTransaction: digest, sigVerify: signVerify, accounts: config));

lib/solana/src/address/sol_address.dart

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import 'package:blockchain_utils/blockchain_utils.dart';
2+
import 'package:on_chain/solana/src/exception/exception.dart';
23
import 'package:on_chain/solana/src/keypair/public_key.dart';
34

45
/// Represents a Solana address.
@@ -21,7 +22,7 @@ class SolAddress {
2122
/// Constructs a Solana address without checking the curve of the bytes.
2223
factory SolAddress.uncheckBytes(List<int> keyBytes) {
2324
if (keyBytes.length != 32) {
24-
throw const MessageException(
25+
throw const SolanaPluginException(
2526
"The public key must have a length of 32 bytes.");
2627
}
2728
return SolAddress._(Base58Encoder.encode(keyBytes));

lib/solana/src/borsh_serialization/core/borsh_serializable.dart

+24-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import 'package:blockchain_utils/blockchain_utils.dart';
2+
import 'package:on_chain/solana/src/exception/exception.dart';
23

34
/// Abstract class for objects that can be serialized using a specific layout.
45
abstract class LayoutSerializable {
@@ -35,19 +36,39 @@ abstract class LayoutSerializable {
3536
for (final i in validator.entries) {
3637
if (i.value is List) {
3738
if (!CompareUtils.iterableIsEqual(i.value, decode[i.key])) {
38-
throw MessageException("cannot validate borsh bytes",
39+
throw SolanaPluginException("cannot validate borsh bytes",
3940
details: {"excepted": validator, "instruction": decode});
4041
}
4142
} else {
4243
if (i.value != decode[i.key]) {
43-
throw MessageException("cannot validate borsh bytes",
44+
throw SolanaPluginException("cannot validate borsh bytes",
4445
details: {"excepted": validator, "instruction": decode});
4546
}
4647
}
4748
}
4849
return decode;
4950
} catch (e) {
50-
throw const MessageException("cannot validate borsh bytes");
51+
throw const SolanaPluginException("cannot validate borsh bytes");
5152
}
5253
}
54+
55+
static _toString(dynamic value) {
56+
if (value is List<int>) {
57+
return BytesUtils.toHexString(value, prefix: "0x");
58+
} else if (value is BigInt) {
59+
return value.toString();
60+
} else if (value is LayoutSerializable) {
61+
return value.toJson();
62+
}
63+
return value.toString();
64+
}
65+
66+
Map<String, dynamic> toJson() {
67+
final json = serialize()..removeWhere((k, v) => v == null);
68+
Map<String, dynamic> toHuman = {};
69+
for (final i in json.entries) {
70+
toHuman[i.key] = _toString(i.value);
71+
}
72+
return toHuman;
73+
}
5374
}

lib/solana/src/borsh_serialization/core/program_layout.dart

+82-7
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,21 @@
1-
import 'package:blockchain_utils/blockchain_utils.dart';
2-
import 'package:on_chain/solana/src/borsh_serialization/core/borsh_serializable.dart';
1+
import 'package:blockchain_utils/layout/layout.dart';
2+
import 'package:blockchain_utils/utils/utils.dart';
3+
import 'package:on_chain/solana/src/address/sol_address.dart';
4+
import 'package:on_chain/solana/src/borsh_serialization/program_layout.dart';
5+
import 'package:on_chain/solana/src/exception/exception.dart';
6+
// import 'package:on_chain/solana/src/instructions/associated_token_account/constant.dart';
7+
import 'package:on_chain/solana/src/instructions/instructions.dart';
38

49
/// Abstract class for Borsh serializable programs.
5-
abstract class ProgramLayout implements LayoutSerializable {
10+
abstract class ProgramLayout extends LayoutSerializable {
611
const ProgramLayout();
712

813
/// The layout representing the structure of the program.
914
@override
1015
abstract final StructLayout layout;
1116

1217
/// The instruction of the program.
13-
abstract final dynamic instruction;
18+
abstract final ProgramLayoutInstruction instruction;
1419

1520
/// Serializes the program.
1621
@override
@@ -19,7 +24,8 @@ abstract class ProgramLayout implements LayoutSerializable {
1924
/// Converts the program to bytes using Borsh serialization.
2025
@override
2126
List<int> toBytes() {
22-
return layout.serialize({"instruction": instruction, ...serialize()});
27+
return layout
28+
.serialize({"instruction": instruction.insturction, ...serialize()});
2329
}
2430

2531
/// Converts the program to a hexadecimal string.
@@ -43,20 +49,89 @@ abstract class ProgramLayout implements LayoutSerializable {
4349
final decode = layout.deserialize(bytes).value;
4450
if (instruction != null) {
4551
if (decode["instruction"] != instruction) {
46-
throw MessageException("invalid instruction index", details: {
52+
throw SolanaPluginException("invalid instruction index", details: {
4753
"excepted": instruction,
4854
"instruction": decode["instruction"]
4955
});
5056
}
5157
}
5258
if (discriminator != null) {
5359
if (decode["discriminator"] != discriminator) {
54-
throw MessageException("invalid discriminator", details: {
60+
throw SolanaPluginException("invalid discriminator", details: {
5561
"excepted": discriminator,
5662
"instruction": decode["discriminator"]
5763
});
5864
}
5965
}
6066
return decode;
6167
}
68+
69+
factory ProgramLayout.fromBytes({
70+
required SolAddress programId,
71+
required List<int> instructionBytes,
72+
}) {
73+
try {
74+
if (programId == AddressLookupTableProgramConst.programId) {
75+
return AddressLookupTableProgramLayout.fromBytes(instructionBytes);
76+
} else if (programId == ComputeBudgetConst.programId) {
77+
return ComputeBudgetProgramLayout.fromBytes(instructionBytes);
78+
} else if (programId == Ed25519ProgramConst.programId) {
79+
return Ed25519ProgramLayout.fromBuffer(instructionBytes);
80+
} else if (programId == MemoProgramConst.programId) {
81+
return MemoLayout.fromBuffer(instructionBytes);
82+
} else if (programId == NameServiceProgramConst.programId) {
83+
return NameServiceProgramLayout.fromBytes(instructionBytes);
84+
} else if (programId == Secp256k1ProgramConst.programId) {
85+
return Secp256k1Layout.fromBuffer(instructionBytes);
86+
} else if (programId == SPLTokenProgramConst.tokenProgramId ||
87+
programId == SPLTokenProgramConst.token2022ProgramId) {
88+
try {
89+
return SPLTokenProgramLayout.fromBytes(instructionBytes);
90+
} on UnimplementedError {
91+
return SPLTokenMetaDataProgramLayout.fromBytes(instructionBytes);
92+
}
93+
} else if (programId == SPLTokenSwapConst.oldTokenSwapProgramId ||
94+
programId == SPLTokenSwapConst.tokenSwapProgramId) {
95+
} else if (programId == StakeProgramConst.programId) {
96+
return StakeProgramLayout.fromBytes(instructionBytes);
97+
} else if (programId == StakePoolProgramConst.programId) {
98+
return StakePoolProgramLayout.fromBytes(instructionBytes);
99+
} else if (programId == SystemProgramConst.programId) {
100+
return SystemProgramLayout.fromBytes(instructionBytes);
101+
} else if (programId == VoteProgramConst.programId) {
102+
return VoteProgramLayout.fromBytes(instructionBytes);
103+
} else if (programId == TokenLendingProgramConst.lendingProgramId) {
104+
return TokenLendingProgramLayout.fromBytes(instructionBytes);
105+
} else if (programId ==
106+
AssociatedTokenAccountProgramConst.associatedTokenProgramId) {
107+
return AssociatedTokenAccountProgramLayout.fromBytes(instructionBytes);
108+
} else if (programId == MetaplexAuctionHouseProgramConst.programId) {
109+
return MetaplexAuctionHouseProgramLayout.fromBytes(instructionBytes);
110+
} else if (programId == MetaplexAuctioneerProgramConst.programId) {
111+
return MetaplexAuctioneerProgramLayout.fromBytes(instructionBytes);
112+
} else if (programId == MetaplexBubblegumProgramConst.programId) {
113+
return MetaplexBubblegumProgramLayout.fromBytes(instructionBytes);
114+
} else if (programId ==
115+
MetaplexCandyMachineCoreProgramConst.candyMachineV3programId ||
116+
programId ==
117+
MetaplexCandyMachineCoreProgramConst.candyGuardProgramId) {
118+
return MetaplexCandyMachineProgramLayout.fromBytes(instructionBytes);
119+
} else if (programId == MetaplexFixedPriceSaleProgramConst.programId) {
120+
return MetaplexFixedPriceSaleProgramLayout.fromBytes(instructionBytes);
121+
} else if (programId == MetaplexGumdropProgramConst.programId) {
122+
return MetaplexGumdropProgramLayout.fromBytes(instructionBytes);
123+
} else if (programId == MetaplexHydraProgramConst.programId) {
124+
return MetaplexHydraProgramLayout.fromBytes(instructionBytes);
125+
} else if (programId == MetaplexNFTPacksProgramConst.programId) {
126+
return MetaplexNFTPacksProgramLayout.fromBytes(instructionBytes);
127+
} else if (programId == MetaplexTokenEntanglerProgramConst.programId) {
128+
return MetaplexTokenEntanglerProgramLayout.fromBytes(instructionBytes);
129+
} else if (programId == MetaplexTokenMetaDataProgramConst.programId) {
130+
return MetaplexTokenMetaDataProgramLayout.fromBytes(instructionBytes);
131+
}
132+
return UnknownProgramLayout(instructionBytes);
133+
} catch (e) {
134+
return UnknownProgramLayout(instructionBytes);
135+
}
136+
}
62137
}
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,25 @@
1+
import 'package:on_chain/solana/solana.dart';
2+
13
abstract class ProgramLayoutInstruction {
24
abstract final dynamic insturction;
35
abstract final String name;
6+
abstract final String programName;
7+
abstract final SolAddress programAddress;
8+
}
9+
10+
class UnknownProgramInstruction implements ProgramLayoutInstruction {
11+
@override
12+
final dynamic insturction;
13+
@override
14+
final String name;
15+
const UnknownProgramInstruction(this.insturction, this.name);
16+
static const UnknownProgramInstruction unknown =
17+
UnknownProgramInstruction(null, "Unknown");
18+
19+
static const List<UnknownProgramInstruction> values = [unknown];
20+
21+
@override
22+
String get programName => "Unknown";
23+
@override
24+
SolAddress get programAddress => throw UnimplementedError();
425
}

lib/solana/src/borsh_serialization/unknown_program_layout/unknown_program_layout.dart

+11-1
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
import 'package:blockchain_utils/layout/layout.dart';
2+
import 'package:blockchain_utils/utils/binary/utils.dart';
23
import 'package:on_chain/solana/src/borsh_serialization/core/program_layout.dart';
4+
import 'package:on_chain/solana/src/borsh_serialization/instuction/instuction.dart';
35

46
/// Represents an unknown program layout.
57
class UnknownProgramLayout extends ProgramLayout {
68
/// The data of the unknown program layout.
79
const UnknownProgramLayout(this.data);
810
final List<int> data;
911
@override
10-
get instruction => null;
12+
UnknownProgramInstruction get instruction =>
13+
UnknownProgramInstruction.unknown;
1114

1215
@override
1316
StructLayout get layout =>
@@ -24,4 +27,11 @@ class UnknownProgramLayout extends ProgramLayout {
2427
List<int> toBytes() {
2528
return data;
2629
}
30+
31+
@override
32+
Map<String, dynamic> toJson() {
33+
return {
34+
"data": BytesUtils.toHexString(data, prefix: "0x"),
35+
};
36+
}
2737
}
+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import 'package:blockchain_utils/exception/exceptions.dart';
2+
3+
class SolanaPluginException extends BlockchainUtilsException {
4+
const SolanaPluginException(this.message, {this.details});
5+
6+
@override
7+
final String message;
8+
9+
@override
10+
final Map<String, dynamic>? details;
11+
12+
@override
13+
String toString() {
14+
String msg = message;
15+
if (details?.isNotEmpty ?? false) {
16+
msg += ' Details: $details';
17+
}
18+
return msg;
19+
}
20+
}

lib/solana/src/instructions/address_lockup_table/accounts/accounts/account_lookup_table.dart

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import 'package:blockchain_utils/exception/exception.dart';
21
import 'package:blockchain_utils/layout/layout.dart';
32
import 'package:on_chain/solana/src/address/sol_address.dart';
3+
import 'package:on_chain/solana/src/exception/exception.dart';
44
import 'package:on_chain/solana/src/instructions/address_lockup_table/constant.dart';
55
import 'package:on_chain/solana/src/transaction/constant/solana_transaction_constant.dart';
66
import 'package:on_chain/solana/src/utils/layouts.dart';
@@ -38,7 +38,7 @@ class _Utils {
3838
if (serializedAddressesLen < 0 ||
3939
serializedAddressesLen % SolanaTransactionConstant.publicKeyLength !=
4040
0) {
41-
throw const MessageException("Lookup table is invalid");
41+
throw const SolanaPluginException("Lookup table is invalid");
4242
}
4343
final meta = lookupTableMetaLayout.deserialize(accountData).value;
4444

lib/solana/src/instructions/address_lockup_table/layouts/instruction/instructions.dart

+8
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
import 'package:on_chain/solana/src/address/sol_address.dart';
12
import 'package:on_chain/solana/src/borsh_serialization/program_layout.dart';
3+
import 'package:on_chain/solana/src/instructions/address_lockup_table/constant.dart';
24

35
class AddressLookupTableProgramInstruction implements ProgramLayoutInstruction {
46
@override
@@ -30,4 +32,10 @@ class AddressLookupTableProgramInstruction implements ProgramLayoutInstruction {
3032
return null;
3133
}
3234
}
35+
36+
@override
37+
String get programName => "AddressLookupTable";
38+
39+
@override
40+
SolAddress get programAddress => AddressLookupTableProgramConst.programId;
3341
}

lib/solana/src/instructions/address_lockup_table/layouts/instruction/program_layout.dart

+2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import 'package:on_chain/solana/src/borsh_serialization/program_layout.dart';
44

55
abstract class AddressLookupTableProgramLayout extends ProgramLayout {
66
const AddressLookupTableProgramLayout();
7+
@override
8+
ProgramLayoutInstruction get instruction;
79
static final StructLayout _layout =
810
LayoutConst.struct([LayoutConst.u32(property: "instruction")]);
911
static ProgramLayout fromBytes(List<int> data) {

lib/solana/src/instructions/address_lockup_table/layouts/layouts/close_lookup_table.dart

+7-2
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,16 @@ class AddressLookupCloseLookupTableLayout
2525
StructLayout get layout => _layout;
2626

2727
@override
28-
int get instruction =>
29-
AddressLookupTableProgramInstruction.closeLookupTable.insturction;
28+
AddressLookupTableProgramInstruction get instruction =>
29+
AddressLookupTableProgramInstruction.closeLookupTable;
3030

3131
@override
3232
Map<String, dynamic> serialize() {
3333
return {};
3434
}
35+
36+
@override
37+
Map<String, dynamic> toJson() {
38+
return {};
39+
}
3540
}

lib/solana/src/instructions/address_lockup_table/layouts/layouts/create_lookup_table.dart

+2-2
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ class AddressLookupCreateLookupTableLayout
3939
StructLayout get layout => _layout;
4040

4141
@override
42-
int get instruction =>
43-
AddressLookupTableProgramInstruction.createLookupTable.insturction;
42+
AddressLookupTableProgramInstruction get instruction =>
43+
AddressLookupTableProgramInstruction.createLookupTable;
4444

4545
@override
4646
Map<String, dynamic> serialize() {

lib/solana/src/instructions/address_lockup_table/layouts/layouts/deactive_lookup_table.dart

+2-2
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ class AddressLookupDeactiveLookupTableLayout
2626
StructLayout get layout => _layout;
2727

2828
@override
29-
int get instruction =>
30-
AddressLookupTableProgramInstruction.deactivateLookupTable.insturction;
29+
AddressLookupTableProgramInstruction get instruction =>
30+
AddressLookupTableProgramInstruction.deactivateLookupTable;
3131

3232
@override
3333
Map<String, dynamic> serialize() {

lib/solana/src/instructions/address_lockup_table/layouts/layouts/extend_lookup_table.dart

+7-2
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,16 @@ class AddressExtendLookupTableLayout extends AddressLookupTableProgramLayout {
3838
StructLayout get layout => _layout;
3939

4040
@override
41-
int get instruction =>
42-
AddressLookupTableProgramInstruction.extendLookupTable.insturction;
41+
AddressLookupTableProgramInstruction get instruction =>
42+
AddressLookupTableProgramInstruction.extendLookupTable;
4343

4444
@override
4545
Map<String, dynamic> serialize() {
4646
return {"addresses": addresses};
4747
}
48+
49+
@override
50+
Map<String, dynamic> toJson() {
51+
return {"addresses": addresses.map((e) => e.address).toList()};
52+
}
4853
}

0 commit comments

Comments
 (0)