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
12 changes: 12 additions & 0 deletions contracts/campaign-escrow/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ impl CampaignEscrowContract {
storage::set_admin(&env, &admin);
storage::set_dispute_contract(&env, &dispute_contract);
storage::set_fee_bps(&env, fee_bps);
storage::bump_instance(&env);
Ok(())
}

Expand All @@ -78,6 +79,7 @@ impl CampaignEscrowContract {
completion_deadline: u64,
metadata_uri: String,
) -> Result<CampaignId, Error> {
storage::bump_instance(&env);
todo!("design + implement campaign creation — see doc comment above")
}

Expand All @@ -95,6 +97,7 @@ impl CampaignEscrowContract {
business: Address,
campaign_id: CampaignId,
) -> Result<(), Error> {
storage::bump_instance(&env);
business.require_auth();
todo!("design + implement escrow funding — see doc comment above")
}
Expand All @@ -111,6 +114,7 @@ impl CampaignEscrowContract {
campaign_id: CampaignId,
pitch_uri: String,
) -> Result<(), Error> {
storage::bump_instance(&env);
creator.require_auth();
todo!("design + implement creator applications — see doc comment above")
}
Expand All @@ -129,6 +133,7 @@ impl CampaignEscrowContract {
creator: Address,
payout_amount: i128,
) -> Result<(), Error> {
storage::bump_instance(&env);
business.require_auth();
todo!("design + implement creator approval — see doc comment above")
}
Expand All @@ -146,6 +151,7 @@ impl CampaignEscrowContract {
campaign_id: CampaignId,
proof_uri: String,
) -> Result<(), Error> {
storage::bump_instance(&env);
creator.require_auth();
todo!("design + implement proof submission/verification — see doc comment above")
}
Expand All @@ -164,6 +170,7 @@ impl CampaignEscrowContract {
campaign_id: CampaignId,
creator: Address,
) -> Result<(), Error> {
storage::bump_instance(&env);
business.require_auth();
todo!("design + implement payout release — see doc comment above")
}
Expand All @@ -180,6 +187,7 @@ impl CampaignEscrowContract {
business: Address,
campaign_id: CampaignId,
) -> Result<(), Error> {
storage::bump_instance(&env);
business.require_auth();
todo!("design + implement cancellation/refund — see doc comment above")
}
Expand All @@ -199,6 +207,7 @@ impl CampaignEscrowContract {
campaign_id: CampaignId,
creator: Address,
) -> Result<(), Error> {
storage::bump_instance(&env);
todo!("design + implement dispute freeze hook — see doc comment above")
}

Expand All @@ -213,11 +222,13 @@ impl CampaignEscrowContract {
creator: Address,
creator_bps: i128,
) -> Result<(), Error> {
storage::bump_instance(&env);
todo!("design + implement dispute payout resolution — see doc comment above")
}

/// Read-only lookup of a campaign's current state.
pub fn get_campaign(env: Env, campaign_id: CampaignId) -> Result<Campaign, Error> {
storage::bump_instance(&env);
storage::get_campaign(&env, campaign_id)
}

Expand All @@ -227,6 +238,7 @@ impl CampaignEscrowContract {
campaign_id: CampaignId,
creator: Address,
) -> Result<Application, Error> {
storage::bump_instance(&env);
storage::get_application(&env, campaign_id, &creator)
}
}
Expand Down
45 changes: 31 additions & 14 deletions contracts/campaign-escrow/src/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,11 @@ use soroban_sdk::{contracttype, Address, Env};
use crate::error::Error;
use crate::types::{Application, Campaign};

/// Extend persistent entries by roughly this many ledgers on every write
/// (~30 days at 5s/ledger). TODO(contributors): tune once real rent/TTL
/// costs on target networks are benchmarked, and consider a max-TTL bump on
/// read-heavy paths too.
const PERSISTENT_BUMP_LEDGERS: u32 = 518_400;
const PERSISTENT_LIFETIME_THRESHOLD: u32 = 500_000;
/// Extend persistent entries by roughly this many ledgers (~1 year at
/// 5 s/ledger — the maximum the Stellar network allows for a single
/// `extend_ttl` call).
const LEDGER_BUMP: u32 = 535_680;
const LEDGER_THRESHOLD: u32 = 500_000;

#[contracttype]
#[derive(Clone, Debug, Eq, PartialEq)]
Expand All @@ -24,6 +23,14 @@ pub enum DataKey {
Application(CampaignId, Address),
}

/// Bump the instance TTL so metadata (admin, fee, etc.) doesn't expire
/// while the contract is actively being used.
pub fn bump_instance(env: &Env) {
env.storage()
.instance()
.extend_ttl(LEDGER_THRESHOLD, LEDGER_BUMP);
}

pub fn is_initialized(env: &Env) -> bool {
env.storage().instance().has(&DataKey::Admin)
}
Expand Down Expand Up @@ -76,19 +83,24 @@ pub fn next_campaign_id(env: &Env) -> CampaignId {
}

pub fn get_campaign(env: &Env, id: CampaignId) -> Result<Campaign, Error> {
let key = DataKey::Campaign(id);
let campaign = env.storage()
.persistent()
.get(&key)
.ok_or(Error::CampaignNotFound)?;
env.storage()
.persistent()
.get(&DataKey::Campaign(id))
.ok_or(Error::CampaignNotFound)
.extend_ttl(&key, LEDGER_THRESHOLD, LEDGER_BUMP);
Ok(campaign)
}

pub fn set_campaign(env: &Env, campaign: &Campaign) {
let key = DataKey::Campaign(campaign.id);
env.storage().persistent().set(&key, campaign);
env.storage().persistent().extend_ttl(
&key,
PERSISTENT_LIFETIME_THRESHOLD,
PERSISTENT_BUMP_LEDGERS,
LEDGER_THRESHOLD,
LEDGER_BUMP,
);
}

Expand All @@ -97,18 +109,23 @@ pub fn get_application(
campaign_id: CampaignId,
creator: &Address,
) -> Result<Application, Error> {
let key = DataKey::Application(campaign_id, creator.clone());
let app = env.storage()
.persistent()
.get(&key)
.ok_or(Error::ApplicationNotFound)?;
env.storage()
.persistent()
.get(&DataKey::Application(campaign_id, creator.clone()))
.ok_or(Error::ApplicationNotFound)
.extend_ttl(&key, LEDGER_THRESHOLD, LEDGER_BUMP);
Ok(app)
}

pub fn set_application(env: &Env, application: &Application) {
let key = DataKey::Application(application.campaign_id, application.creator.clone());
env.storage().persistent().set(&key, application);
env.storage().persistent().extend_ttl(
&key,
PERSISTENT_LIFETIME_THRESHOLD,
PERSISTENT_BUMP_LEDGERS,
LEDGER_THRESHOLD,
LEDGER_BUMP,
);
}
65 changes: 53 additions & 12 deletions contracts/campaign-escrow/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,28 +8,29 @@ use super::*;
use soroban_sdk::testutils::Address as _;
use soroban_sdk::Env;

fn setup(env: &Env) -> (CampaignEscrowContractClient<'_>, Address, Address) {
fn setup(
env: &Env,
) -> (CampaignEscrowContractClient<'_>, Address, Address, Address) {
env.mock_all_auths();
let contract_id = env.register(CampaignEscrowContract, ());
let client = CampaignEscrowContractClient::new(env, &contract_id);
let admin = Address::generate(env);
let dispute_contract = Address::generate(env);
(client, admin, dispute_contract)
(client, contract_id, admin, dispute_contract)
}

#[test]
fn initialize_sets_admin_and_fee() {
let env = Env::default();
env.mock_all_auths();
let (client, admin, dispute_contract) = setup(&env);
let (client, _contract_id, admin, dispute_contract) = setup(&env);

client.initialize(&admin, &dispute_contract, &250);
}

#[test]
fn initialize_twice_fails() {
let env = Env::default();
env.mock_all_auths();
let (client, admin, dispute_contract) = setup(&env);
let (client, _contract_id, admin, dispute_contract) = setup(&env);

client.initialize(&admin, &dispute_contract, &250);
let result = client.try_initialize(&admin, &dispute_contract, &250);
Expand All @@ -39,8 +40,7 @@ fn initialize_twice_fails() {
#[test]
fn initialize_rejects_out_of_range_fee() {
let env = Env::default();
env.mock_all_auths();
let (client, admin, dispute_contract) = setup(&env);
let (client, _contract_id, admin, dispute_contract) = setup(&env);

let result = client.try_initialize(
&admin,
Expand All @@ -53,23 +53,64 @@ fn initialize_rejects_out_of_range_fee() {
#[test]
fn get_campaign_not_found_before_creation() {
let env = Env::default();
env.mock_all_auths();
let (client, admin, dispute_contract) = setup(&env);
let (client, _contract_id, admin, dispute_contract) = setup(&env);
client.initialize(&admin, &dispute_contract, &250);

let result = client.try_get_campaign(&0);
assert_eq!(result, Err(Ok(Error::CampaignNotFound)));
}

#[test]
fn initialize_bumps_instance_ttl() {
let env = Env::default();
let (client, contract_id, admin, dispute_contract) = setup(&env);
client.initialize(&admin, &dispute_contract, &250);

let admin_back = env.as_contract(&contract_id, || super::storage::get_admin(&env));
assert_eq!(admin_back, Ok(admin));
}

#[test]
fn set_and_get_campaign_maintains_ttl() {
let env = Env::default();
let (client, contract_id, admin, dispute_contract) = setup(&env);
client.initialize(&admin, &dispute_contract, &250);

let business = Address::generate(&env);
let token = Address::generate(&env);
let campaign = Campaign {
id: 1,
business: business.clone(),
asset: ads_bazaar_shared::PayoutAsset {
token,
symbol: String::from_str(&env, "USDC"),
},
total_budget: 1_000_000,
escrow_balance: 0,
max_creators: 5,
approved_count: 0,
status: ads_bazaar_shared::CampaignStatus::Draft,
application_deadline: env.ledger().timestamp() + 86_400,
completion_deadline: env.ledger().timestamp() + 604_800,
metadata_uri: String::from_str(&env, "ipfs://campaign"),
};

env.as_contract(&contract_id, || {
let _ = super::storage::set_campaign(&env, &campaign);
let loaded = super::storage::get_campaign(&env, 1).unwrap();
assert_eq!(loaded.id, campaign.id);
assert_eq!(loaded.business, campaign.business);
});
}

#[test]
#[should_panic(expected = "not yet implemented")]
fn create_campaign_is_not_yet_implemented() {
// Documents current scaffold state: this will start failing (in a good
// way) once `create_campaign` is implemented — replace this test with a
// real assertion at that point.
let env = Env::default();
env.mock_all_auths();
let (client, admin, dispute_contract) = setup(&env);
let (client, _contract_id, admin, dispute_contract) = setup(&env);
client.initialize(&admin, &dispute_contract, &250);

let business = Address::generate(&env);
Expand Down
5 changes: 5 additions & 0 deletions contracts/dispute-resolution/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ impl DisputeResolutionContract {

storage::set_admin(&env, &admin);
storage::set_escrow_contract(&env, &escrow_contract);
storage::bump_instance(&env);
Ok(())
}

Expand All @@ -55,6 +56,7 @@ impl DisputeResolutionContract {
creator: Address,
reason_uri: String,
) -> Result<DisputeId, Error> {
storage::bump_instance(&env);
raised_by.require_auth();
todo!("design + implement dispute raising — see doc comment above")
}
Expand All @@ -69,6 +71,7 @@ impl DisputeResolutionContract {
dispute_id: DisputeId,
arbiter: Address,
) -> Result<(), Error> {
storage::bump_instance(&env);
admin.require_auth();
todo!("design + implement arbiter assignment — see doc comment above")
}
Expand All @@ -85,12 +88,14 @@ impl DisputeResolutionContract {
dispute_id: DisputeId,
outcome: DisputeOutcome,
) -> Result<(), Error> {
storage::bump_instance(&env);
arbiter.require_auth();
todo!("design + implement dispute resolution — see doc comment above")
}

/// Read-only lookup of a dispute's current state.
pub fn get_dispute(env: Env, dispute_id: DisputeId) -> Result<Dispute, Error> {
storage::bump_instance(&env);
storage::get_dispute(&env, dispute_id)
}
}
Expand Down
23 changes: 17 additions & 6 deletions contracts/dispute-resolution/src/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ use soroban_sdk::{contracttype, Address, Env};
use crate::error::Error;
use crate::types::Dispute;

const PERSISTENT_BUMP_LEDGERS: u32 = 518_400;
const PERSISTENT_LIFETIME_THRESHOLD: u32 = 500_000;
const LEDGER_BUMP: u32 = 535_680;
const LEDGER_THRESHOLD: u32 = 500_000;

#[contracttype]
#[derive(Clone, Debug, Eq, PartialEq)]
Expand All @@ -18,6 +18,12 @@ pub enum DataKey {
Dispute(DisputeId),
}

pub fn bump_instance(env: &Env) {
env.storage()
.instance()
.extend_ttl(LEDGER_THRESHOLD, LEDGER_BUMP);
}

pub fn is_initialized(env: &Env) -> bool {
env.storage().instance().has(&DataKey::Admin)
}
Expand Down Expand Up @@ -59,18 +65,23 @@ pub fn next_dispute_id(env: &Env) -> DisputeId {
}

pub fn get_dispute(env: &Env, id: DisputeId) -> Result<Dispute, Error> {
let key = DataKey::Dispute(id);
let dispute = env.storage()
.persistent()
.get(&key)
.ok_or(Error::DisputeNotFound)?;
env.storage()
.persistent()
.get(&DataKey::Dispute(id))
.ok_or(Error::DisputeNotFound)
.extend_ttl(&key, LEDGER_THRESHOLD, LEDGER_BUMP);
Ok(dispute)
}

pub fn set_dispute(env: &Env, id: DisputeId, dispute: &Dispute) {
let key = DataKey::Dispute(id);
env.storage().persistent().set(&key, dispute);
env.storage().persistent().extend_ttl(
&key,
PERSISTENT_LIFETIME_THRESHOLD,
PERSISTENT_BUMP_LEDGERS,
LEDGER_THRESHOLD,
LEDGER_BUMP,
);
}