Skip to content
Merged
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
16 changes: 16 additions & 0 deletions COMEBACKHERE-contracts/contracts/compliance/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,22 @@ pub enum DataKey {
#[contract]
pub struct ComplianceContract;

#[cfg(test)]
mod version_tests {
use super::*;

#[test]
fn test_version_returns_package_version() {
let env = Env::default();
let contract_id = env.register(ComplianceContract, ());
let client = ComplianceContractClient::new(&env, &contract_id);
assert_eq!(
client.version(),
String::from_str(&env, env!("CARGO_PKG_VERSION"))
);
}
}

fn is_paused(e: &Env) -> bool {
e.storage()
.instance()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,8 +157,69 @@ fn test_resolve_dispute_callable_by_signer() {
client.raise_dispute(&merchant, &settlement_id, &1u32);

client.resolve_dispute(&signer_a, &settlement_id, &true);
assert_eq!(
client.get_settlement(&settlement_id).unwrap().status,
SettlementStatus::Pending
);

let events = env.events().all();
let (_, topics, data) = events.get(events.len() - 1).unwrap();
assert_eq!(topics.get(0).unwrap(), Symbol::new(&env, "dispute_resolved").into_val(&env));
assert_eq!(data, (settlement_id, true, 0u64).into_val(&env));
}

#[test]
fn test_resolve_dispute_against_merchant_cancels_settlement() {
let (env, contract_id) = setup_env();
let client = make_client(&env, &contract_id);
let admin = Address::generate(&env);
let signer = Address::generate(&env);
let token = Address::generate(&env);
let merchant = Address::generate(&env);
client.initialize(&vec![&env, (signer.clone(), 1u64)], &1u64, &admin);
let settlement_id = client.propose_settlement(&signer, &token, &5_000_000u64, &merchant);
client.raise_dispute(&merchant, &settlement_id, &1u32);

client.resolve_dispute(&signer, &settlement_id, &false);

assert_eq!(
client.get_settlement(&settlement_id).unwrap().status,
SettlementStatus::Cancelled
);
let events = env.events().all();
let (_, _, data) = events.get(events.len() - 1).unwrap();
assert_eq!(data, (settlement_id, false, 0u64).into_val(&env));
}

client.resolve_dispute(&signer_b, &settlement_id, &true);
#[test]
fn test_resolve_non_disputed_settlement_returns_error() {
let (env, contract_id) = setup_env();
let client = make_client(&env, &contract_id);
let admin = Address::generate(&env);
let signer = Address::generate(&env);
let token = Address::generate(&env);
let merchant = Address::generate(&env);
client.initialize(&vec![&env, (signer.clone(), 1u64)], &1u64, &admin);
let settlement_id = client.propose_settlement(&signer, &token, &5_000_000u64, &merchant);

assert_eq!(
client.try_resolve_dispute(&signer, &settlement_id, &true),
Err(Ok(TreasuryError::NotDisputed))
);
assert_eq!(
client.get_settlement(&settlement_id).unwrap().status,
SettlementStatus::Pending
);
}

#[test]
fn test_treasury_version_returns_package_version() {
let (env, contract_id) = setup_env();
let client = make_client(&env, &contract_id);
assert_eq!(
client.version(),
soroban_sdk::String::from_str(&env, env!("CARGO_PKG_VERSION"))
);
}

#[test]
Expand Down
28 changes: 24 additions & 4 deletions COMEBACKHERE-contracts/contracts/treasury/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -940,19 +940,39 @@ impl TreasuryContract {
/// # Arguments
/// * `e` - Soroban environment handle.
/// * `signer` - Authorized signer address resolving the dispute (must authenticate).
/// * `_settlement_id` - ID of the disputed settlement.
/// * `_resolve_in_favor` - Resolution outcome decision flag.
/// * `settlement_id` - ID of the disputed settlement.
/// * `resolve_in_favor` - Whether the outcome favors the merchant.
///
/// # Errors
/// * Returns [`TreasuryError::ContractPaused`] if contract is paused.
/// * Returns [`TreasuryError::NotDisputed`] if the settlement is not on hold.
pub fn resolve_dispute(
e: Env,
signer: Address,
_settlement_id: u64,
_resolve_in_favor: bool,
settlement_id: u64,
resolve_in_favor: bool,
) -> Result<(), TreasuryError> {
check_not_paused(&e)?;
signer.require_auth();
let mut settlement = Self::get_settlement_internal(&e, settlement_id);
if settlement.status != SettlementStatus::OnHold {
return Err(TreasuryError::NotDisputed);
}
let resolution_weight = settlement.approval_weight;
settlement.status = if resolve_in_favor {
SettlementStatus::Pending
} else {
SettlementStatus::Cancelled
};
e.storage()
.instance()
.set(&DataKey::Settlement(settlement_id), &settlement);
crate::events::dispute_resolved(
&e,
&settlement_id,
&resolve_in_favor,
&resolution_weight,
);
Ok(())
}

Expand Down
7 changes: 6 additions & 1 deletion comebackhere-backend/src/schemas/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { z } from "zod"
import { Keypair } from "stellar-sdk"

const MAX_REFERENCE_LEN_BYTES = 64

function isValidStellarAddress(addr: string): boolean {
try {
Keypair.fromPublicKey(addr)
Expand Down Expand Up @@ -35,7 +37,10 @@ export const createInvoiceSchema = z.object({
due_date: futureTimestamp,
reference: z
.string()
.refine((val) => Buffer.byteLength(val, "utf8") <= 64, "reference must not exceed 64 bytes")
.refine(
(val) => Buffer.byteLength(val, "utf8") <= MAX_REFERENCE_LEN_BYTES,
`reference must not exceed ${MAX_REFERENCE_LEN_BYTES} bytes`,
)
.optional(),
})

Expand Down
1 change: 1 addition & 0 deletions docs/api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,7 @@ Create a new invoice by submitting `create_invoice` to the Soroban RPC.
| `token` | string | Token identifier |
| `amount` | number | Positive number (in stroops / smallest unit) |
| `due_date` | number | Future Unix timestamp (seconds) for the due date |
| `reference` | string | Optional invoice reference, limited to 64 UTF-8 bytes |

The merchant identity is taken from the API key, never from the request body.
Include `Authorization: Bearer <api_key>`.
Expand Down
33 changes: 33 additions & 0 deletions scripts/verify.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
# INVOICE_CONTRACT_ID — deployed invoice contract ID (C…)
# TREASURY_CONTRACT_ID — deployed treasury contract ID (C…)
# COMPLIANCE_CONTRACT_ID — deployed compliance contract ID (C…)
# SOROBAN_NETWORK_PASSPHRASE — network passphrase for contract invocation
# SOROBAN_SOURCE_ACCOUNT — public G-address used to simulate read-only calls
#
# Optional:
# CONTRACTS_DIR — path to built WASM artifacts
Expand Down Expand Up @@ -66,12 +68,15 @@ fi

: "${TREASURY_CONTRACT_ID:?TREASURY_CONTRACT_ID is required for WASM verification}"
: "${COMPLIANCE_CONTRACT_ID:?COMPLIANCE_CONTRACT_ID is required for WASM verification}"
: "${SOROBAN_NETWORK_PASSPHRASE:?SOROBAN_NETWORK_PASSPHRASE is required to read contract versions}"
: "${SOROBAN_SOURCE_ACCOUNT:?SOROBAN_SOURCE_ACCOUNT is required to simulate version getters}"

CONTRACTS_DIR="${CONTRACTS_DIR:-$ROOT_DIR/../COMEBACKHERE-contracts/target/wasm32-unknown-unknown/release}"

FAIL=0
MISMATCHES=()
CHECKS=() # For JSON output: array of check results
VERSIONS=() # For JSON output: deployed contract versions

verify_contract() {
local name="$1"
Expand Down Expand Up @@ -134,6 +139,25 @@ print(hashlib.sha256(wasm_bytes).hexdigest())
fi
}

print_contract_version() {
local name="$1"
local contract_id="$2"
local version

version=$(stellar contract invoke \
--id "$contract_id" \
--rpc-url "$SOROBAN_RPC_URL" \
--network-passphrase "$SOROBAN_NETWORK_PASSPHRASE" \
--source-account "$SOROBAN_SOURCE_ACCOUNT" \
--send no \
-- version)

if [[ "$OUTPUT_FORMAT" == "text" ]]; then
echo " $name version: $version"
fi
VERSIONS+=("{\"contract\":\"$name\",\"version\":\"$version\"}")
}

echo ""
echo "Verifying deployed WASM hashes against local build artifacts…"

Expand All @@ -149,6 +173,10 @@ verify_contract "compliance" \
"$COMPLIANCE_CONTRACT_ID" \
"$CONTRACTS_DIR/comebackhere_compliance.wasm"

print_contract_version "invoice" "$INVOICE_CONTRACT_ID"
print_contract_version "treasury" "$TREASURY_CONTRACT_ID"
print_contract_version "compliance" "$COMPLIANCE_CONTRACT_ID"

if [[ "$OUTPUT_FORMAT" == "json" ]]; then
# Output JSON result
echo "{"
Expand All @@ -157,6 +185,11 @@ if [[ "$OUTPUT_FORMAT" == "json" ]]; then
for i in "${!CHECKS[@]}"; do
echo " ${CHECKS[$i]}$([ $i -lt $((${#CHECKS[@]} - 1)) ] && echo ',' || echo '')"
done
echo " ],"
echo " \"versions\": ["
for i in "${!VERSIONS[@]}"; do
echo " ${VERSIONS[$i]}$([ $i -lt $((${#VERSIONS[@]} - 1)) ] && echo ',' || echo '')"
done
echo " ]"
echo "}"
else
Expand Down