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
Binary file removed .DS_Store
Binary file not shown.
8 changes: 8 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 16 additions & 0 deletions contracts/Contract-Events-for-Indexer/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[package]
name = "Contract-Events-for-Indexer"
version = "0.0.0"
edition = "2021"
publish = false

[lib]
crate-type = ["cdylib"]

[dependencies]
soroban-sdk = { workspace = true }
soroban-token-sdk = { workspace = true }

[dev-dependencies]
soroban-sdk = { workspace = true, features = ["testutils"] }
soroban-token-sdk = { workspace = true }
26 changes: 26 additions & 0 deletions contracts/Contract-Events-for-Indexer/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Spike 1 Findings: Adding Soroban Events

## Overview

This spike focused on implementing comprehensive Soroban events for the Trustless Work Smart Escrow contract to enable better indexing and monitoring capabilities. The implementation adds structured events for all major contract operations including escrow creation, funding, milestone management, dispute handling, and fund releases.

## Findings

- For each function that published an event, I needed to call a separate helper function to retrieve the current escrow state.
- Testing event publishing requires generating a contract ID.

## Assumptions

- **Soroban SDK Compatibility**: Events use standard Soroban SDK event publishing methods
- **Data Type Compatibility**: All event data types (Address, i128, String) are compatible with Soroban events
- **Event Ordering**: Events are emitted after successful state changes to ensure consistency
- **Gas Efficiency**: Event data is optimized to minimize gas costs while providing necessary context
- **Indexer Compatibility**: Event structure follows Soroban indexing best practices

## Recommendations

- **Add Error Event Handling**: Implement events for failed operations to help with debugging and monitoring
- **Event Validation**: Add tests specifically for event data structure and content validation
- **Event Documentation**: Create comprehensive documentation for indexers on event structure and data types
- **Event Versioning**: Consider adding event versioning for future contract upgrades
- **Event Filtering**: Implement event filtering capabilities for different use cases
195 changes: 195 additions & 0 deletions contracts/Contract-Events-for-Indexer/src/contract.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
use soroban_sdk::{
contract, contractimpl, symbol_short, Address, BytesN, Env, String, Symbol, Val, Vec,
};

use crate::core::{DisputeManager, EscrowManager, MilestoneManager};
use crate::error::ContractError;
use crate::storage::types::{AddressBalance, DataKey, Escrow};

#[contract]
pub struct EscrowContract;

#[contractimpl]
impl EscrowContract {
pub fn __constructor(env: Env, admin: Address) {
env.storage().instance().set(&DataKey::Admin, &admin);
}

pub fn deploy(
env: Env,
deployer: Address,
wasm_hash: BytesN<32>,
salt: BytesN<32>,
init_fn: Symbol,
init_args: Vec<Val>,
constructor_args: Vec<Val>,
) -> (Address, Val) {
if deployer != env.current_contract_address() {
deployer.require_auth();
}

let deployed_address = env
.deployer()
.with_address(deployer, salt)
.deploy_v2(wasm_hash, constructor_args);

let res: Val = env.invoke_contract(&deployed_address, &init_fn, init_args);
(deployed_address, res)
}

////////////////////////
// Escrow /////
////////////////////////

pub fn initialize_escrow(e: Env, escrow_properties: Escrow) -> Result<Escrow, ContractError> {
let initialized_escrow =
EscrowManager::initialize_escrow(e.clone(), escrow_properties)?;
let escrow = initialized_escrow.clone();
e.events().publish(
(symbol_short!("escrow"), symbol_short!("created")),
(escrow.engagement_id, escrow.amount, escrow.roles.receiver)
);
Ok(initialized_escrow)
}

pub fn fund_escrow(
e: Env,
signer: Address,
amount_to_deposit: i128,
) -> Result<(), ContractError> {
EscrowManager::fund_escrow(e.clone(), signer.clone(), amount_to_deposit)?;
let escrow = EscrowManager::get_escrow(e.clone())?;
e.events().publish(
(symbol_short!("escrow"), symbol_short!("funded")),
(e.current_contract_address(), escrow.engagement_id, signer, amount_to_deposit)
);
Ok(())
}

pub fn release_funds(
e: Env,
release_signer: Address,
trustless_work_address: Address,
) -> Result<(), ContractError> {
EscrowManager::release_funds(
e.clone(),
release_signer.clone(),
trustless_work_address.clone(),
)?;
let escrow = EscrowManager::get_escrow(e.clone())?;
e.events().publish(
(symbol_short!("escrow"), symbol_short!("released")),
(escrow.engagement_id, trustless_work_address)
);
Ok(())
}

pub fn update_escrow(
e: Env,
plataform_address: Address,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix typo in parameter name

The parameter name contains a typo: "plataform_address" should be "platform_address".

-        plataform_address: Address,
+        platform_address: Address,

Also update the usage on line 94:

-            plataform_address.clone(),
+            platform_address.clone(),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
plataform_address: Address,
pub fn update_escrow(
env: Env,
engagement_id: u64,
- plataform_address: Address,
+ platform_address: Address,
arbiter_address: Address,
dispute_resolution_address: Address,
) -> Result<(), Error> {
// Delegate to manager with updated properties
EscrowManager::change_escrow_properties(
env,
engagement_id,
- plataform_address.clone(),
+ platform_address.clone(),
arbiter_address.clone(),
dispute_resolution_address.clone(),
)?;
Ok(())
}
🤖 Prompt for AI Agents
In contracts/Contract-Events-for-Indexer/src/contract.rs at line 89, correct the
typo in the parameter name from "plataform_address" to "platform_address". Also,
update all usages of this parameter, including the one at line 94, to use the
corrected name "platform_address" to maintain consistency and avoid errors.

escrow_properties: Escrow,
) -> Result<Escrow, ContractError> {
let updated_escrow = EscrowManager::change_escrow_properties(
e.clone(),
plataform_address.clone(),
escrow_properties.clone(),
)?;

Ok(updated_escrow)
}
Comment on lines +87 to +99
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add event emission for escrow updates

The update_escrow function modifies escrow state but doesn't emit an event. For consistency with other state-changing functions and to support proper indexing, an event should be emitted.

     pub fn update_escrow(
         e: Env,
-        plataform_address: Address,
+        platform_address: Address,
         escrow_properties: Escrow,
     ) -> Result<Escrow, ContractError> {
         let updated_escrow = EscrowManager::change_escrow_properties(
             e.clone(),
-            plataform_address.clone(),
+            platform_address.clone(),
             escrow_properties.clone(),
         )?;
+        e.events().publish(
+            (symbol_short!("escrow"), symbol_short!("updated")),
+            (updated_escrow.engagement_id.clone(), platform_address)
+        );
         
         Ok(updated_escrow)
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
pub fn update_escrow(
e: Env,
plataform_address: Address,
escrow_properties: Escrow,
) -> Result<Escrow, ContractError> {
let updated_escrow = EscrowManager::change_escrow_properties(
e.clone(),
plataform_address.clone(),
escrow_properties.clone(),
)?;
Ok(updated_escrow)
}
pub fn update_escrow(
e: Env,
platform_address: Address,
escrow_properties: Escrow,
) -> Result<Escrow, ContractError> {
let updated_escrow = EscrowManager::change_escrow_properties(
e.clone(),
platform_address.clone(),
escrow_properties.clone(),
)?;
e.events().publish(
(symbol_short!("escrow"), symbol_short!("updated")),
(updated_escrow.engagement_id.clone(), platform_address)
);
Ok(updated_escrow)
}
🤖 Prompt for AI Agents
In contracts/Contract-Events-for-Indexer/src/contract.rs around lines 87 to 99,
the update_escrow function updates escrow state but does not emit an event.
Modify the function to emit an event after successfully updating the escrow by
calling the appropriate event emission method with relevant escrow update
details. This will ensure consistency with other state-changing functions and
support proper indexing.


pub fn get_escrow(e: Env) -> Result<Escrow, ContractError> {
EscrowManager::get_escrow(e)
}

pub fn get_escrow_by_contract_id(
e: Env,
contract_id: Address,
) -> Result<Escrow, ContractError> {
EscrowManager::get_escrow_by_contract_id(e, &contract_id)
}

pub fn get_multiple_escrow_balances(
e: Env,
signer: Address,
addresses: Vec<Address>,
) -> Result<Vec<AddressBalance>, ContractError> {
EscrowManager::get_multiple_escrow_balances(e, signer, addresses)
}

////////////////////////
// Milestones /////
////////////////////////

pub fn change_milestone_status(
e: Env,
milestone_index: i128,
new_status: String,
new_evidence: Option<String>,
service_provider: Address,
) -> Result<(), ContractError> {
MilestoneManager::change_milestone_status(
e.clone(),
milestone_index,
new_status.clone(),
new_evidence,
service_provider,
)?;
let escrow = EscrowManager::get_escrow(e.clone())?;
e.events().publish(
(symbol_short!("escrow"), symbol_short!("marked")),
(milestone_index, new_status, escrow.engagement_id)
);
Ok(())
}

pub fn approve_milestone(
e: Env,
milestone_index: i128,
new_flag: bool,
approver: Address,
) -> Result<(), ContractError> {
MilestoneManager::change_milestone_approved_flag(e.clone(), milestone_index, new_flag, approver)?;
let escrow = EscrowManager::get_escrow(e.clone())?;
e.events().publish(
(symbol_short!("escrow"), symbol_short!("approved")),
(milestone_index, new_flag, escrow.engagement_id));
Ok(())
}

////////////////////////
// Disputes /////
////////////////////////

pub fn resolve_dispute(
e: Env,
dispute_resolver: Address,
approver_funds: i128,
receiver_funds: i128,
trustless_work_address: Address,
) -> Result<(), ContractError> {
DisputeManager::resolve_dispute(
e.clone(),
dispute_resolver,
approver_funds,
receiver_funds,
trustless_work_address,
)?;
let escrow = EscrowManager::get_escrow(e.clone())?;
e.events().publish(
(symbol_short!("escrow"), symbol_short!("resolved")),
(approver_funds, receiver_funds, escrow.engagement_id)
);
Ok(())
}

pub fn dispute_escrow(e: Env, signer: Address) -> Result<(), ContractError> {
DisputeManager::dispute_escrow(e.clone(), signer.clone())?;
let escrow = EscrowManager::get_escrow(e.clone())?;
e.events().publish(
(symbol_short!("escrow"), symbol_short!("raised")),
(signer, escrow.engagement_id)
);
Ok(())
}
}
90 changes: 90 additions & 0 deletions contracts/Contract-Events-for-Indexer/src/core/dispute.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
use soroban_sdk::{Address, Env};
use soroban_sdk::token::Client as TokenClient;

use crate::core::escrow::EscrowManager;
use crate::error::ContractError;
use crate::events::escrows_by_contract_id;
use crate::modules::{
fee::{FeeCalculator, FeeCalculatorTrait},
math::{BasicArithmetic, BasicMath},
};
use crate::storage::types::DataKey;

use super::validators::dispute::{
validate_dispute_flag_change_conditions, validate_dispute_resolution_conditions,
};

pub struct DisputeManager;

impl DisputeManager {
pub fn resolve_dispute(
e: Env,
dispute_resolver: Address,
approver_funds: i128,
receiver_funds: i128,
trustless_work_address: Address,
) -> Result<(), ContractError> {
dispute_resolver.require_auth();
let mut escrow = EscrowManager::get_escrow(e.clone())?;
let contract_address = e.current_contract_address();

let token_client = TokenClient::new(&e, &escrow.trustline.address);

let total_funds = BasicMath::safe_add(approver_funds, receiver_funds)?;

if token_client.balance(&contract_address) < total_funds {
return Err(ContractError::InsufficientFundsForResolution);
}

let fee_result = FeeCalculator::calculate_dispute_fees(
approver_funds,
receiver_funds,
escrow.platform_fee as i128,
total_funds,
)?;

let current_balance = token_client.balance(&contract_address);
validate_dispute_resolution_conditions(
&escrow,
&dispute_resolver,
approver_funds,
receiver_funds,
total_funds,
&fee_result,
current_balance,
)?;

token_client.transfer(&contract_address, &trustless_work_address, &fee_result.trustless_work_fee);
token_client.transfer(&contract_address, &escrow.roles.platform_address, &fee_result.platform_fee);

if fee_result.net_approver_funds > 0 {
token_client.transfer(&contract_address, &escrow.roles.approver, &fee_result.net_approver_funds);
}

if fee_result.net_receiver_funds > 0 {
let receiver = EscrowManager::get_receiver(&escrow);
token_client.transfer(&contract_address, &receiver, &fee_result.net_receiver_funds);
}

escrow.flags.resolved = true;
escrow.flags.disputed = false;
e.storage().instance().set(&DataKey::Escrow, &escrow);

escrows_by_contract_id(&e, escrow.engagement_id.clone(), escrow);

Ok(())
}

pub fn dispute_escrow(e: Env, signer: Address) -> Result<(), ContractError> {
signer.require_auth();
let mut escrow = EscrowManager::get_escrow(e.clone())?;
validate_dispute_flag_change_conditions(&escrow, &signer)?;

escrow.flags.disputed = true;
e.storage().instance().set(&DataKey::Escrow, &escrow);

escrows_by_contract_id(&e, escrow.engagement_id.clone(), escrow);

Ok(())
}
}
Loading