Skip to content

Commit

Permalink
v4.1.0
Browse files Browse the repository at this point in the history
- Fix Solana pre-instruction serialization issues
  • Loading branch information
mrtnetwork committed Sep 1, 2024
1 parent 1dde91e commit bcb3f29
Show file tree
Hide file tree
Showing 515 changed files with 3,037 additions and 1,428 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# 4.1.0

- Fix Solana pre-instruction serialization issues

## 4.0.1

- Resolve issues with TRON model deserialization on the web.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class QuickWalletForTest {
return th;
}

Future<Map<String, dynamic>> simulateTR(String digest,
Future<SimulateTranasctionResponse> simulateTR(String digest,
{bool signVerify = false, RPCAccountConfig? config}) async {
final th = await rpc.request(SolanaRPCSimulateTransaction(
encodedTransaction: digest, sigVerify: signVerify, accounts: config));
Expand Down
3 changes: 2 additions & 1 deletion lib/solana/src/address/sol_address.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:blockchain_utils/blockchain_utils.dart';
import 'package:on_chain/solana/src/exception/exception.dart';
import 'package:on_chain/solana/src/keypair/public_key.dart';

/// Represents a Solana address.
Expand All @@ -21,7 +22,7 @@ class SolAddress {
/// Constructs a Solana address without checking the curve of the bytes.
factory SolAddress.uncheckBytes(List<int> keyBytes) {
if (keyBytes.length != 32) {
throw const MessageException(
throw const SolanaPluginException(
"The public key must have a length of 32 bytes.");
}
return SolAddress._(Base58Encoder.encode(keyBytes));
Expand Down
27 changes: 24 additions & 3 deletions lib/solana/src/borsh_serialization/core/borsh_serializable.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:blockchain_utils/blockchain_utils.dart';
import 'package:on_chain/solana/src/exception/exception.dart';

/// Abstract class for objects that can be serialized using a specific layout.
abstract class LayoutSerializable {
Expand Down Expand Up @@ -35,19 +36,39 @@ abstract class LayoutSerializable {
for (final i in validator.entries) {
if (i.value is List) {
if (!CompareUtils.iterableIsEqual(i.value, decode[i.key])) {
throw MessageException("cannot validate borsh bytes",
throw SolanaPluginException("cannot validate borsh bytes",
details: {"excepted": validator, "instruction": decode});
}
} else {
if (i.value != decode[i.key]) {
throw MessageException("cannot validate borsh bytes",
throw SolanaPluginException("cannot validate borsh bytes",
details: {"excepted": validator, "instruction": decode});
}
}
}
return decode;
} catch (e) {
throw const MessageException("cannot validate borsh bytes");
throw const SolanaPluginException("cannot validate borsh bytes");
}
}

static _toString(dynamic value) {
if (value is List<int>) {
return BytesUtils.toHexString(value, prefix: "0x");
} else if (value is BigInt) {
return value.toString();
} else if (value is LayoutSerializable) {
return value.toJson();
}
return value.toString();
}

Map<String, dynamic> toJson() {
final json = serialize()..removeWhere((k, v) => v == null);
Map<String, dynamic> toHuman = {};
for (final i in json.entries) {
toHuman[i.key] = _toString(i.value);
}
return toHuman;
}
}
89 changes: 82 additions & 7 deletions lib/solana/src/borsh_serialization/core/program_layout.dart
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
import 'package:blockchain_utils/blockchain_utils.dart';
import 'package:on_chain/solana/src/borsh_serialization/core/borsh_serializable.dart';
import 'package:blockchain_utils/layout/layout.dart';
import 'package:blockchain_utils/utils/utils.dart';
import 'package:on_chain/solana/src/address/sol_address.dart';
import 'package:on_chain/solana/src/borsh_serialization/program_layout.dart';
import 'package:on_chain/solana/src/exception/exception.dart';
// import 'package:on_chain/solana/src/instructions/associated_token_account/constant.dart';
import 'package:on_chain/solana/src/instructions/instructions.dart';

/// Abstract class for Borsh serializable programs.
abstract class ProgramLayout implements LayoutSerializable {
abstract class ProgramLayout extends LayoutSerializable {
const ProgramLayout();

/// The layout representing the structure of the program.
@override
abstract final StructLayout layout;

/// The instruction of the program.
abstract final dynamic instruction;
abstract final ProgramLayoutInstruction instruction;

/// Serializes the program.
@override
Expand All @@ -19,7 +24,8 @@ abstract class ProgramLayout implements LayoutSerializable {
/// Converts the program to bytes using Borsh serialization.
@override
List<int> toBytes() {
return layout.serialize({"instruction": instruction, ...serialize()});
return layout
.serialize({"instruction": instruction.insturction, ...serialize()});
}

/// Converts the program to a hexadecimal string.
Expand All @@ -43,20 +49,89 @@ abstract class ProgramLayout implements LayoutSerializable {
final decode = layout.deserialize(bytes).value;
if (instruction != null) {
if (decode["instruction"] != instruction) {
throw MessageException("invalid instruction index", details: {
throw SolanaPluginException("invalid instruction index", details: {
"excepted": instruction,
"instruction": decode["instruction"]
});
}
}
if (discriminator != null) {
if (decode["discriminator"] != discriminator) {
throw MessageException("invalid discriminator", details: {
throw SolanaPluginException("invalid discriminator", details: {
"excepted": discriminator,
"instruction": decode["discriminator"]
});
}
}
return decode;
}

factory ProgramLayout.fromBytes({
required SolAddress programId,
required List<int> instructionBytes,
}) {
try {
if (programId == AddressLookupTableProgramConst.programId) {
return AddressLookupTableProgramLayout.fromBytes(instructionBytes);
} else if (programId == ComputeBudgetConst.programId) {
return ComputeBudgetProgramLayout.fromBytes(instructionBytes);
} else if (programId == Ed25519ProgramConst.programId) {
return Ed25519ProgramLayout.fromBuffer(instructionBytes);
} else if (programId == MemoProgramConst.programId) {
return MemoLayout.fromBuffer(instructionBytes);
} else if (programId == NameServiceProgramConst.programId) {
return NameServiceProgramLayout.fromBytes(instructionBytes);
} else if (programId == Secp256k1ProgramConst.programId) {
return Secp256k1Layout.fromBuffer(instructionBytes);
} else if (programId == SPLTokenProgramConst.tokenProgramId ||
programId == SPLTokenProgramConst.token2022ProgramId) {
try {
return SPLTokenProgramLayout.fromBytes(instructionBytes);
} on UnimplementedError {
return SPLTokenMetaDataProgramLayout.fromBytes(instructionBytes);
}
} else if (programId == SPLTokenSwapConst.oldTokenSwapProgramId ||
programId == SPLTokenSwapConst.tokenSwapProgramId) {
} else if (programId == StakeProgramConst.programId) {
return StakeProgramLayout.fromBytes(instructionBytes);
} else if (programId == StakePoolProgramConst.programId) {
return StakePoolProgramLayout.fromBytes(instructionBytes);
} else if (programId == SystemProgramConst.programId) {
return SystemProgramLayout.fromBytes(instructionBytes);
} else if (programId == VoteProgramConst.programId) {
return VoteProgramLayout.fromBytes(instructionBytes);
} else if (programId == TokenLendingProgramConst.lendingProgramId) {
return TokenLendingProgramLayout.fromBytes(instructionBytes);
} else if (programId ==
AssociatedTokenAccountProgramConst.associatedTokenProgramId) {
return AssociatedTokenAccountProgramLayout.fromBytes(instructionBytes);
} else if (programId == MetaplexAuctionHouseProgramConst.programId) {
return MetaplexAuctionHouseProgramLayout.fromBytes(instructionBytes);
} else if (programId == MetaplexAuctioneerProgramConst.programId) {
return MetaplexAuctioneerProgramLayout.fromBytes(instructionBytes);
} else if (programId == MetaplexBubblegumProgramConst.programId) {
return MetaplexBubblegumProgramLayout.fromBytes(instructionBytes);
} else if (programId ==
MetaplexCandyMachineCoreProgramConst.candyMachineV3programId ||
programId ==
MetaplexCandyMachineCoreProgramConst.candyGuardProgramId) {
return MetaplexCandyMachineProgramLayout.fromBytes(instructionBytes);
} else if (programId == MetaplexFixedPriceSaleProgramConst.programId) {
return MetaplexFixedPriceSaleProgramLayout.fromBytes(instructionBytes);
} else if (programId == MetaplexGumdropProgramConst.programId) {
return MetaplexGumdropProgramLayout.fromBytes(instructionBytes);
} else if (programId == MetaplexHydraProgramConst.programId) {
return MetaplexHydraProgramLayout.fromBytes(instructionBytes);
} else if (programId == MetaplexNFTPacksProgramConst.programId) {
return MetaplexNFTPacksProgramLayout.fromBytes(instructionBytes);
} else if (programId == MetaplexTokenEntanglerProgramConst.programId) {
return MetaplexTokenEntanglerProgramLayout.fromBytes(instructionBytes);
} else if (programId == MetaplexTokenMetaDataProgramConst.programId) {
return MetaplexTokenMetaDataProgramLayout.fromBytes(instructionBytes);
}
return UnknownProgramLayout(instructionBytes);
} catch (e) {
return UnknownProgramLayout(instructionBytes);
}
}
}
21 changes: 21 additions & 0 deletions lib/solana/src/borsh_serialization/instuction/instuction.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,25 @@
import 'package:on_chain/solana/solana.dart';

abstract class ProgramLayoutInstruction {
abstract final dynamic insturction;
abstract final String name;
abstract final String programName;
abstract final SolAddress programAddress;
}

class UnknownProgramInstruction implements ProgramLayoutInstruction {
@override
final dynamic insturction;
@override
final String name;
const UnknownProgramInstruction(this.insturction, this.name);
static const UnknownProgramInstruction unknown =
UnknownProgramInstruction(null, "Unknown");

static const List<UnknownProgramInstruction> values = [unknown];

@override
String get programName => "Unknown";
@override
SolAddress get programAddress => throw UnimplementedError();
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import 'package:blockchain_utils/layout/layout.dart';
import 'package:blockchain_utils/utils/binary/utils.dart';
import 'package:on_chain/solana/src/borsh_serialization/core/program_layout.dart';
import 'package:on_chain/solana/src/borsh_serialization/instuction/instuction.dart';

/// Represents an unknown program layout.
class UnknownProgramLayout extends ProgramLayout {
/// The data of the unknown program layout.
const UnknownProgramLayout(this.data);
final List<int> data;
@override
get instruction => null;
UnknownProgramInstruction get instruction =>
UnknownProgramInstruction.unknown;

@override
StructLayout get layout =>
Expand All @@ -24,4 +27,11 @@ class UnknownProgramLayout extends ProgramLayout {
List<int> toBytes() {
return data;
}

@override
Map<String, dynamic> toJson() {
return {
"data": BytesUtils.toHexString(data, prefix: "0x"),
};
}
}
20 changes: 20 additions & 0 deletions lib/solana/src/exception/exception.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import 'package:blockchain_utils/exception/exceptions.dart';

class SolanaPluginException extends BlockchainUtilsException {
const SolanaPluginException(this.message, {this.details});

@override
final String message;

@override
final Map<String, dynamic>? details;

@override
String toString() {
String msg = message;
if (details?.isNotEmpty ?? false) {
msg += ' Details: $details';
}
return msg;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import 'package:blockchain_utils/exception/exception.dart';
import 'package:blockchain_utils/layout/layout.dart';
import 'package:on_chain/solana/src/address/sol_address.dart';
import 'package:on_chain/solana/src/exception/exception.dart';
import 'package:on_chain/solana/src/instructions/address_lockup_table/constant.dart';
import 'package:on_chain/solana/src/transaction/constant/solana_transaction_constant.dart';
import 'package:on_chain/solana/src/utils/layouts.dart';
Expand Down Expand Up @@ -38,7 +38,7 @@ class _Utils {
if (serializedAddressesLen < 0 ||
serializedAddressesLen % SolanaTransactionConstant.publicKeyLength !=
0) {
throw const MessageException("Lookup table is invalid");
throw const SolanaPluginException("Lookup table is invalid");
}
final meta = lookupTableMetaLayout.deserialize(accountData).value;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import 'package:on_chain/solana/src/address/sol_address.dart';
import 'package:on_chain/solana/src/borsh_serialization/program_layout.dart';
import 'package:on_chain/solana/src/instructions/address_lockup_table/constant.dart';

class AddressLookupTableProgramInstruction implements ProgramLayoutInstruction {
@override
Expand Down Expand Up @@ -30,4 +32,10 @@ class AddressLookupTableProgramInstruction implements ProgramLayoutInstruction {
return null;
}
}

@override
String get programName => "AddressLookupTable";

@override
SolAddress get programAddress => AddressLookupTableProgramConst.programId;
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import 'package:on_chain/solana/src/borsh_serialization/program_layout.dart';

abstract class AddressLookupTableProgramLayout extends ProgramLayout {
const AddressLookupTableProgramLayout();
@override
ProgramLayoutInstruction get instruction;
static final StructLayout _layout =
LayoutConst.struct([LayoutConst.u32(property: "instruction")]);
static ProgramLayout fromBytes(List<int> data) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,16 @@ class AddressLookupCloseLookupTableLayout
StructLayout get layout => _layout;

@override
int get instruction =>
AddressLookupTableProgramInstruction.closeLookupTable.insturction;
AddressLookupTableProgramInstruction get instruction =>
AddressLookupTableProgramInstruction.closeLookupTable;

@override
Map<String, dynamic> serialize() {
return {};
}

@override
Map<String, dynamic> toJson() {
return {};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ class AddressLookupCreateLookupTableLayout
StructLayout get layout => _layout;

@override
int get instruction =>
AddressLookupTableProgramInstruction.createLookupTable.insturction;
AddressLookupTableProgramInstruction get instruction =>
AddressLookupTableProgramInstruction.createLookupTable;

@override
Map<String, dynamic> serialize() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ class AddressLookupDeactiveLookupTableLayout
StructLayout get layout => _layout;

@override
int get instruction =>
AddressLookupTableProgramInstruction.deactivateLookupTable.insturction;
AddressLookupTableProgramInstruction get instruction =>
AddressLookupTableProgramInstruction.deactivateLookupTable;

@override
Map<String, dynamic> serialize() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,16 @@ class AddressExtendLookupTableLayout extends AddressLookupTableProgramLayout {
StructLayout get layout => _layout;

@override
int get instruction =>
AddressLookupTableProgramInstruction.extendLookupTable.insturction;
AddressLookupTableProgramInstruction get instruction =>
AddressLookupTableProgramInstruction.extendLookupTable;

@override
Map<String, dynamic> serialize() {
return {"addresses": addresses};
}

@override
Map<String, dynamic> toJson() {
return {"addresses": addresses.map((e) => e.address).toList()};
}
}
Loading

0 comments on commit bcb3f29

Please sign in to comment.