Skip to content

Commit

Permalink
soroban contract parser & xdr contract spec fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
christian-rogobete committed Sep 5, 2024
1 parent eb7f708 commit 9184d7b
Show file tree
Hide file tree
Showing 6 changed files with 609 additions and 13 deletions.
220 changes: 220 additions & 0 deletions lib/src/soroban/soroban_contract_parser.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
// Copyright 2024 The Stellar Flutter SDK Authors. All rights reserved.
// Use of this source code is governed by a license that can be
// found in the LICENSE file.

import 'dart:typed_data';

import 'package:stellar_flutter_sdk/src/xdr/xdr_contract.dart';
import 'package:stellar_flutter_sdk/src/xdr/xdr_data_io.dart';

/// Parses a soroban contract byte code to get Environment Meta, Contract Spec and Contract Meta.
/// see: https://developers.stellar.org/docs/tools/sdks/build-your-own
class SorobanContractParser {
/// Parses a soroban contract [byteCode] to get Environment Meta, Contract Spec and Contract Meta.
/// see: https://developers.stellar.org/docs/tools/sdks/build-your-own
/// Returns [SorobanContractInfo] containing the parsed data.
/// Throws [SorobanContractParserFailed] if any exception occurred during the byte code parsing. E.g. invalid byte code.
static SorobanContractInfo parseContractByteCode(Uint8List byteCode) {
String bytesString = new String.fromCharCodes(byteCode);
var xdrEnvMeta = _parseEnvironmentMeta(bytesString);
if (xdrEnvMeta == null || xdrEnvMeta.interfaceVersion == null) {
throw SorobanContractParserFailed(
'invalid byte code: environment meta not found.');
}
var specEntries = _parseContractSpec(bytesString);
if (specEntries == null) {
throw SorobanContractParserFailed(
'invalid byte code: spec entries not found.');
}
var metaEntries = _parseMeta(bytesString);

return SorobanContractInfo(
xdrEnvMeta.interfaceVersion!.uint64, specEntries, metaEntries);
}

static XdrSCEnvMetaEntry? _parseEnvironmentMeta(String bytesString) {
try {
var metaEnvEntryBytes = _extractStringBetween(
bytesString, 'contractenvmetav0', 'contractmetav0');
if (metaEnvEntryBytes == null) {
metaEnvEntryBytes = _extractStringBetween(
bytesString, 'contractenvmetav0', 'contractspecv0');
}
if (metaEnvEntryBytes == null) {
metaEnvEntryBytes =
_extractStringToEnd(bytesString, 'contractenvmetav0');
}

if (metaEnvEntryBytes == null) {
return null;
}

var bytes = new Uint8List.fromList(metaEnvEntryBytes.codeUnits);
return XdrSCEnvMetaEntry.decode(XdrDataInputStream(bytes));
} on Exception catch (_) {
return null;
} on Error catch (_) {
return null;
}
}

static List<XdrSCSpecEntry>? _parseContractSpec(String bytesString) {
var specBytesString = _extractStringBetween(
bytesString, 'contractspecv0', 'contractenvmetav0');
if (specBytesString == null) {
specBytesString = _extractStringBetween(
bytesString, 'contractspecv0', 'contractspecv0');
}
if (specBytesString == null) {
specBytesString = _extractStringToEnd(bytesString, 'contractspecv0');
}

if (specBytesString == null) {
return null;
}
List<XdrSCSpecEntry> result = List<XdrSCSpecEntry>.empty(growable: true);

while (specBytesString!.length > 0) {
try {
var bytes = new Uint8List.fromList(specBytesString.codeUnits);
var entry = XdrSCSpecEntry.decode(XdrDataInputStream(bytes));
if (entry.discriminant ==
XdrSCSpecEntryKind.SC_SPEC_ENTRY_FUNCTION_V0 ||
entry.discriminant ==
XdrSCSpecEntryKind.SC_SPEC_ENTRY_UDT_STRUCT_V0 ||
entry.discriminant ==
XdrSCSpecEntryKind.SC_SPEC_ENTRY_UDT_UNION_V0 ||
entry.discriminant ==
XdrSCSpecEntryKind.SC_SPEC_ENTRY_UDT_ENUM_V0 ||
entry.discriminant ==
XdrSCSpecEntryKind.SC_SPEC_ENTRY_UDT_ERROR_ENUM_V0) {
result.add(entry);
XdrDataOutputStream xdrOutputStream = XdrDataOutputStream();
XdrSCSpecEntry.encode(xdrOutputStream, entry);
var entryBytes = xdrOutputStream.bytes;
var entryBytesString = new String.fromCharCodes(entryBytes);
var specBytesStrOrNull =
_extractStringToEnd(specBytesString, entryBytesString);
if (specBytesStrOrNull == null) {
break;
}
specBytesString = specBytesStrOrNull;
} else {
break;
}
} on Exception catch (_) {
break;
} on Error catch (_) {
break;
}
}
return result;
}

static Map<String, String> _parseMeta(String bytesString) {
var metaBytesString = _extractStringBetween(
bytesString, 'contractmetav0', 'contractenvmetav0');
if (metaBytesString == null) {
metaBytesString = _extractStringBetween(
bytesString, 'contractmetav0', 'contractspecv0');
}
if (metaBytesString == null) {
metaBytesString = _extractStringToEnd(bytesString, 'contractmetav0');
}

Map<String, String> result = Map<String, String>();

if (metaBytesString == null) {
return result;
}

while (metaBytesString!.length > 0) {
try {
var bytes = new Uint8List.fromList(metaBytesString.codeUnits);
var entry = XdrSCMetaEntry.decode(XdrDataInputStream(bytes));
if (entry.discriminant == XdrSCMetaKind.SC_META_V0) {
result[entry.v0!.key] = entry.v0!.value;
XdrDataOutputStream xdrOutputStream = XdrDataOutputStream();
XdrSCMetaEntry.encode(xdrOutputStream, entry);
var entryBytes = xdrOutputStream.bytes;
var entryBytesString = new String.fromCharCodes(entryBytes);
var metaBytesStrOrNull =
_extractStringToEnd(metaBytesString, entryBytesString);
if (metaBytesStrOrNull == null) {
break;
}
metaBytesString = metaBytesStrOrNull;
} else {
break;
}
} on Exception catch (_) {
break;
} on Error catch (_) {
break;
}
}
return result;
}

static String? _extractStringBetween(
String input, String startSymbol, String endSymbol) {
int startIndex = input.indexOf(startSymbol);
int endIndex = input.indexOf(endSymbol, startIndex + startSymbol.length);

if (startIndex != -1 && endIndex != -1) {
return input.substring(startIndex + startSymbol.length, endIndex);
}

return null;
}

static String? _extractStringToEnd(String input, String startSymbol) {
int startIndex = input.indexOf(startSymbol);

if (startIndex != -1) {
return input.substring(startIndex + startSymbol.length);
}

return null;
}
}

/// Thrown if the [SorobanContractParser] failed parsing the given byte code.
class SorobanContractParserFailed implements Exception {
String _message;

SorobanContractParserFailed(this._message);

String toString() {
return _message;
}
}

/// Stores information parsed from a soroban contract byte code such as
/// Environment Meta, Contract Spec Entries and Contract Meta Entries.
/// See also: https://developers.stellar.org/docs/tools/sdks/build-your-own
class SorobanContractInfo {
/**
* Environment interface number from Environment Meta.
*/
int envInterfaceVersion;

/**
* Contract Spec Entries. There is a SCSpecEntry for every function, struct,
* and union exported by the contract.
*/
List<XdrSCSpecEntry> specEntries;

/**
* Contract Meta Entries. Key => Value pairs.
* Contracts may store any metadata in the entries that can be used by applications
* and tooling off-network.
*/
Map<String, String> metaEntries;

/**
* Constructor.
*/
SorobanContractInfo(
this.envInterfaceVersion, this.specEntries, this.metaEntries);
}
28 changes: 28 additions & 0 deletions lib/src/soroban/soroban_server.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import 'package:dio/io.dart';
import 'package:stellar_flutter_sdk/src/account.dart';
import 'package:stellar_flutter_sdk/src/key_pair.dart';
import 'package:stellar_flutter_sdk/src/responses/response.dart';
import 'package:stellar_flutter_sdk/src/soroban/soroban_contract_parser.dart';
import 'package:stellar_flutter_sdk/src/xdr/xdr_account.dart';
import 'package:stellar_flutter_sdk/src/xdr/xdr_type.dart';
import 'soroban_auth.dart';
Expand Down Expand Up @@ -215,6 +216,33 @@ class SorobanServer {
return null;
}

/// Loads contract source byte code for the given [contractId] and extracts
/// the information (Environment Meta, Contract Spec, Contract Meta).
/// Returns [SorobanContractInfo] or null if the contract was not found.
/// Throws [SorobanContractParserFailed] if parsing of the byte code failed.
Future<SorobanContractInfo?> loadContractInfoForContractId(
String contractId) async {
var contractCodeEntry = await loadContractCodeForContractId(contractId);
if (contractCodeEntry == null) {
return null;
}
var byteCode = contractCodeEntry.code.dataValue;
return SorobanContractParser.parseContractByteCode(byteCode);
}

/// Loads contract source byte code for the given [wasmId] and extracts
/// the information (Environment Meta, Contract Spec, Contract Meta).
/// Returns [SorobanContractInfo] null if the contract was not found.
/// Throws [SorobanContractParserFailed] if parsing of the byte code failed.
Future<SorobanContractInfo?> loadContractInfoForWasmId(String wasmId) async {
var contractCodeEntry = await loadContractCodeForWasmId(wasmId);
if (contractCodeEntry == null) {
return null;
}
var byteCode = contractCodeEntry.code.dataValue;
return SorobanContractParser.parseContractByteCode(byteCode);
}

/// General info about the currently configured network.
/// See: https://developers.stellar.org/network/soroban-rpc/api-reference/methods/getNetwork
Future<GetNetworkResponse> getNetwork() async {
Expand Down
103 changes: 90 additions & 13 deletions lib/src/xdr/xdr_contract.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1216,6 +1216,83 @@ class XdrSCEnvMetaEntry {
}
}

class XdrSCMetaV0 {
String _key;
String get key => this._key;
set key(String value) => this._key = value;

String _value;
String get value => this._value;
set value(String value) => this._value = value;

XdrSCMetaV0(this._key, this._value);

static void encode(XdrDataOutputStream stream, XdrSCMetaV0 encoded) {
stream.writeString(encoded.key);
stream.writeString(encoded.value);
}

static XdrSCMetaV0 decode(XdrDataInputStream stream) {
String key = stream.readString();
String value = stream.readString();
return XdrSCMetaV0(key, value);
}
}

class XdrSCMetaKind {
final _value;
const XdrSCMetaKind._internal(this._value);
toString() => 'SCMetaKind.$_value';
XdrSCMetaKind(this._value);
get value => this._value;

static const SC_META_V0 = const XdrSCMetaKind._internal(0);

static XdrSCMetaKind decode(XdrDataInputStream stream) {
int value = stream.readInt();
switch (value) {
case 0:
return SC_META_V0;
default:
throw Exception("Unknown enum value: $value");
}
}

static void encode(XdrDataOutputStream stream, XdrSCMetaKind value) {
stream.writeInt(value.value);
}
}

class XdrSCMetaEntry {
XdrSCMetaEntry(this._kind);
XdrSCMetaKind _kind;
XdrSCMetaKind get discriminant => this._kind;
set discriminant(XdrSCMetaKind value) => this._kind = value;

XdrSCMetaV0? _v0;
XdrSCMetaV0? get v0 => this._v0;
set v0(XdrSCMetaV0? value) => this._v0 = value;

static void encode(XdrDataOutputStream stream, XdrSCMetaEntry encoded) {
stream.writeInt(encoded.discriminant.value);
switch (encoded.discriminant) {
case XdrSCMetaKind.SC_META_V0:
XdrSCMetaV0.encode(stream, encoded.v0!);
break;
}
}

static XdrSCMetaEntry decode(XdrDataInputStream stream) {
XdrSCMetaEntry decoded = XdrSCMetaEntry(XdrSCMetaKind.decode(stream));
switch (decoded.discriminant) {
case XdrSCMetaKind.SC_META_V0:
decoded.v0 = XdrSCMetaV0.decode(stream);
break;
}
return decoded;
}
}

class XdrSCSpecTypeOption {
XdrSCSpecTypeDef _valueType;
XdrSCSpecTypeDef get valueType => this._valueType;
Expand Down Expand Up @@ -1688,33 +1765,33 @@ class XdrSCSpecUDTUnionCaseTupleV0 {
String get name => this._name;
set name(String value) => this._name = value;

XdrSCSpecTypeDef? _type;
XdrSCSpecTypeDef? get type => this._type;
set type(XdrSCSpecTypeDef? value) => this._type = value;
List<XdrSCSpecTypeDef> _type;
List<XdrSCSpecTypeDef> get type => this._type;
set type(List<XdrSCSpecTypeDef> value) => this._type = value;

XdrSCSpecUDTUnionCaseTupleV0(this._doc, this._name, this._type);

static void encode(
XdrDataOutputStream stream, XdrSCSpecUDTUnionCaseTupleV0 encoded) {
stream.writeString(encoded.doc);
stream.writeString(encoded.name);
if (encoded.type != null) {
stream.writeInt(1);
XdrSCSpecTypeDef.encode(stream, encoded.type!);
} else {
stream.writeInt(0);
int typeSize = encoded.type.length;
stream.writeInt(typeSize);
for (int i = 0; i < typeSize; i++) {
XdrSCSpecTypeDef.encode(stream, encoded.type[i]);
}
}

static XdrSCSpecUDTUnionCaseTupleV0 decode(XdrDataInputStream stream) {
String doc = stream.readString();
String name = stream.readString();
XdrSCSpecTypeDef? typ;
int typePresent = stream.readInt();
if (typePresent != 0) {
typ = XdrSCSpecTypeDef.decode(stream);
int typeSize = stream.readInt();
List<XdrSCSpecTypeDef> type =
List<XdrSCSpecTypeDef>.empty(growable: true);
for (int i = 0; i < typeSize; i++) {
type.add(XdrSCSpecTypeDef.decode(stream));
}
return XdrSCSpecUDTUnionCaseTupleV0(doc, name, typ);
return XdrSCSpecUDTUnionCaseTupleV0(doc, name, type);
}
}

Expand Down
Loading

0 comments on commit 9184d7b

Please sign in to comment.