Skip to content
Open
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
9 changes: 0 additions & 9 deletions contracts/amm-liquidity-pools/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,3 @@ soroban-sdk = { workspace = true, features = ["testutils"] }
[lib]
crate-type = ["cdylib"]

[profile.release]
opt-level = "z"
overflow-checks = true
debug = 0
strip = "symbols"
debug-assertions = false
panic = "abort"
codegen-units = 1
lto = true
70 changes: 70 additions & 0 deletions contracts/escrow/src/founder_vesting.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
use crate::storage::{get_vesting_schedule, set_vesting_schedule, remove_vesting_schedule};
use shared::types::{Amount, VestingSchedule};
use shared::errors::Error;
use soroban_sdk::{Address, Env};

pub fn calculate_unlocked(env: &Env, schedule: &VestingSchedule) -> Amount {
let now = env.ledger().timestamp();
if now <= schedule.start_time {
return 0;
}

let elapsed = now - schedule.start_time;
if elapsed >= schedule.duration {
return schedule.total_amount;
}

// Linear vesting: total_amount * elapsed / duration
(schedule.total_amount * elapsed as i128) / schedule.duration as i128
}

pub fn create_vesting(
env: &Env,
project_id: u64,
milestone_id: u64,
beneficiary: Address,
amount: Amount,
duration: u64,
) {
let schedule = VestingSchedule {
project_id,
milestone_id,
beneficiary,
total_amount: amount,
claimed_amount: 0,
start_time: env.ledger().timestamp(),
duration,
};
set_vesting_schedule(env, project_id, milestone_id, &schedule);
}

pub fn internal_claim_unlocked(
env: &Env,
project_id: u64,
milestone_id: u64,
) -> Result<(Address, Amount), Error> {
let mut schedule = get_vesting_schedule(env, project_id, milestone_id)?;
schedule.beneficiary.require_auth();

let unlocked = calculate_unlocked(env, &schedule);
let claimable = unlocked - schedule.claimed_amount;

if claimable <= 0 {
return Err(Error::NoClaim);
}

schedule.claimed_amount += claimable;
let beneficiary = schedule.beneficiary.clone();

if schedule.claimed_amount >= schedule.total_amount {
remove_vesting_schedule(env, project_id, milestone_id);
} else {
set_vesting_schedule(env, project_id, milestone_id, &schedule);
}

Ok((beneficiary, claimable))
}

pub fn get_vesting(env: &Env, project_id: u64, milestone_id: u64) -> Result<VestingSchedule, Error> {
get_vesting_schedule(env, project_id, milestone_id)
}
65 changes: 58 additions & 7 deletions contracts/escrow/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,16 @@ pub trait ProfitDistributionTrait {

mod storage;
mod validation;
mod founder_vesting;

mod yield_router;

#[cfg(test)]
mod tests;

use storage::*;
use founder_vesting::*;

use yield_router::{
configure_yield_router as configure_router, disable_yield_for_escrow as disable_yield_router,
emergency_withdraw_from_pool, enable_yield_for_escrow as enable_yield_router,
Expand Down Expand Up @@ -87,8 +91,10 @@ impl EscrowContract {
token: Address,
validators: Vec<Address>,
approval_threshold: u32,
vesting_duration: u64,
management_fee_bps: u32,
) -> Result<(), Error> {

creator.require_auth();

// Validate inputs
Expand Down Expand Up @@ -118,9 +124,11 @@ impl EscrowContract {
released_amount: 0,
validators,
approval_threshold,
vesting_duration,
management_fee_bps,
};


// Store escrow
set_escrow(&env, project_id, &escrow);

Expand Down Expand Up @@ -347,17 +355,20 @@ impl EscrowContract {
if milestone.approval_count as u32 >= required_approvals {
milestone.status = MilestoneStatus::Approved;

// Release funds
// Release funds (this updates escrow.released_amount)
release_milestone_funds(&env, &mut escrow, &milestone)?;

// Perform token transfer to creator
let token_client = TokenClient::new(&env, &escrow.token);
token_client.transfer(
&env.current_contract_address(),
&escrow.creator,
&milestone.amount,
// Instead of direct transfer, create a vesting schedule
create_vesting(
&env,
project_id,
milestone_id,
escrow.creator.clone(),
milestone.amount,
escrow.vesting_duration,
);


// Store updated escrow
set_escrow(&env, project_id, &escrow);

Expand Down Expand Up @@ -1296,6 +1307,14 @@ impl EscrowContract {
release_milestone_funds(env, &mut virtual_escrow, &virtual_milestone)?;
escrow.released_amount = virtual_escrow.released_amount;

// Instead of direct transfer, create vesting
create_vesting(
&env,
dispute.project_id,
dispute.milestone_id,
escrow.creator.clone(),
release_amount,
escrow.vesting_duration,
let token_client = TokenClient::new(env, &escrow.token);
token_client.transfer(
&env.current_contract_address(),
Expand All @@ -1304,6 +1323,7 @@ impl EscrowContract {
);
}


set_milestone(env, dispute.project_id, dispute.milestone_id, &milestone);
set_escrow(env, dispute.project_id, &escrow);

Expand Down Expand Up @@ -1445,6 +1465,35 @@ impl EscrowContract {
storage::get_pending_upgrade(&env)
}

/// Claim unlocked funds from a vesting schedule
pub fn claim_unlocked(
env: Env,
project_id: u64,
milestone_id: u64,
) -> Result<Amount, Error> {
let (beneficiary, amount) = internal_claim_unlocked(&env, project_id, milestone_id)?;

// Get escrow for the project to get token address
let escrow = get_escrow(&env, project_id)?;

let token_client = TokenClient::new(&env, &escrow.token);
token_client.transfer(
&env.current_contract_address(),
&beneficiary,
&amount,
);

Ok(amount)
}

/// Get vesting schedule information
pub fn get_vesting_info(
env: Env,
project_id: u64,
milestone_id: u64,
) -> Result<VestingSchedule, Error> {
get_vesting(&env, project_id, milestone_id)

/// Claim excess yield from the escrow contract
/// Yield = Balance - (TotalDeposited - ReleasedAmount)
pub fn claim_yield(
Expand Down Expand Up @@ -1515,9 +1564,11 @@ impl EscrowContract {
);

Ok(())
main
}
}


/// Helper function to release milestone funds
fn release_milestone_funds(
_env: &Env,
Expand Down
23 changes: 23 additions & 0 deletions contracts/escrow/src/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ const JUROR_PREFIX: &str = "juror";
const DISPUTE_VOTE_PREFIX: &str = "d_vote";
const JUROR_ASSIGNMENTS_PREFIX: &str = "j_assign";
const ACTIVE_JURORS_KEY: &str = "act_jurors";
const VESTING_PREFIX: &str = "vesting";

const EMERGENCY_WITHDRAW_PREFIX: &str = "emg_withdraw";

/// Store platform admin
Expand Down Expand Up @@ -344,6 +346,27 @@ pub fn has_pending_upgrade(env: &Env) -> bool {
env.storage().instance().has(&PENDING_UPGRADE_KEY)
}

/// Store a vesting schedule for a project/milestone
pub fn set_vesting_schedule(env: &Env, project_id: u64, milestone_id: u64, vesting: &VestingSchedule) {
let key = (VESTING_PREFIX, project_id, milestone_id);
env.storage().persistent().set(&key, vesting);
}

/// Retrieve a vesting schedule
pub fn get_vesting_schedule(env: &Env, project_id: u64, milestone_id: u64) -> Result<VestingSchedule, Error> {
let key = (VESTING_PREFIX, project_id, milestone_id);
env.storage()
.persistent()
.get::<(&str, u64, u64), VestingSchedule>(&key)
.ok_or(Error::NotFound)
}

/// Remove a vesting schedule
pub fn remove_vesting_schedule(env: &Env, project_id: u64, milestone_id: u64) {
let key = (VESTING_PREFIX, project_id, milestone_id);
env.storage().persistent().remove(&key);
}

pub fn get_emergency_withdraw_state(env: &Env, project_id: u64) -> EmergencyWithdrawState {
let key = (EMERGENCY_WITHDRAW_PREFIX, project_id);
env.storage()
Expand Down
Loading
Loading