@layerzerolabs/common-utils-stellar-contracts 0.2.122
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.toml +28 -0
- package/LICENSE +23 -0
- package/clippy.toml +7 -0
- package/package.json +43 -0
- package/rust-toolchain.toml +4 -0
- package/rustfmt.toml +15 -0
- package/src/auth.rs +48 -0
- package/src/buffer_reader.rs +194 -0
- package/src/buffer_writer.rs +138 -0
- package/src/bytes_ext.rs +18 -0
- package/src/errors.rs +82 -0
- package/src/lib.rs +20 -0
- package/src/multisig.rs +247 -0
- package/src/option_ext.rs +38 -0
- package/src/ownable.rs +227 -0
- package/src/rbac.rs +438 -0
- package/src/testing_utils.rs +180 -0
- package/src/tests/auth.rs +179 -0
- package/src/tests/buffer_reader.rs +969 -0
- package/src/tests/buffer_writer.rs +689 -0
- package/src/tests/bytes_ext.rs +160 -0
- package/src/tests/mod.rs +13 -0
- package/src/tests/multisig.rs +760 -0
- package/src/tests/option_ext.rs +18 -0
- package/src/tests/ownable.rs +822 -0
- package/src/tests/rbac.rs +559 -0
- package/src/tests/test_helper.rs +67 -0
- package/src/tests/testing_utils.rs +522 -0
- package/src/tests/ttl_configurable.rs +586 -0
- package/src/tests/ttl_extendable.rs +64 -0
- package/src/tests/upgradeable.rs +529 -0
- package/src/ttl_configurable.rs +169 -0
- package/src/ttl_extendable.rs +25 -0
- package/src/upgradeable.rs +103 -0
package/Cargo.toml
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Standalone framework crate (extracted from protocol-stellar-v2). Self-contained: explicit
|
|
2
|
+
# version/edition/dep versions (not workspace-inherited) so it builds outside the protocol workspace
|
|
3
|
+
# and vendors cleanly into consumers' dependencies/. Versions mirror the protocol's [workspace.dependencies].
|
|
4
|
+
# Proc-macro crate lives in sibling package @layerzerolabs/common-utils-macros-stellar-contracts.
|
|
5
|
+
[package]
|
|
6
|
+
name = "utils"
|
|
7
|
+
version = "0.0.1"
|
|
8
|
+
edition = "2021"
|
|
9
|
+
publish = false
|
|
10
|
+
|
|
11
|
+
[lib]
|
|
12
|
+
crate-type = ["rlib"]
|
|
13
|
+
doctest = false
|
|
14
|
+
|
|
15
|
+
[features]
|
|
16
|
+
# Forwards Soroban test APIs so enabling utils/testutils alone is self-contained
|
|
17
|
+
# for external consumers (no separate soroban-sdk/testutils required).
|
|
18
|
+
testutils = ["soroban-sdk/testutils"]
|
|
19
|
+
|
|
20
|
+
[dependencies]
|
|
21
|
+
# exact-pinned (=): a bare "25.1.1" is a caret range that lets fresh resolution drift to soroban-sdk's
|
|
22
|
+
# latest patch, which can raise its MSRV above the pinned rustc toolchain here (hit: 25.3.1 needs
|
|
23
|
+
# rustc 1.91, this crate builds with 1.90) -- exact-pin so that can never happen silently.
|
|
24
|
+
soroban-sdk = { version = "=25.1.1", features = ["hazmat-address", "hazmat-crypto"] }
|
|
25
|
+
common-macros = { path = "dependencies/common-utils-macros-stellar-contracts" }
|
|
26
|
+
|
|
27
|
+
[dev-dependencies]
|
|
28
|
+
soroban-sdk = { version = "=25.1.1", features = ["hazmat-address", "hazmat-crypto", "testutils"] }
|
package/LICENSE
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
Copyright (c) 2026 - LayerZero Labs Ltd.
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any
|
|
4
|
+
person obtaining a copy of this software and associated
|
|
5
|
+
documentation files (the "Software"), to deal in the
|
|
6
|
+
Software without restriction, including without
|
|
7
|
+
limitation the rights to use, copy, modify, merge,
|
|
8
|
+
publish, distribute, sublicense, and/or sell copies of
|
|
9
|
+
the Software, and to permit persons to whom the Software
|
|
10
|
+
is furnished to do so, subject to the following
|
|
11
|
+
conditions:
|
|
12
|
+
The above copyright notice and this permission notice
|
|
13
|
+
shall be included in all copies or substantial portions
|
|
14
|
+
of the Software.
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
|
16
|
+
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
|
17
|
+
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
|
18
|
+
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
|
19
|
+
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
20
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
|
22
|
+
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
23
|
+
DEALINGS IN THE SOFTWARE.
|
package/clippy.toml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Clippy configuration for Stellar protocol contracts
|
|
2
|
+
|
|
3
|
+
# Set the too_many_arguments threshold to 11.
|
|
4
|
+
# This aligns with Stellar: contract functions are limited to 10 parameters, plus 1 for 'env'.
|
|
5
|
+
# (Default is 7; raised here to prevent unnecessary clippy warnings on valid contract interfaces.)
|
|
6
|
+
too-many-arguments-threshold = 11
|
|
7
|
+
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@layerzerolabs/common-utils-stellar-contracts",
|
|
3
|
+
"version": "0.2.122",
|
|
4
|
+
"private": false,
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"files": [
|
|
7
|
+
"src",
|
|
8
|
+
"Cargo.toml",
|
|
9
|
+
"rust-toolchain.toml",
|
|
10
|
+
"rustfmt.toml",
|
|
11
|
+
"clippy.toml"
|
|
12
|
+
],
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@layerzerolabs/common-utils-macros-stellar-contracts": "0.2.122"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"@layerzerolabs/build-utils-rust": "0.2.122",
|
|
18
|
+
"@layerzerolabs/vm-tooling-stellar": "0.2.122"
|
|
19
|
+
},
|
|
20
|
+
"publishConfig": {
|
|
21
|
+
"access": "public",
|
|
22
|
+
"registry": "https://registry.npmjs.org/"
|
|
23
|
+
},
|
|
24
|
+
"externalRepoConfig": {
|
|
25
|
+
"targets": [
|
|
26
|
+
"audit-external",
|
|
27
|
+
"console-pen-test",
|
|
28
|
+
"monorepo-external",
|
|
29
|
+
"onesig-client"
|
|
30
|
+
]
|
|
31
|
+
},
|
|
32
|
+
"implicitDependencies": {
|
|
33
|
+
"@layerzerolabs/common-utils-macros-stellar-contracts": "workspace:*"
|
|
34
|
+
},
|
|
35
|
+
"scripts": {
|
|
36
|
+
"build": "pnpm resolve-dependencies && pnpm exec lz-tool --script \"cargo build\" stellar",
|
|
37
|
+
"clean": "rm -rf ./dependencies ./target ./node_modules",
|
|
38
|
+
"format": "pnpm exec lz-tool --script \"cargo fmt\" stellar",
|
|
39
|
+
"lint": "pnpm resolve-dependencies && pnpm exec lz-tool --script \"cargo clippy -- -D warnings\" stellar",
|
|
40
|
+
"resolve-dependencies": "pnpm exec build-utils-rust resolve",
|
|
41
|
+
"test": "pnpm resolve-dependencies && pnpm exec lz-tool --script \"cargo nextest run --features testutils\" stellar"
|
|
42
|
+
}
|
|
43
|
+
}
|
package/rustfmt.toml
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
format_macro_bodies = true
|
|
2
|
+
max_width = 120
|
|
3
|
+
format_macro_matchers = true
|
|
4
|
+
format_strings = true
|
|
5
|
+
imports_granularity = "Crate"
|
|
6
|
+
match_arm_blocks = false
|
|
7
|
+
reorder_impl_items = true
|
|
8
|
+
group_imports = "StdExternalCrate"
|
|
9
|
+
use_field_init_shorthand = true
|
|
10
|
+
use_small_heuristics = "Max"
|
|
11
|
+
wrap_comments = true
|
|
12
|
+
format_code_in_doc_comments = true
|
|
13
|
+
|
|
14
|
+
# most of these are unstable, so we enable them
|
|
15
|
+
unstable_features = true
|
package/src/auth.rs
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
//! Base authorization trait for owner-protected operations.
|
|
2
|
+
//!
|
|
3
|
+
//! The `Auth` trait provides a common interface for authorization that can be
|
|
4
|
+
//! implemented by different access control patterns (e.g., single owner, multisig).
|
|
5
|
+
|
|
6
|
+
use crate::{errors::AuthError, option_ext::OptionExt};
|
|
7
|
+
use soroban_sdk::{contractclient, Address, Env};
|
|
8
|
+
|
|
9
|
+
// ===========================================================================
|
|
10
|
+
// Auth trait
|
|
11
|
+
// ===========================================================================
|
|
12
|
+
|
|
13
|
+
/// Base trait for authorization.
|
|
14
|
+
///
|
|
15
|
+
/// Provides the authorizer address for owner-protected operations. This trait
|
|
16
|
+
/// is implemented by both `Ownable` (external owner) and `MultiSig` (self-owning).
|
|
17
|
+
#[contractclient(name = "AuthClient")]
|
|
18
|
+
pub trait Auth: Sized {
|
|
19
|
+
/// Returns the address that authorizes owner-protected operations.
|
|
20
|
+
///
|
|
21
|
+
/// For `Ownable` contracts, this returns the stored owner address.
|
|
22
|
+
/// For `MultiSig` contracts, this returns the contract's own address
|
|
23
|
+
/// (self-owning pattern).
|
|
24
|
+
///
|
|
25
|
+
/// Returns `None` when there is no authorizer (for example, when an Ownable
|
|
26
|
+
/// contract's owner has been renounced).
|
|
27
|
+
fn authorizer(env: &Env) -> Option<Address>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// ===========================================================================
|
|
31
|
+
// Auth helper functions
|
|
32
|
+
// ===========================================================================
|
|
33
|
+
|
|
34
|
+
/// Enforces authorization from the authorizer and returns the authorizer address.
|
|
35
|
+
///
|
|
36
|
+
/// Panics if the authorizer has not provided authorization for this invocation.
|
|
37
|
+
pub fn enforce_auth<T: Auth>(env: &Env) -> Address {
|
|
38
|
+
let authorizer = T::authorizer(env).unwrap_or_panic(env, AuthError::AuthorizerNotFound);
|
|
39
|
+
authorizer.require_auth();
|
|
40
|
+
authorizer
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/// Requires authorization from the authorizer.
|
|
44
|
+
///
|
|
45
|
+
/// Panics if the authorizer has not provided authorization for this invocation.
|
|
46
|
+
pub fn require_auth<T: Auth>(env: &Env) {
|
|
47
|
+
let _ = enforce_auth::<T>(env);
|
|
48
|
+
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
use crate::{
|
|
2
|
+
buffer_writer::{ACCOUNT_PAYLOAD_TYPE, CONTRACT_PAYLOAD_TYPE},
|
|
3
|
+
bytes_ext::BytesExt,
|
|
4
|
+
errors::BufferReaderError,
|
|
5
|
+
};
|
|
6
|
+
use core::mem;
|
|
7
|
+
use soroban_sdk::{
|
|
8
|
+
address_payload::AddressPayload, assert_with_error, panic_with_error, Address, Bytes, BytesN, Env, I256, U256,
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
/// Generates read methods for primitive integer types using big-endian byte order.
|
|
12
|
+
///
|
|
13
|
+
/// Each generated method reads a fixed number of bytes from the buffer, converts them
|
|
14
|
+
/// from big-endian format to the target integer type, and advances the read position.
|
|
15
|
+
///
|
|
16
|
+
/// # Syntax
|
|
17
|
+
/// ```ignore
|
|
18
|
+
/// impl_read_int!(method_name, Type; ...);
|
|
19
|
+
/// ```
|
|
20
|
+
macro_rules! impl_read_int {
|
|
21
|
+
($($method:ident, $type:ty);* $(;)?) => {
|
|
22
|
+
$(
|
|
23
|
+
pub fn $method(&mut self) -> $type {
|
|
24
|
+
<$type>::from_be_bytes(self.read_bytes(mem::size_of::<$type>() as u32).to_array())
|
|
25
|
+
}
|
|
26
|
+
)*
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/// A sequential reader for parsing binary data from a byte buffer.
|
|
31
|
+
///
|
|
32
|
+
/// Maintains a position cursor that advances as data is read.
|
|
33
|
+
/// All integer reads are big-endian.
|
|
34
|
+
///
|
|
35
|
+
/// # Example
|
|
36
|
+
/// ```ignore
|
|
37
|
+
/// let mut reader = BufferReader::new(&bytes);
|
|
38
|
+
/// let value = reader.read_u32();
|
|
39
|
+
/// let value = reader.read_bool();
|
|
40
|
+
/// let addr = reader.read_address();
|
|
41
|
+
/// ```
|
|
42
|
+
pub struct BufferReader<'a> {
|
|
43
|
+
buffer: &'a Bytes,
|
|
44
|
+
pos: u32,
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
impl<'a> BufferReader<'a> {
|
|
48
|
+
/// Creates a new reader starting at position 0.
|
|
49
|
+
pub fn new(buffer: &'a Bytes) -> Self {
|
|
50
|
+
Self { buffer, pos: 0 }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/// Sets the read position to an absolute byte offset.
|
|
54
|
+
pub fn seek(&mut self, pos: u32) -> &mut Self {
|
|
55
|
+
assert_with_error!(self.buffer.env(), pos <= self.buffer.len(), BufferReaderError::InvalidLength);
|
|
56
|
+
self.pos = pos;
|
|
57
|
+
self
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/// Advances the position by `len` bytes without reading.
|
|
61
|
+
pub fn skip(&mut self, len: u32) -> &mut Self {
|
|
62
|
+
self.seek(self.pos + len)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/// Moves the position backwards by `len` bytes.
|
|
66
|
+
pub fn rewind(&mut self, len: u32) -> &mut Self {
|
|
67
|
+
assert_with_error!(self.buffer.env(), self.pos >= len, BufferReaderError::InvalidLength);
|
|
68
|
+
self.pos -= len;
|
|
69
|
+
self
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Generates: read_u8 (1 byte), read_u16 (2 bytes), read_u32 (4 bytes), read_u64 (8 bytes), read_u128 (16 bytes)
|
|
73
|
+
impl_read_int!(read_u8, u8; read_u16, u16; read_u32, u32; read_u64, u64; read_u128, u128);
|
|
74
|
+
// Generate: read_i8 (1 byte), read_i16 (2 bytes), read_i32 (4 bytes), read_i64 (8 bytes), read_i128 (16 bytes)
|
|
75
|
+
impl_read_int!(read_i8, i8; read_i16, i16; read_i32, i32; read_i64, i64; read_i128, i128);
|
|
76
|
+
|
|
77
|
+
/// Reads a boolean (1 byte, non-zero = true).
|
|
78
|
+
pub fn read_bool(&mut self) -> bool {
|
|
79
|
+
self.read_u8() != 0
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/// Reads a U256 (32 bytes, big-endian).
|
|
83
|
+
pub fn read_u256(&mut self) -> U256 {
|
|
84
|
+
U256::from_be_bytes(self.buffer.env(), &self.read_bytes(32))
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/// Reads a I256 (32 bytes, big-endian).
|
|
88
|
+
pub fn read_i256(&mut self) -> I256 {
|
|
89
|
+
I256::from_be_bytes(self.buffer.env(), &self.read_bytes(32))
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/// Reads a Stellar address (33 bytes: 1 type + 32 payload).
|
|
93
|
+
pub fn read_address(&mut self) -> Address {
|
|
94
|
+
let payload_type = self.read_u8();
|
|
95
|
+
let payload = self.read_address_payload();
|
|
96
|
+
self.compose_address(payload_type, payload)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/// Reads an address payload only from the buffer (32 bytes).
|
|
100
|
+
pub fn read_address_payload(&mut self) -> BytesN<32> {
|
|
101
|
+
self.read_bytes_n()
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/// Reads N bytes as a fixed-size BytesN<N>.
|
|
105
|
+
pub fn read_bytes_n<const N: usize>(&mut self) -> BytesN<N> {
|
|
106
|
+
BytesN::<N>::from_array(self.buffer.env(), &self.read_array())
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
pub fn read_array<const N: usize>(&mut self) -> [u8; N] {
|
|
110
|
+
self.read_bytes(N as u32).to_array()
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/// Reads `len` bytes from current position and advances.
|
|
114
|
+
pub fn read_bytes(&mut self, len: u32) -> Bytes {
|
|
115
|
+
let end = self.pos + len;
|
|
116
|
+
assert_with_error!(self.buffer.env(), self.buffer.len() >= end, BufferReaderError::InvalidLength);
|
|
117
|
+
|
|
118
|
+
let value = self.buffer.slice(self.pos..end);
|
|
119
|
+
self.pos = end;
|
|
120
|
+
value
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/// Reads all bytes from current position to the end of the buffer.
|
|
124
|
+
pub fn read_bytes_until_end(&mut self) -> Bytes {
|
|
125
|
+
self.read_bytes(self.remaining_len())
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/// Returns the current read position.
|
|
129
|
+
pub fn position(&self) -> u32 {
|
|
130
|
+
self.pos
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/// Returns a reference to the underlying buffer.
|
|
134
|
+
pub fn buffer(&self) -> &Bytes {
|
|
135
|
+
self.buffer
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/// Returns the total length of the buffer.
|
|
139
|
+
pub fn len(&self) -> u32 {
|
|
140
|
+
self.buffer.len()
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/// Returns true if the buffer is empty.
|
|
144
|
+
pub fn is_empty(&self) -> bool {
|
|
145
|
+
self.buffer.is_empty()
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/// Returns the number of bytes remaining after current position.
|
|
149
|
+
pub fn remaining_len(&self) -> u32 {
|
|
150
|
+
self.buffer.len() - self.pos
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/// Returns the Soroban environment reference.
|
|
154
|
+
pub fn env(&self) -> &Env {
|
|
155
|
+
self.buffer.env()
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// ============================================================================================
|
|
159
|
+
// Internal Functions
|
|
160
|
+
// ============================================================================================
|
|
161
|
+
|
|
162
|
+
/// Compose an address from its type byte and 32-byte payload.
|
|
163
|
+
fn compose_address(&self, payload_type: u8, payload: BytesN<32>) -> Address {
|
|
164
|
+
let addr_payload = match payload_type {
|
|
165
|
+
ACCOUNT_PAYLOAD_TYPE => AddressPayload::AccountIdPublicKeyEd25519(payload),
|
|
166
|
+
CONTRACT_PAYLOAD_TYPE => AddressPayload::ContractIdHash(payload),
|
|
167
|
+
_ => panic_with_error!(self.buffer.env(), BufferReaderError::InvalidAddressPayload),
|
|
168
|
+
};
|
|
169
|
+
Address::from_payload(self.buffer.env(), addr_payload)
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
#[cfg(test)]
|
|
174
|
+
mod tests {
|
|
175
|
+
use super::*;
|
|
176
|
+
|
|
177
|
+
#[test]
|
|
178
|
+
fn test_size_of_unsigned_integers() {
|
|
179
|
+
assert_eq!(mem::size_of::<u8>(), 1);
|
|
180
|
+
assert_eq!(mem::size_of::<u16>(), 2);
|
|
181
|
+
assert_eq!(mem::size_of::<u32>(), 4);
|
|
182
|
+
assert_eq!(mem::size_of::<u64>(), 8);
|
|
183
|
+
assert_eq!(mem::size_of::<u128>(), 16);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
#[test]
|
|
187
|
+
fn test_size_of_signed_integers() {
|
|
188
|
+
assert_eq!(mem::size_of::<i8>(), 1);
|
|
189
|
+
assert_eq!(mem::size_of::<i16>(), 2);
|
|
190
|
+
assert_eq!(mem::size_of::<i32>(), 4);
|
|
191
|
+
assert_eq!(mem::size_of::<i64>(), 8);
|
|
192
|
+
assert_eq!(mem::size_of::<i128>(), 16);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
use crate::{errors::BufferWriterError, option_ext::OptionExt};
|
|
2
|
+
use soroban_sdk::{address_payload::AddressPayload, Address, Bytes, BytesN, Env, I256, U256};
|
|
3
|
+
|
|
4
|
+
/// Generates write methods for primitive integer types using big-endian byte order.
|
|
5
|
+
///
|
|
6
|
+
/// Each generated method writes a fixed number of bytes to the buffer, converts the
|
|
7
|
+
/// integer value to big-endian format, and advances the write position.
|
|
8
|
+
///
|
|
9
|
+
/// # Syntax
|
|
10
|
+
/// ```ignore
|
|
11
|
+
/// impl_write_int!(method_name, Type; ...);
|
|
12
|
+
/// ```
|
|
13
|
+
macro_rules! impl_write_int {
|
|
14
|
+
($($method:ident, $type:ty);* $(;)?) => {
|
|
15
|
+
$(
|
|
16
|
+
pub fn $method(&mut self, value: $type) -> &mut Self {
|
|
17
|
+
self.buffer.extend_from_slice(&value.to_be_bytes());
|
|
18
|
+
self
|
|
19
|
+
}
|
|
20
|
+
)*
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
pub const ACCOUNT_PAYLOAD_TYPE: u8 = 0;
|
|
25
|
+
pub const CONTRACT_PAYLOAD_TYPE: u8 = 1;
|
|
26
|
+
|
|
27
|
+
/// A writer for serializing data to a byte buffer.
|
|
28
|
+
///
|
|
29
|
+
/// The writer maintains an internal byte buffer and provides methods to write
|
|
30
|
+
/// various data types in big-endian format. All write operations return a
|
|
31
|
+
/// mutable reference to the writer, enabling method chaining.
|
|
32
|
+
///
|
|
33
|
+
/// # Example
|
|
34
|
+
/// ```ignore
|
|
35
|
+
/// let mut writer = BufferWriter::new(&env);
|
|
36
|
+
/// writer.write_u32(0x12345678)
|
|
37
|
+
/// .write_bool(true)
|
|
38
|
+
/// .write_bytes32(&bytes32);
|
|
39
|
+
/// let bytes = writer.to_bytes();
|
|
40
|
+
/// ```
|
|
41
|
+
pub struct BufferWriter {
|
|
42
|
+
buffer: Bytes,
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
impl BufferWriter {
|
|
46
|
+
/// Create a new empty writer.
|
|
47
|
+
pub fn new(env: &Env) -> Self {
|
|
48
|
+
Self { buffer: Bytes::new(env) }
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/// Create a new writer initialized with existing data.
|
|
52
|
+
pub fn from_bytes(buffer: Bytes) -> Self {
|
|
53
|
+
Self { buffer }
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Generates: write_u8 (1 byte), write_u16 (2 bytes), write_u32 (4 bytes), write_u64 (8 bytes), write_u128 (16 bytes)
|
|
57
|
+
impl_write_int!(write_u8, u8; write_u16, u16; write_u32, u32; write_u64, u64; write_u128, u128);
|
|
58
|
+
// Generate: write_i8 (1 byte), write_i16 (2 bytes), write_i32 (4 bytes), write_i64 (8 bytes), write_i128 (16 bytes)
|
|
59
|
+
impl_write_int!(write_i8, i8; write_i16, i16; write_i32, i32; write_i64, i64; write_i128, i128);
|
|
60
|
+
|
|
61
|
+
/// Write a boolean value to the buffer (true as 1, false as 0).
|
|
62
|
+
pub fn write_bool(&mut self, value: bool) -> &mut Self {
|
|
63
|
+
self.write_u8(if value { 1 } else { 0 })
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/// Write an unsigned 256-bit integer in big-endian format.
|
|
67
|
+
pub fn write_u256(&mut self, value: U256) -> &mut Self {
|
|
68
|
+
self.buffer.append(&value.to_be_bytes());
|
|
69
|
+
self
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/// Write a signed 256-bit integer in big-endian format.
|
|
73
|
+
pub fn write_i256(&mut self, value: I256) -> &mut Self {
|
|
74
|
+
self.buffer.append(&value.to_be_bytes());
|
|
75
|
+
self
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/// Write an address type and payload to the buffer (33 bytes: 1 type + 32 payload).
|
|
79
|
+
pub fn write_address(&mut self, address: &Address) -> &mut Self {
|
|
80
|
+
let (payload_type, payload) = self.decompose_address(address);
|
|
81
|
+
self.write_u8(payload_type).write_bytes_n(&payload)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/// Write an address payload only to the buffer (32 bytes).
|
|
85
|
+
pub fn write_address_payload(&mut self, address: &Address) -> &mut Self {
|
|
86
|
+
let (_, payload) = self.decompose_address(address);
|
|
87
|
+
self.write_bytes_n(&payload)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/// Write a byte slice to the buffer (appends without length prefix).
|
|
91
|
+
pub fn write_bytes(&mut self, bytes: &Bytes) -> &mut Self {
|
|
92
|
+
self.buffer.append(bytes);
|
|
93
|
+
self
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/// Write a fixed-size BytesN<N> to the buffer.
|
|
97
|
+
pub fn write_bytes_n<const N: usize>(&mut self, bytes_n: &BytesN<N>) -> &mut Self {
|
|
98
|
+
self.buffer.extend_from_array(&bytes_n.to_array());
|
|
99
|
+
self
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
pub fn write_array<const N: usize>(&mut self, array: &[u8; N]) -> &mut Self {
|
|
103
|
+
self.buffer.extend_from_array(array);
|
|
104
|
+
self
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/// Get the complete buffer as a Bytes.
|
|
108
|
+
pub fn to_bytes(&self) -> Bytes {
|
|
109
|
+
self.buffer.clone()
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/// Get the current length of the buffer.
|
|
113
|
+
pub fn len(&self) -> u32 {
|
|
114
|
+
self.buffer.len()
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/// Check if the buffer is empty.
|
|
118
|
+
pub fn is_empty(&self) -> bool {
|
|
119
|
+
self.buffer.is_empty()
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/// Get the environment from the buffer.
|
|
123
|
+
pub fn env(&self) -> &Env {
|
|
124
|
+
self.buffer.env()
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// ============================================================================================
|
|
128
|
+
// Internal Functions
|
|
129
|
+
// ============================================================================================
|
|
130
|
+
|
|
131
|
+
/// Decompose an address into its type byte and 32-byte payload.
|
|
132
|
+
fn decompose_address(&self, address: &Address) -> (u8, BytesN<32>) {
|
|
133
|
+
match address.to_payload().unwrap_or_panic(self.buffer.env(), BufferWriterError::InvalidAddressPayload) {
|
|
134
|
+
AddressPayload::AccountIdPublicKeyEd25519(p) => (ACCOUNT_PAYLOAD_TYPE, p),
|
|
135
|
+
AddressPayload::ContractIdHash(p) => (CONTRACT_PAYLOAD_TYPE, p),
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
package/src/bytes_ext.rs
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
use crate::errors::BytesExtError;
|
|
2
|
+
use soroban_sdk::{assert_with_error, Bytes};
|
|
3
|
+
|
|
4
|
+
/// Extension trait for `Bytes` to convert to fixed-size arrays
|
|
5
|
+
pub trait BytesExt {
|
|
6
|
+
/// Copies the entire bytes into a fixed-size array.
|
|
7
|
+
/// Panics if bytes length != N.
|
|
8
|
+
fn to_array<const N: usize>(&self) -> [u8; N];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
impl BytesExt for Bytes {
|
|
12
|
+
fn to_array<const N: usize>(&self) -> [u8; N] {
|
|
13
|
+
assert_with_error!(self.env(), self.len() == N as u32, BytesExtError::LengthMismatch);
|
|
14
|
+
let mut buf = [0u8; N];
|
|
15
|
+
self.copy_into_slice(&mut buf);
|
|
16
|
+
buf
|
|
17
|
+
}
|
|
18
|
+
}
|
package/src/errors.rs
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
use common_macros::contract_error;
|
|
2
|
+
|
|
3
|
+
// Utils library error codes: 1000-1099
|
|
4
|
+
// See docs/error-spec.md for allocation rules
|
|
5
|
+
|
|
6
|
+
/// BufferReaderError: 1000-1009
|
|
7
|
+
#[contract_error]
|
|
8
|
+
pub enum BufferReaderError {
|
|
9
|
+
InvalidLength = 1000,
|
|
10
|
+
InvalidAddressPayload,
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/// BufferWriterError: 1010-1019
|
|
14
|
+
#[contract_error]
|
|
15
|
+
pub enum BufferWriterError {
|
|
16
|
+
InvalidAddressPayload = 1010,
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/// TtlConfigurableError: 1020-1029
|
|
20
|
+
#[contract_error]
|
|
21
|
+
pub enum TtlConfigurableError {
|
|
22
|
+
InvalidTtlConfig = 1020,
|
|
23
|
+
TtlConfigFrozen,
|
|
24
|
+
TtlConfigAlreadyFrozen,
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/// OwnableError: 1030-1039
|
|
28
|
+
#[contract_error]
|
|
29
|
+
pub enum OwnableError {
|
|
30
|
+
InvalidAuthorizer = 1030,
|
|
31
|
+
InvalidPendingOwner,
|
|
32
|
+
InvalidTtl,
|
|
33
|
+
NoPendingTransfer,
|
|
34
|
+
OwnerAlreadySet,
|
|
35
|
+
OwnerNotSet,
|
|
36
|
+
TransferInProgress,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/// BytesExtError: 1040-1049
|
|
40
|
+
#[contract_error]
|
|
41
|
+
pub enum BytesExtError {
|
|
42
|
+
LengthMismatch = 1040,
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/// UpgradeableError: 1050-1059
|
|
46
|
+
#[contract_error]
|
|
47
|
+
pub enum UpgradeableError {
|
|
48
|
+
InvalidMigrationData = 1050,
|
|
49
|
+
MigrationNotAllowed,
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/// MultiSigError: 1060-1069
|
|
53
|
+
#[contract_error]
|
|
54
|
+
pub enum MultiSigError {
|
|
55
|
+
AlreadyInitialized = 1060,
|
|
56
|
+
InvalidAuthorizer,
|
|
57
|
+
InvalidSigner,
|
|
58
|
+
SignatureError,
|
|
59
|
+
SignerAlreadyExists,
|
|
60
|
+
SignerNotFound,
|
|
61
|
+
TotalSignersLessThanThreshold,
|
|
62
|
+
UnsortedSigners,
|
|
63
|
+
ZeroThreshold,
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/// AuthError: 1070-1079
|
|
67
|
+
#[contract_error]
|
|
68
|
+
pub enum AuthError {
|
|
69
|
+
AuthorizerNotFound = 1070,
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/// RbacError: 1080-1089
|
|
73
|
+
#[contract_error]
|
|
74
|
+
pub enum RbacError {
|
|
75
|
+
AdminRoleNotFound = 1080,
|
|
76
|
+
IndexOutOfBounds,
|
|
77
|
+
MaxRolesExceeded,
|
|
78
|
+
RoleIsEmpty,
|
|
79
|
+
RoleNotFound,
|
|
80
|
+
RoleNotHeld,
|
|
81
|
+
Unauthorized,
|
|
82
|
+
}
|
package/src/lib.rs
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#![no_std]
|
|
2
|
+
|
|
3
|
+
pub mod auth;
|
|
4
|
+
pub mod buffer_reader;
|
|
5
|
+
pub mod buffer_writer;
|
|
6
|
+
pub mod bytes_ext;
|
|
7
|
+
pub mod errors;
|
|
8
|
+
pub mod multisig;
|
|
9
|
+
pub mod option_ext;
|
|
10
|
+
pub mod ownable;
|
|
11
|
+
pub mod rbac;
|
|
12
|
+
pub mod ttl_configurable;
|
|
13
|
+
pub mod ttl_extendable;
|
|
14
|
+
pub mod upgradeable;
|
|
15
|
+
|
|
16
|
+
#[cfg(test)]
|
|
17
|
+
mod tests;
|
|
18
|
+
|
|
19
|
+
#[cfg(any(test, feature = "testutils"))]
|
|
20
|
+
pub mod testing_utils;
|