Publication
Partagez vos connaissances.
Meilleures pratiques pour la mise en œuvre de calendriers d'acquisition en Sui
Je suis en train de concevoir un système d'acquisition de jetons (par exemple, pour la répartition des équipes ou le blocage des investisseurs) dans Sui Move et je dois m'assurer que :
Sécurité — Pas de retraits prématurés
Flexibilité — Support pour les falaises, revêtement linéaire/progressif
Efficacité du gaz — Coûts de stockage minimaux
Transparence — Vérification facile du statut d'acquisition
Défis actuels :
- Les blocages temporels de base fonctionnent mais manquent de contrôle granulaire
- Difficultés liées à la gestion de plusieurs bénéficiaires
- Je ne sais pas comment traiter les cas de licenciement anticipé
Des questions :
Quelle est l'architecture la plus sécurisée pour les contrats d'acquisition ?
Comment implémenter différentes courbes d'acquisition (linéaire, graduée, en falaise) ?
La meilleure façon de gérer les horaires révocables par rapport aux horaires irrévocables ?
Des vulnérabilités courantes à éviter ?
- Sui
- Move
Réponses
1####1. Architecture d'acquisition de base
#####Mise en œuvre minimale viable
module vesting::linear {
use sui::coin;
use sui::clock;
use sui::balance;
use sui::tx_context;
struct VestingSchedule has key {
id: UID,
recipient: address,
total_amount: u64,
start_time: u64, // Clock timestamp
duration_ms: u64, // Total vesting period
claimed: u64
}
/// Initialize vesting
public entry fn create(
recipient: address,
total_amount: u64,
duration_ms: u64,
clock: &Clock,
ctx: &mut TxContext
) {
let schedule = VestingSchedule {
id: object::new(ctx),
recipient,
total_amount,
start_time: clock.timestamp_ms,
duration_ms,
claimed: 0
};
transfer::transfer(schedule, recipient);
}
}
Composantes clés :
Clock
horodatages basés sur des numéros de blocs (et non des numéros de blocs)- Objets de calendrier à gérer soi-même
- Délivrance linéaire par défaut
####2. Modèles d'acquisition avancés
#####Falaise + Vestage linéaire
public fun claimable_amount(
schedule: &VestingSchedule,
clock: &Clock
): u64 {
let elapsed = clock.timestamp_ms.saturating_sub(schedule.start_time);
// Cliff period (e.g., 1 year)
if (elapsed < CLIFF_MS) return 0;
// Linear vesting after cliff
let vested = (schedule.total_amount * elapsed) / schedule.duration_ms;
vested - schedule.claimed
}
#####Désignation graduelle (étapes multiples)
struct GradedVesting has key {
id: UID,
milestones: vector<Milestone>, // [{time: u64, percent: u8}]
claimed: u64
}
public fun graded_claimable(schedule: &GradedVesting, clock: &Clock): u64 {
let total_vested = 0;
let i = 0;
while (i < vector::length(&schedule.milestones)) {
let milestone = vector::borrow(&schedule.milestones, i);
if (clock.timestamp_ms >= milestone.time) {
total_vested = total_vested +
(schedule.total_amount * milestone.percent as u64) / 100;
};
i = i + 1;
};
total_vested - schedule.claimed
}
####3. Améliorations de sécurité
#####Attributions révocables (contrôles administratifs)
struct AdminCap has key { id: UID }
public entry fn revoke(
schedule: VestingSchedule,
admin_cap: &AdminCap,
ctx: &mut TxContext
) {
assert!(address_of(signer) == admin_cap.admin, EUnauthorized);
let remaining = schedule.total_amount - schedule.claimed;
coin::transfer(remaining, admin_cap.admin, ctx);
object::delete(schedule);
}
#####Anti-Fronrunning
public entry fn claim(
schedule: &mut VestingSchedule,
clock: &Clock,
ctx: &mut TxContext
) {
let amount = claimable_amount(schedule, clock);
assert!(amount > 0, ENothingToClaim);
assert!(tx_context::epoch(ctx) == clock.epoch(), EStaleClock);
schedule.claimed = schedule.claimed + amount;
transfer::public_transfer(coin::mint(amount, ctx), schedule.recipient);
}
####4. Techniques d'optimisation du gaz
#####Calendriers d'acquisition par lots
struct BatchVesting has key {
id: UID,
schedules: Table<address, VestingSchedule>
}
// Claim all eligible in one TXN
public entry fn batch_claim(batch: &mut BatchVesting, clock: &Clock) {
let iter = table::iter(&mut batch.schedules);
while (table::has_next(&iter)) {
let (addr, schedule) = table::next(&iter);
let amount = claimable_amount(schedule, clock);
if (amount > 0) transfer_coins(addr, amount);
}
}
#####Rabais sur l'entreposage
// Use small types where possible
struct CompactVesting {
start_time: u64,
duration_ms: u32, // Sufficient for 49-day periods
claimed: u64
}
####5. Vulnérabilités courantes
❌Attaques chronométrées par Oracle
// UNSAFE: Trusting user-provided timestamps
fun unsafe_claim(user_time: u64) { ... }
❌Erreurs d'arrondi
// May leave "dust" unclaimed
let vested = (amount * elapsed) / duration; // Use checked_math
❌Logique de révocation manquante
// Irrevocable vesting may violate securities laws
struct DangerousVesting { ... }
####6. Stratégies de test
#####Test d'horloge simulée
#[test]
fun test_cliff_period() {
let clock = mock_clock(0);
let schedule = create_vesting(CLIFF_MS * 2);
// Before cliff
test_clock::advance(&mut clock, CLIFF_MS - 1);
assert!(claimable(&schedule, &clock) == 0, ETestFail);
// After cliff
test_clock::advance(&mut clock, 1);
assert!(claimable(...) > 0, ETestFail);
}
#####Tests de fuzz
#[test]
fun test_overflow_scenarios() {
let schedule = VestingSchedule {
start_time: u64::MAX - 1000,
duration_ms: 2000
};
// Should handle wrap-around correctly
claimable_amount(&schedule, &clock);
}
Connaissez-vous la réponse ?
Veuillez vous connecter et la partager.
Sui is a Layer 1 protocol blockchain designed as the first internet-scale programmable blockchain platform.
Gagne ta part de 1000 Sui
Gagne des points de réputation et obtiens des récompenses pour avoir aidé la communauté Sui à se développer.
- Pourquoi BCS exige-t-il un ordre de champs exact pour la désérialisation alors que les structures Move ont des champs nommés ?53
- « Erreurs de vérification de sources multiples » dans les publications du module Sui Move - Résolution automatique des erreurs43
- Échec de la transaction Sui : objets réservés pour une autre transaction25
- Comment les contraintes de capacité interagissent-elles avec les champs dynamiques dans des collections hétérogènes ?05