Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Value representation overhaul #70

Merged
merged 1 commit into from
Mar 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 28 additions & 19 deletions Stellar-contract-spec.x
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,23 @@ enum SCSpecType
SC_SPEC_TYPE_VAL = 0,

// Types with no parameters.
SC_SPEC_TYPE_U32 = 1,
SC_SPEC_TYPE_I32 = 2,
SC_SPEC_TYPE_U64 = 3,
SC_SPEC_TYPE_I64 = 4,
SC_SPEC_TYPE_U128 = 5,
SC_SPEC_TYPE_I128 = 6,
SC_SPEC_TYPE_BOOL = 7,
SC_SPEC_TYPE_SYMBOL = 8,
SC_SPEC_TYPE_BITSET = 9,
SC_SPEC_TYPE_STATUS = 10,
SC_SPEC_TYPE_BYTES = 11,
SC_SPEC_TYPE_INVOKER = 12,
SC_SPEC_TYPE_ADDRESS = 13,
SC_SPEC_TYPE_BOOL = 1,
SC_SPEC_TYPE_VOID = 2,
SC_SPEC_TYPE_STATUS = 3,
SC_SPEC_TYPE_U32 = 4,
SC_SPEC_TYPE_I32 = 5,
SC_SPEC_TYPE_U64 = 6,
SC_SPEC_TYPE_I64 = 7,
graydon marked this conversation as resolved.
Show resolved Hide resolved
SC_SPEC_TYPE_TIMEPOINT = 8,
SC_SPEC_TYPE_DURATION = 9,
SC_SPEC_TYPE_U128 = 10,
SC_SPEC_TYPE_I128 = 11,
SC_SPEC_TYPE_U256 = 12,
SC_SPEC_TYPE_I256 = 13,
SC_SPEC_TYPE_BYTES = 14,
SC_SPEC_TYPE_STRING = 16,
SC_SPEC_TYPE_SYMBOL = 17,
SC_SPEC_TYPE_ADDRESS = 19,

// Types with parameters.
SC_SPEC_TYPE_OPTION = 1000,
Expand Down Expand Up @@ -89,17 +93,22 @@ struct SCSpecTypeUDT
union SCSpecTypeDef switch (SCSpecType type)
{
case SC_SPEC_TYPE_VAL:
case SC_SPEC_TYPE_BOOL:
case SC_SPEC_TYPE_VOID:
case SC_SPEC_TYPE_STATUS:
case SC_SPEC_TYPE_U32:
case SC_SPEC_TYPE_I32:
case SC_SPEC_TYPE_U64:
case SC_SPEC_TYPE_I64:
case SC_SPEC_TYPE_TIMEPOINT:
case SC_SPEC_TYPE_DURATION:
case SC_SPEC_TYPE_U128:
case SC_SPEC_TYPE_I128:
case SC_SPEC_TYPE_U32:
case SC_SPEC_TYPE_I32:
case SC_SPEC_TYPE_BOOL:
case SC_SPEC_TYPE_SYMBOL:
case SC_SPEC_TYPE_BITSET:
case SC_SPEC_TYPE_STATUS:
case SC_SPEC_TYPE_U256:
case SC_SPEC_TYPE_I256:
case SC_SPEC_TYPE_BYTES:
case SC_SPEC_TYPE_STRING:
case SC_SPEC_TYPE_SYMBOL:
case SC_SPEC_TYPE_ADDRESS:
void;
case SC_SPEC_TYPE_OPTION:
Expand Down
285 changes: 151 additions & 134 deletions Stellar-contract.x
Original file line number Diff line number Diff line change
Expand Up @@ -5,67 +5,73 @@
% #include "xdr/Stellar-types.h"
namespace stellar
{
/*
* Smart Contracts deal in SCVals. These are a (dynamic) disjoint union
* between several possible variants, to allow storing generic SCVals in
* generic data structures and passing them in and out of languages that
* have simple or dynamic type systems.
*
* SCVals are (in WASM's case) stored in a tagged 64-bit word encoding. Most
* signed 64-bit values in Stellar are actually signed positive values
* (sequence numbers, timestamps, amounts), so we don't need the high bit
* and can get away with 1-bit tagging and store them as "unsigned 63bit",
* (u63) separate from everything else.
*
* We actually reserve the low _four_ bits, leaving 3 bits for 8 cases of
* "non-u63 values", some of which have substructure of their own.
*
* 0x_NNNN_NNNN_NNNN_NNNX - u63, for any even X
* 0x_0000_000N_NNNN_NNN1 - u32
* 0x_0000_000N_NNNN_NNN3 - i32
* 0x_NNNN_NNNN_NNNN_NNN5 - static: void, true, false, ... (SCS_*)
* 0x_IIII_IIII_TTTT_TTT7 - object: 32-bit index I, 28-bit type code T
* 0x_NNNN_NNNN_NNNN_NNN9 - symbol: up to 10 6-bit identifier characters
* 0x_NNNN_NNNN_NNNN_NNNb - bitset: up to 60 bits
* 0x_CCCC_CCCC_TTTT_TTTd - status: 32-bit code C, 28-bit type code T
* 0x_NNNN_NNNN_NNNN_NNNf - reserved
*
* Up here in XDR we have variable-length tagged disjoint unions but no
* bit-level packing, so we can be more explicit in their structure, at the
* cost of spending more than 64 bits to encode many cases, and also having
* to convert. It's a little non-obvious at the XDR level why there's a
* split between SCVal and SCObject given that they are both immutable types
* with value semantics; but the split reflects the split that happens in
* the implementation, and marks a place where different implementations of
* immutability (CoW, structural sharing, etc.) will likely occur.
*/

// A symbol is up to 10 chars drawn from [a-zA-Z0-9_], which can be packed
// into 60 bits with a 6-bit-per-character code, usable as a small key type
// to specify function, argument, tx-local environment and map entries
// efficiently.
typedef string SCSymbol<10>;

// We fix a maximum of 128 value types in the system for two reasons: we want to
// keep the codes relatively small (<= 8 bits) when bit-packing values into a
// u64 at the environment interface level, so that we keep many bits for
// payloads (small strings, small numeric values, object handles); and then we
// actually want to go one step further and ensure (for code-size) that our
// codes fit in a single ULEB128-code byte, which means we can only use 7 bits.
//
// We also reserve several type codes from this space because we want to _reuse_
// the SCValType codes at the environment interface level (or at least not
// exceed its number-space) but there are more types at that level, assigned to
// optimizations/special case representations of values abstract at this level.

enum SCValType
{
SCV_U63 = 0,
SCV_U32 = 1,
SCV_I32 = 2,
SCV_STATIC = 3,
SCV_OBJECT = 4,
SCV_SYMBOL = 5,
SCV_BITSET = 6,
SCV_STATUS = 7
};
SCV_BOOL = 0,
SCV_VOID = 1,
SCV_STATUS = 2,

% struct SCObject;
// 32 bits is the smallest type in WASM or XDR; no need for u8/u16.
SCV_U32 = 3,
SCV_I32 = 4,

enum SCStatic
{
SCS_VOID = 0,
SCS_TRUE = 1,
SCS_FALSE = 2,
SCS_LEDGER_KEY_CONTRACT_CODE = 3
// 64 bits is naturally supported by both WASM and XDR also.
SCV_U64 = 5,
SCV_I64 = 6,

// Time-related u64 subtypes with their own functions and formatting.
SCV_TIMEPOINT = 7,
SCV_DURATION = 8,

// 128 bits is naturally supported by Rust and we use it for Soroban
// fixed-point arithmetic prices / balances / similar "quantities". These
// are represented in XDR as a pair of 2 u64s, unlike {u,i}256 which is
// represented as an array of 32 bytes.
SCV_U128 = 9,
SCV_I128 = 10,

// 256 bits is the size of sha256 output, ed25519 keys, and the EVM machine
// word, so for interop use we include this even though it requires a small
// amount of Rust guest and/or host library code.
SCV_U256 = 11,
SCV_I256 = 12,

// TODO: possibly allocate subtypes of i64, i128 and/or u256 for
// fixed-precision with a specific number of decimals.

// Bytes come in 3 flavors, 2 of which have meaningfully different
// formatting and validity-checking / domain-restriction.
SCV_BYTES = 13,
SCV_STRING = 14,
SCV_SYMBOL = 15,

// Vecs and maps are just polymorphic containers of other ScVals.
SCV_VEC = 16,
SCV_MAP = 17,

// SCContractExecutable and SCAddressType are types that gets used separately from
// SCVal so we do not flatten their structures into separate SCVal cases.
SCV_CONTRACT_EXECUTABLE = 18,
SCV_ADDRESS = 19,

// SCV_LEDGER_KEY_CONTRACT_EXECUTABLE and SCV_LEDGER_KEY_NONCE are unique
// symbolic SCVals used as the key for ledger entries for a contract's code
// and an address' nonce, respectively.
SCV_LEDGER_KEY_CONTRACT_EXECUTABLE = 20,
SCV_LEDGER_KEY_NONCE = 21
};

enum SCStatusType
Expand Down Expand Up @@ -195,78 +201,28 @@ case SST_HOST_AUTH_ERROR:
SCHostAuthErrorCode authCode;
};

union SCVal switch (SCValType type)
{
case SCV_U63:
int64 u63;
case SCV_U32:
uint32 u32;
case SCV_I32:
int32 i32;
case SCV_STATIC:
SCStatic ic;
case SCV_OBJECT:
SCObject* obj;
case SCV_SYMBOL:
SCSymbol sym;
case SCV_BITSET:
uint64 bits;
case SCV_STATUS:
SCStatus status;
};

enum SCObjectType
{
// We have a few objects that represent non-stellar-specific concepts
// like general-purpose maps, vectors, numbers, blobs.

SCO_VEC = 0,
SCO_MAP = 1,
SCO_U64 = 2,
SCO_I64 = 3,
SCO_U128 = 4,
SCO_I128 = 5,
SCO_BYTES = 6,
SCO_CONTRACT_CODE = 7,
SCO_ADDRESS = 8,
SCO_NONCE_KEY = 9

// TODO: add more
};

struct SCMapEntry
{
SCVal key;
SCVal val;
struct Int128Parts {
// Both signed and unsigned 128-bit ints
// are transported in a pair of uint64s
// to reduce the risk of sign-extension.
uint64 lo;
uint64 hi;
};

const SCVAL_LIMIT = 256000;

typedef SCVal SCVec<SCVAL_LIMIT>;
typedef SCMapEntry SCMap<SCVAL_LIMIT>;

enum SCContractCodeType
enum SCContractExecutableType
{
SCCONTRACT_CODE_WASM_REF = 0,
SCCONTRACT_CODE_TOKEN = 1
SCCONTRACT_EXECUTABLE_WASM_REF = 0,
SCCONTRACT_EXECUTABLE_TOKEN = 1
};

union SCContractCode switch (SCContractCodeType type)
union SCContractExecutable switch (SCContractExecutableType type)
{
case SCCONTRACT_CODE_WASM_REF:
case SCCONTRACT_EXECUTABLE_WASM_REF:
Hash wasm_id;
case SCCONTRACT_CODE_TOKEN:
case SCCONTRACT_EXECUTABLE_TOKEN:
void;
};

struct Int128Parts {
// Both signed and unsigned 128-bit ints
// are transported in a pair of uint64s
// to reduce the risk of sign-extension.
uint64 lo;
uint64 hi;
};

enum SCAddressType
{
SC_ADDRESS_TYPE_ACCOUNT = 0,
Expand All @@ -281,27 +237,88 @@ case SC_ADDRESS_TYPE_CONTRACT:
Hash contractId;
};

union SCObject switch (SCObjectType type)
%struct SCVal;
%struct SCMapEntry;

const SCVAL_LIMIT = 256000;
const SCSYMBOL_LIMIT = 32;

typedef SCVal SCVec<SCVAL_LIMIT>;
typedef SCMapEntry SCMap<SCVAL_LIMIT>;

typedef opaque SCBytes<SCVAL_LIMIT>;
typedef string SCString<SCVAL_LIMIT>;
typedef string SCSymbol<SCSYMBOL_LIMIT>;

struct SCNonceKey {
SCAddress nonce_address;
};

union SCVal switch (SCValType type)
{
case SCO_VEC:
SCVec vec;
case SCO_MAP:
SCMap map;
case SCO_U64:

case SCV_BOOL:
bool b;
case SCV_VOID:
void;
case SCV_STATUS:
SCStatus error;
graydon marked this conversation as resolved.
Show resolved Hide resolved

case SCV_U32:
uint32 u32;
case SCV_I32:
int32 i32;

case SCV_U64:
uint64 u64;
case SCO_I64:
case SCV_I64:
int64 i64;
case SCO_U128:
case SCV_TIMEPOINT:
TimePoint timepoint;
case SCV_DURATION:
Duration duration;

case SCV_U128:
Int128Parts u128;
case SCO_I128:
case SCV_I128:
Int128Parts i128;
case SCO_BYTES:
opaque bin<SCVAL_LIMIT>;
case SCO_CONTRACT_CODE:
SCContractCode contractCode;
case SCO_ADDRESS:

case SCV_U256:
uint256 u256;
case SCV_I256:
uint256 i256;

case SCV_BYTES:
SCBytes bytes;
case SCV_STRING:
SCString str;
case SCV_SYMBOL:
SCSymbol sym;

// Vec and Map are recursive so need to live
// behind an option, due to xdrpp limitations.
case SCV_VEC:
SCVec *vec;
case SCV_MAP:
SCMap *map;

case SCV_CONTRACT_EXECUTABLE:
SCContractExecutable exec;
case SCV_ADDRESS:
SCAddress address;
case SCO_NONCE_KEY:
SCAddress nonceAddress;

// Special SCVals reserved for system-constructed contract-data
// ledger keys, not generally usable elsewhere.
case SCV_LEDGER_KEY_CONTRACT_EXECUTABLE:
void;
case SCV_LEDGER_KEY_NONCE:
SCNonceKey nonce_key;
};

struct SCMapEntry
{
SCVal key;
SCVal val;
};

}
Loading