forked from StellarSplit/StellarSplit
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlib.rs
More file actions
229 lines (200 loc) · 7.75 KB
/
Copy pathlib.rs
File metadata and controls
229 lines (200 loc) · 7.75 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
//! # Split Template Contract
//!
//! Manages reusable split templates that can be deployed across multiple splits.
//! Provides deterministic template ID generation, creator-based indexing, and
//! template application tracking.
#![no_std]
use soroban_sdk::{contract, contractimpl, Address, Env, String, Vec};
mod events;
mod storage;
mod types;
mod utils;
#[cfg(test)]
mod test;
pub use events::*;
pub use storage::*;
pub use types::*;
pub use utils::*;
/// Current template schema version. Bump when the Template struct or contract
/// semantics change in a backwards-incompatible way.
pub const CURRENT_TEMPLATE_VERSION: u32 = 1;
/// The Split Template contract for managing reusable split configurations.
#[contract]
pub struct SplitTemplateContract;
#[contractimpl]
impl SplitTemplateContract {
/// Create a new split template with the given configuration.
///
/// Generates a deterministic template ID based on creator, name, and current ledger time.
/// Validates participants and shares according to the split type.
/// Stores the template and indexes it by creator.
///
/// # Arguments
/// * `env` - The Soroban environment
/// * `creator` - The address creating this template (must authorize)
/// * `name` - Human-readable name for the template
/// * `split_type` - How to divide funds (Equal, Percentage, or Fixed)
/// * `participants` - List of participants and their share values
///
/// # Returns
/// The deterministic template ID (hex string) or an error
pub fn create_template(
env: Env,
creator: Address,
name: String,
split_type: SplitType,
participants: Vec<Participant>,
) -> Result<String, Error> {
// Require authorization from the creator
creator.require_auth();
// Validate that participants list is not empty
if participants.len() == 0 {
return Err(Error::InvalidParticipants);
}
// Validate shares based on split type
Self::validate_shares(&env, split_type, &participants)?;
// Generate deterministic template ID from creator + name + ledger time
let template_id = Self::generate_template_id(&env, &creator, &name);
// Create the template struct
let template = Template {
id: template_id.clone(),
creator: creator.clone(),
name,
split_type,
participants,
version: CURRENT_TEMPLATE_VERSION,
};
// Store the template
storage::store_template(&env, &template);
// Add to creator's index for efficient lookup
storage::add_to_creator_index(&env, &creator, template_id.clone());
// Emit event (includes version)
events::emit_template_created(
&env,
template_id.clone(),
creator,
template.name.clone(),
CURRENT_TEMPLATE_VERSION,
);
Ok(template_id)
}
/// Use an existing template to track its usage in a split.
///
/// Loads the template and emits an event linking the template to a new split.
/// This is called after the split is created to record the template usage.
///
/// # Arguments
/// * `env` - The Soroban environment
/// * `template_id` - The ID of the template used
/// * `split_id` - The ID of the split created from the template
///
/// # Returns
/// Success or error if template not found
pub fn use_template(env: Env, template_id: String, split_id: u64) -> Result<(), Error> {
// Load the template; fail if not found
let template = storage::get_template(&env, &template_id).ok_or(Error::TemplateNotFound)?;
// Check version compatibility
if !Self::is_compatible(env.clone(), template.version) {
return Err(Error::IncompatibleVersion);
}
// Emit event linking template to split
events::emit_template_used(&env, template_id, split_id);
Ok(())
}
/// Get all templates created by a specific creator.
///
/// Reads the creator index and returns full template objects.
/// Returns empty vec if creator has no templates.
///
/// # Arguments
/// * `env` - The Soroban environment
/// * `creator` - The address to list templates for
///
/// # Returns
/// Vector of full Template objects for this creator
pub fn get_templates(env: Env, creator: Address) -> Vec<Template> {
// Get all template IDs for this creator
let template_ids = storage::get_creator_template_ids(&env, &creator);
// Load full template objects for each ID
let mut templates = Vec::new(&env);
for template_id in template_ids.iter() {
if let Some(template) = storage::get_template(&env, &template_id) {
templates.push_back(template);
}
}
templates
}
/// Get a single template by ID.
///
/// # Arguments
/// * `env` - The Soroban environment
/// * `template_id` - The template ID to retrieve
///
/// # Returns
/// The template if found, or an error
pub fn get_template(env: Env, template_id: String) -> Result<Template, Error> {
storage::get_template(&env, &template_id).ok_or(Error::TemplateNotFound)
}
/// Return the current template version used by this contract.
///
/// Useful for off-chain tooling to detect contract upgrades.
pub fn get_template_version(_env: Env) -> u32 {
CURRENT_TEMPLATE_VERSION
}
/// Check whether a given version is compatible with the current contract.
///
/// Returns `true` when `version` equals `CURRENT_TEMPLATE_VERSION`.
/// Callers can use this before attempting to deserialize or apply a template
/// that was created by a potentially different contract version.
pub fn is_compatible(_env: Env, version: u32) -> bool {
version == CURRENT_TEMPLATE_VERSION
}
// ============================================
// Private Helper Functions
// ============================================
/// Generate a deterministic template ID.
///
/// Creates a template ID from hash of creator address, name, and ledger timestamp.
/// This ensures uniqueness even with same name from same creator at different times.
fn generate_template_id(env: &Env, creator: &Address, name: &String) -> String {
// TODO: Use hash for production uniqueness
// For now, use name as simple ID
name.clone()
}
/// Validate participant shares based on split type.
fn validate_shares(
_env: &Env,
split_type: SplitType,
participants: &Vec<Participant>,
) -> Result<(), Error> {
match split_type {
SplitType::Equal => {
// For equal splits, shares must all be 1 (or not checked; we trust the caller)
Ok(())
}
SplitType::Percentage => {
// For percentage splits, all shares must be 0-100 and sum to 100
let mut total: i128 = 0;
for participant in participants.iter() {
if participant.share < 0 || participant.share > 100 {
return Err(Error::InvalidShares);
}
total += participant.share;
}
if total != 100 {
return Err(Error::InvalidShares);
}
Ok(())
}
SplitType::Fixed => {
// For fixed splits, all shares must be positive
for participant in participants.iter() {
if participant.share <= 0 {
return Err(Error::InvalidShares);
}
}
Ok(())
}
}
}
}