diff --git a/Cargo.lock b/Cargo.lock index 9aa43d8..e9ef5b5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,9 +18,9 @@ dependencies = [ [[package]] name = "addr2line" -version = "0.25.1" +version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" +checksum = "e567177890eb1617b1f774005b66b26b2377afd138a2ca37aae7d8f0c81429d4" dependencies = [ "gimli", ] @@ -58,9 +58,9 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "android_system_properties" -version = "0.1.6" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" dependencies = [ "libc", ] @@ -129,7 +129,7 @@ dependencies = [ "ark-serialize", "ark-std", "arrayvec", - "digest 0.10.7", + "digest", "educe", "itertools 0.13.0", "num-bigint", @@ -185,7 +185,7 @@ dependencies = [ "ark-serialize-derive", "ark-std", "arrayvec", - "digest 0.10.7", + "digest", "num-bigint", ] @@ -231,9 +231,9 @@ checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "backtrace" -version = "0.3.76" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" +checksum = "42f4220217f3288537b83684130f42fad43320b1c3b805f1910d683d52ded24f" dependencies = [ "addr2line", "cfg-if", @@ -327,15 +327,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "block-buffer" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" -dependencies = [ - "hybrid-array", -] - [[package]] name = "borsh" version = "1.8.1" @@ -427,9 +418,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.9.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877bf67ac8e52449eb2f9e98a344ea163beb70dd1f9eebe3cc5357dcfeaee4df" +checksum = "5add81bb678e6cb321aff7fa0dc7689ad82b112dbc032cea19f91d6b8e3582b9" dependencies = [ "find-msvc-tools", "shlex", @@ -546,6 +537,13 @@ dependencies = [ "serde_json", ] +[[package]] +name = "credit-scoring" +version = "0.1.0" +dependencies = [ + "soroban-sdk 27.0.6", +] + [[package]] name = "crypto-bigint" version = "0.5.5" @@ -568,16 +566,6 @@ dependencies = [ "typenum", ] -[[package]] -name = "crypto-common" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" -dependencies = [ - "hybrid-array", - "rand_core 0.10.1", -] - [[package]] name = "ctor" version = "0.2.9" @@ -613,25 +601,8 @@ dependencies = [ "cfg-if", "cpufeatures 0.2.17", "curve25519-dalek-derive", - "digest 0.10.7", - "fiat-crypto 0.2.9", - "rustc_version", - "subtle", - "zeroize", -] - -[[package]] -name = "curve25519-dalek" -version = "5.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5eed333089e2e1c1ac8c6c0398e5e2497b4c9926ca6d0365ed1e099afa5bc23" -dependencies = [ - "cfg-if", - "cpufeatures 0.3.1", - "curve25519-dalek-derive", - "digest 0.11.3", - "fiat-crypto 0.3.0", - "rand_core 0.10.1", + "digest", + "fiat-crypto", "rustc_version", "subtle", "zeroize", @@ -790,22 +761,12 @@ version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer 0.10.4", + "block-buffer", "const-oid", - "crypto-common 0.1.6", + "crypto-common", "subtle", ] -[[package]] -name = "digest" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" -dependencies = [ - "block-buffer 0.12.1", - "crypto-common 0.2.2", -] - [[package]] name = "dividend-reinvestment" version = "0.1.0" @@ -861,10 +822,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" dependencies = [ "der", - "digest 0.10.7", + "digest", "elliptic-curve", "rfc6979", - "signature 2.2.0", + "signature", ] [[package]] @@ -874,16 +835,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" dependencies = [ "pkcs8", - "signature 2.2.0", -] - -[[package]] -name = "ed25519" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29fcf32e6c73d1079f83ab4d782de2d81620346a5f38c6237a86a22f8368980a" -dependencies = [ - "signature 3.0.0", + "signature", ] [[package]] @@ -892,26 +844,11 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" dependencies = [ - "curve25519-dalek 4.1.3", - "ed25519 2.2.3", + "curve25519-dalek", + "ed25519", "rand_core 0.6.4", "serde", - "sha2 0.10.9", - "subtle", - "zeroize", -] - -[[package]] -name = "ed25519-dalek" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ebaa1a2bf1290ab3bfe5a7b771d050ebffab2711c19a81691c683a5144a25de" -dependencies = [ - "curve25519-dalek 5.0.0", - "ed25519 3.0.0", - "rand_core 0.10.1", - "sha2 0.11.1", - "signature 3.0.0", + "sha2", "subtle", "zeroize", ] @@ -942,7 +879,7 @@ checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" dependencies = [ "base16ct", "crypto-bigint", - "digest 0.10.7", + "digest", "ff", "generic-array", "group", @@ -1013,13 +950,6 @@ dependencies = [ "soroban-sdk 27.0.6", ] -[[package]] -name = "fee-treasury" -version = "0.1.0" -dependencies = [ - "soroban-sdk 27.0.6", -] - [[package]] name = "ff" version = "0.13.1" @@ -1036,17 +966,11 @@ version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" -[[package]] -name = "fiat-crypto" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64cd1e32ddd350061ae6edb1b082d7c54915b5c672c389143b9a63403a109f24" - [[package]] name = "find-msvc-tools" -version = "0.1.15" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c66915cc5391097de7a2b0b972e8505aa33c0df401fd3a6811dbdb32881ba48" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "fnv" @@ -1056,21 +980,21 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "futures-core" -version = "0.3.34" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" +checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7" [[package]] name = "futures-task" -version = "0.3.34" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" +checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109" [[package]] name = "futures-util" -version = "0.3.34" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" +checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa" dependencies = [ "futures-core", "futures-task", @@ -1116,9 +1040,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.32.3" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" +checksum = "1033caf0b349c518623b5396bfb2cf0bddf44f0306d543a250e5743297aafd10" [[package]] name = "grace-interest" @@ -1205,16 +1129,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest 0.10.7", -] - -[[package]] -name = "hybrid-array" -version = "0.4.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17" -dependencies = [ - "typenum", + "digest", ] [[package]] @@ -1370,6 +1285,13 @@ dependencies = [ "soroban-sdk 27.0.6", ] +[[package]] +name = "invoice-milestone" +version = "0.1.0" +dependencies = [ + "soroban-sdk 27.0.6", +] + [[package]] name = "invoice-performance-alert" version = "0.1.0" @@ -1499,9 +1421,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.106" +version = "0.3.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7883d941dae510fb2d978fc3fe018c71c9e2892fd38854de3e8b92c2e5ad9cc5" +checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" dependencies = [ "cfg-if", "futures-util", @@ -1517,7 +1439,7 @@ dependencies = [ "cfg-if", "ecdsa", "elliptic-curve", - "sha2 0.10.9", + "sha2", ] [[package]] @@ -1556,9 +1478,9 @@ checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "log" -version = "0.4.34" +version = "0.4.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" +checksum = "5d15f86d12da44cd8d4ec3c73984537eb7f4cadcb9776a575a0a8a05ca11005c" [[package]] name = "loyalty-tier" @@ -1586,9 +1508,9 @@ checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" [[package]] name = "miniz_oxide" -version = "0.8.9" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +checksum = "b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c" dependencies = [ "adler2", ] @@ -1654,9 +1576,9 @@ dependencies = [ [[package]] name = "object" -version = "0.37.3" +version = "0.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +checksum = "dd229a0361b9d0d4396176e02d65897f487eebeab7caa6d443855ee152ca0b9c" dependencies = [ "memchr", ] @@ -1681,6 +1603,13 @@ dependencies = [ "soroban-sdk 27.0.6", ] +[[package]] +name = "overcollateral-reward" +version = "0.1.0" +dependencies = [ + "soroban-sdk 27.0.6", +] + [[package]] name = "p256" version = "0.13.2" @@ -1690,7 +1619,7 @@ dependencies = [ "ecdsa", "elliptic-curve", "primeorder", - "sha2 0.10.9", + "sha2", ] [[package]] @@ -2046,6 +1975,13 @@ dependencies = [ "zeroize", ] +[[package]] +name = "secondary-cooldown" +version = "0.1.0" +dependencies = [ + "soroban-sdk 27.0.6", +] + [[package]] name = "secondary-market" version = "0.1.0" @@ -2053,6 +1989,13 @@ dependencies = [ "soroban-sdk 27.0.6", ] +[[package]] +name = "secondary-market-oracle" +version = "0.1.0" +dependencies = [ + "soroban-sdk 27.0.6", +] + [[package]] name = "secondary-trade-history" version = "0.1.0" @@ -2151,18 +2094,7 @@ checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures 0.2.17", - "digest 0.10.7", -] - -[[package]] -name = "sha2" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47d7069beb7d6ac7b9acd1039986e73443f24234f41074da099d6f994ac9ad19" -dependencies = [ - "cfg-if", - "cpufeatures 0.3.1", - "digest 0.11.3", + "digest", ] [[package]] @@ -2171,7 +2103,7 @@ version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77fd7028345d415a4034cf8777cd4f8ab1851274233b45f84e3d955502d93874" dependencies = [ - "digest 0.10.7", + "digest", "keccak", ] @@ -2187,19 +2119,10 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ - "digest 0.10.7", + "digest", "rand_core 0.6.4", ] -[[package]] -name = "signature" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d567dcbaf0049cb8ac2608a76cd95ff9e4412e1899d389ee400918ca7537f5" -dependencies = [ - "rand_core 0.10.1", -] - [[package]] name = "slab" version = "0.4.12" @@ -2208,9 +2131,9 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" -version = "1.16.3" +version = "1.16.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b3dc8af474f516a851ff4bd12db780f948b9250ad37211e4eec0bccea54e01b" +checksum = "d0d5d4b1981b8bbbb7aed204cae5726d683d5d6ac7e74ec54122ea28f315e7c8" [[package]] name = "soroban-builtin-sdk-macros" @@ -2301,9 +2224,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b7a32c28f281c423189f1298960194f0e0fc4eeb72378028171e556d8cd6160" dependencies = [ "backtrace", - "curve25519-dalek 5.0.0", + "curve25519-dalek", "ecdsa", - "ed25519-dalek 3.0.0", + "ed25519-dalek", "elliptic-curve", "generic-array", "getrandom 0.2.17", @@ -2317,7 +2240,7 @@ dependencies = [ "rand 0.8.8", "rand_chacha", "sec1", - "sha2 0.10.9", + "sha2", "sha3", "soroban-builtin-sdk-macros 21.2.1", "soroban-env-common 21.2.1", @@ -2338,9 +2261,9 @@ dependencies = [ "ark-ec", "ark-ff", "ark-serialize", - "curve25519-dalek 5.0.0", + "curve25519-dalek", "ecdsa", - "ed25519-dalek 2.2.0", + "ed25519-dalek", "elliptic-curve", "generic-array", "getrandom 0.2.17", @@ -2354,7 +2277,7 @@ dependencies = [ "rand 0.8.8", "rand_chacha", "sec1", - "sha2 0.10.9", + "sha2", "sha3", "soroban-builtin-sdk-macros 27.0.1", "soroban-env-common 27.0.1", @@ -2432,7 +2355,7 @@ dependencies = [ "bytes-lit 0.0.5", "ctor 0.2.9", "derive_arbitrary", - "ed25519-dalek 2.2.0", + "ed25519-dalek", "rand 0.8.8", "rustc_version", "serde", @@ -2455,7 +2378,7 @@ dependencies = [ "crate-git-revision 0.0.9", "ctor 0.5.0", "derive_arbitrary", - "ed25519-dalek 2.2.0", + "ed25519-dalek", "rand 0.8.8", "rustc_version", "serde", @@ -2480,7 +2403,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "sha2 0.10.9", + "sha2", "soroban-env-common 21.2.1", "soroban-spec 21.7.7", "soroban-spec-rust 21.7.7", @@ -2500,7 +2423,7 @@ dependencies = [ "macro-string", "proc-macro2", "quote", - "sha2 0.10.9", + "sha2", "soroban-env-common 27.0.1", "soroban-spec 27.0.6", "soroban-spec-rust 27.0.6", @@ -2527,7 +2450,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "473404322827b285cbcd87517f365986bd63af7842c78b2a86ee061715fda61e" dependencies = [ "base64 0.22.1", - "sha2 0.10.9", + "sha2", "stellar-xdr 27.0.0", "thiserror 1.0.69", "wasmparser", @@ -2542,7 +2465,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "sha2 0.10.9", + "sha2", "soroban-spec 21.7.7", "stellar-xdr 21.2.0", "syn 2.0.119", @@ -2558,7 +2481,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "sha2 0.10.9", + "sha2", "soroban-spec 27.0.6", "stellar-xdr 27.0.0", "syn 2.0.119", @@ -2676,7 +2599,7 @@ dependencies = [ "hex", "serde", "serde_with", - "sha2 0.10.9", + "sha2", "stellar-strkey 0.0.13", ] @@ -2833,6 +2756,13 @@ dependencies = [ "winnow", ] +[[package]] +name = "transfer-restriction" +version = "0.1.0" +dependencies = [ + "soroban-sdk 27.0.6", +] + [[package]] name = "treasury-management" version = "0.1.0" @@ -2899,9 +2829,9 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasm-bindgen" -version = "0.2.129" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409" +checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" dependencies = [ "cfg-if", "once_cell", @@ -2912,9 +2842,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.129" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e29d0c35b16e224a7eeb5cd2d25e3e1968fbd65604117b44d3b789d00ee8535" +checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2922,22 +2852,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.129" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7" +checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 3.0.9", + "syn 2.0.119", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.129" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6" +checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" dependencies = [ "unicode-ident", ] diff --git a/contracts/invoice-escrow/src/events.rs b/contracts/invoice-escrow/src/events.rs index 2b9536c..f6966cf 100644 --- a/contracts/invoice-escrow/src/events.rs +++ b/contracts/invoice-escrow/src/events.rs @@ -176,6 +176,27 @@ pub fn early_settlement_applied( ); } +/// Investment made event (#518): emitted when an investor locks funds into +/// an invoice's escrow via `invest`. `investment_topped_up` below covers a +/// separate, legacy top-up path; this is the primary event for the +/// multi-investor `FundingInvoice` flow. +pub fn investment_made( + env: &Env, + investor: &Address, + invoice_id: BytesN<32>, + amount: i128, + new_total_position: i128, +) { + env.events().publish( + ( + Symbol::new(env, "investment_made"), + investor.clone(), + invoice_id.clone(), + ), + (amount, new_total_position), + ); +} + /// Investment topped up event. pub fn investment_topped_up( env: &Env, diff --git a/contracts/invoice-escrow/src/lib.rs b/contracts/invoice-escrow/src/lib.rs index 50d4d02..c7a2bf1 100644 --- a/contracts/invoice-escrow/src/lib.rs +++ b/contracts/invoice-escrow/src/lib.rs @@ -1751,6 +1751,7 @@ impl InvoiceEscrow { min_investment, per_investor_cap, token, + None, ) } @@ -1767,6 +1768,7 @@ impl InvoiceEscrow { min_investment: i128, per_investor_cap: Option, token: Address, + fraction_token: Option
, ) -> Result<(), Error> { seller.require_auth(); Self::create_invoice_core( @@ -1780,6 +1782,7 @@ impl InvoiceEscrow { min_investment, per_investor_cap, token, + fraction_token, ) } @@ -1795,6 +1798,7 @@ impl InvoiceEscrow { min_investment: i128, per_investor_cap: Option, token: Address, + fraction_token: Option
, ) -> Result<(), Error> { if funding_target <= 0 { return Err(Error::InvalidAmount); @@ -1827,6 +1831,7 @@ impl InvoiceEscrow { min_funding_threshold, max_funding_threshold, threshold_reached: false, + fraction_token, }; storage::set_invoice(env, invoice_id.clone(), &invoice); events::funding_thresholds_set( @@ -2190,6 +2195,7 @@ impl InvoiceEscrow { // be paid out of other investors' principal or issuer collateral. storage::set_investor_position(&env, &invoice_id, &investor, new_pos); investor_cap::sync_holder(&env, &invoice_id, &investor); + let fraction_token = invoice.fraction_token.clone(); invoice.total_raised = new_total; storage::set_invoice(&env, invoice_id.clone(), &invoice); if returned > 0 { @@ -2202,6 +2208,27 @@ impl InvoiceEscrow { returned, ); } + + // Mint fraction tokens to the investor to represent their + // ownership share (#517), same pattern as the legacy + // create_escrow_with_tokens path's inv_token mint below. Scaled + // to `accepted`, not the originally requested `amount`: any + // `returned` remainder above the funding cap was refunded to + // the investor and must not also get a fraction-token claim. + if let Some(fraction_token) = fraction_token { + env.invoke_contract::<()>( + &fraction_token, + &Symbol::new(&env, "mint"), + soroban_sdk::vec![ + &env, + investor.to_val(), + accepted.into_val(&env), + env.current_contract_address().to_val() + ], + ); + } + + events::investment_made(&env, &investor, invoice_id, accepted, new_pos); return Ok(()); } diff --git a/contracts/invoice-escrow/src/test.rs b/contracts/invoice-escrow/src/test.rs index 79ec1f0..d03558c 100644 --- a/contracts/invoice-escrow/src/test.rs +++ b/contracts/invoice-escrow/src/test.rs @@ -1,11 +1,13 @@ #![allow(deprecated, unused_variables, dead_code, unused_mut, clippy::all)] use super::*; +use invoice_token::{InvoiceToken, InvoiceTokenClient}; use soroban_sdk::testutils::{Address as _, Events, Ledger}; use soroban_sdk::token::Client as TokenClient; use soroban_sdk::token::StellarAssetClient as AssetClient; use soroban_sdk::{ - contract, contractimpl, Address, BytesN, Env, IntoVal, Symbol, TryFromVal, TryIntoVal, Val, Vec, + contract, contractimpl, Address, BytesN, Env, IntoVal, String as SorobanString, Symbol, + TryFromVal, TryIntoVal, Val, Vec, }; /// Helper function to create a test commitment hash (SHA-256 format) @@ -11029,3 +11031,127 @@ fn issue455_fill_to_target_under_cap_pressure() { EscrowStatus::Funded ); } + +/// `invest` on the multi-investor `FundingInvoice` flow (created via +/// `create_invoice`, distinct from the legacy `register_invoice`/ +/// `EscrowStatus` path) silently emitted no event at all — every other +/// state-changing call in this contract emits one (#518's acceptance +/// criteria: "Events emitted correctly for all state transitions"). Covers +/// the fix: `investment_made` is published with the investor, invoice id, +/// amount, and the investor's new total position. +#[test] +fn test_invest_on_funding_invoice_emits_investment_made_event() { + let env = Env::default(); + env.mock_all_auths(); + let contract_id = env.register_contract(None, InvoiceEscrow); + let client = InvoiceEscrowClient::new(&env, &contract_id); + + let admin = Address::generate(&env); + let seller = Address::generate(&env); + let investor = Address::generate(&env); + let invoice_id = BytesN::from_array(&env, &[200u8; 32]); + + let token_admin = Address::generate(&env); + let token_id = env.register_stellar_asset_contract_v2(token_admin.clone()); + let token = TokenClient::new(&env, &token_id.address()); + let token_asset = AssetClient::new(&env, &token_id.address()); + token_asset.mint(&investor, &100_000i128); + + client.initialize(&admin, &300); + client.create_invoice_legacy( + &invoice_id, + &seller, + &100_000i128, + &1_000u32, + &1_000i128, + &None, + &token_id.address(), + &None, + ); + + client.invest(&invoice_id, &investor, &40_000i128); + + assert_eq!(token.balance(&investor), 60_000i128); + + let events = env.events().all(); + let event = events + .events() + .iter() + .rev() + .find(|e| { + let (_, topics, _) = parse_event(&env, e); + topics + .get(0) + .map(|t| { + Symbol::try_from_val(&env, &t).unwrap() == Symbol::new(&env, "investment_made") + }) + .unwrap_or(false) + }) + .expect("expected investment_made event"); + + let (_contract_addr, topics, data) = parse_event(&env, event); + assert_eq!( + topics, + ( + Symbol::new(&env, "investment_made"), + investor.clone(), + invoice_id.clone(), + ) + .into_val(&env) + ); + let event_data: (i128, i128) = data.try_into_val(&env).unwrap(); + assert_eq!(event_data.0, 40_000i128); // amount invested + assert_eq!(event_data.1, 40_000i128); // new total position +} + +/// `invest` on a `FundingInvoice` created with a `fraction_token` set +/// mints the investor that many fraction tokens on the configured +/// invoice-token contract (#517), so their ownership share has an +/// on-chain representation, not just an internal storage entry. +#[test] +fn test_invest_mints_fraction_tokens_when_fraction_token_configured() { + let env = Env::default(); + env.mock_all_auths(); + let escrow_id = env.register_contract(None, InvoiceEscrow); + let escrow = InvoiceEscrowClient::new(&env, &escrow_id); + + let admin = Address::generate(&env); + let seller = Address::generate(&env); + let investor = Address::generate(&env); + let invoice_id = BytesN::from_array(&env, &[201u8; 32]); + + let payment_token_admin = Address::generate(&env); + let payment_token_id = env.register_stellar_asset_contract_v2(payment_token_admin.clone()); + let payment_token_asset = AssetClient::new(&env, &payment_token_id.address()); + payment_token_asset.mint(&investor, &100_000i128); + + let fraction_token_id = env.register_contract(None, InvoiceToken); + let fraction_token = InvoiceTokenClient::new(&env, &fraction_token_id); + fraction_token.initialize( + &admin, + &SorobanString::from_str(&env, "Invoice Fraction"), + &SorobanString::from_str(&env, "IFRAC"), + &7u32, + &Symbol::new(&env, "inv201"), + &escrow_id, // the escrow contract itself is the authorized minter + ); + + escrow.initialize(&admin, &300); + escrow.create_invoice_legacy( + &invoice_id, + &seller, + &100_000i128, + &1_000u32, + &1_000i128, + &None, + &payment_token_id.address(), + &Some(fraction_token_id.clone()), + ); + + escrow.invest(&invoice_id, &investor, &40_000i128); + + assert_eq!(fraction_token.balance(&investor), 40_000i128); + + escrow.invest(&invoice_id, &investor, &10_000i128); + assert_eq!(fraction_token.balance(&investor), 50_000i128); +} diff --git a/contracts/invoice-escrow/src/test_buy_limit.rs b/contracts/invoice-escrow/src/test_buy_limit.rs index dfacee8..4a7f72e 100644 --- a/contracts/invoice-escrow/src/test_buy_limit.rs +++ b/contracts/invoice-escrow/src/test_buy_limit.rs @@ -29,6 +29,10 @@ fn setup<'a>() -> Market<'a> { per_investor_cap: None, status: InvoiceStatus::Funded, token: Address::generate(&env), + min_funding_threshold: 1_000, + max_funding_threshold: 1_000, + threshold_reached: true, + fraction_token: None, }; storage::set_invoice(&env, invoice_id.clone(), &invoice); }); diff --git a/contracts/invoice-escrow/src/types.rs b/contracts/invoice-escrow/src/types.rs index 88b1d0e..cbd3736 100644 --- a/contracts/invoice-escrow/src/types.rs +++ b/contracts/invoice-escrow/src/types.rs @@ -319,6 +319,12 @@ pub struct FundingInvoice { /// Whether `ThresholdReached` has already been emitted for this invoice. /// Guarantees the event fires exactly once per invoice. pub threshold_reached: bool, + /// Optional fraction-token contract address (#517): when set, `invest` + /// mints the investor a matching amount of fraction tokens on this + /// contract so their ownership share has an on-chain representation. + /// `None` preserves the pre-#517 behaviour (payment-only, no fraction + /// tokens) for invoices that don't need one. + pub fraction_token: Option, } /// Funding progress view returned by `get_funding_status`. diff --git a/contracts/invoice-escrow/tests/institutional_controls.rs b/contracts/invoice-escrow/tests/institutional_controls.rs index 9ad068c..275283f 100644 --- a/contracts/invoice-escrow/tests/institutional_controls.rs +++ b/contracts/invoice-escrow/tests/institutional_controls.rs @@ -483,8 +483,9 @@ fn collateral_breach_blocks_creation_until_a_real_topup_restores_ratio() { Err(Ok(Error::CollateralTopUpRequired)) ); assert_eq!( - f.client - .try_create_invoice_legacy(&next, &f.issuer, &1000, &1000, &0, &None, &f.token), + f.client.try_create_invoice_legacy( + &next, &f.issuer, &1000, &1000, &0, &None, &f.token, &None, + ), Err(Ok(Error::CollateralTopUpRequired)) ); f.client.top_up_collateral(&f.id, &50); diff --git a/contracts/invoice-escrow/tests/thresholds.rs b/contracts/invoice-escrow/tests/thresholds.rs index 11b07d5..146de42 100644 --- a/contracts/invoice-escrow/tests/thresholds.rs +++ b/contracts/invoice-escrow/tests/thresholds.rs @@ -285,8 +285,16 @@ fn test_legacy_create_invoice_defaults_min_and_max_to_target() { let investor = Address::generate(&env); mint(&env, &f.token, &investor, 500_000); - f.client - .create_invoice_legacy(&id, &f.seller, &80_000i128, &1000u32, &0i128, &None, &f.token); + f.client.create_invoice_legacy( + &id, + &f.seller, + &80_000i128, + &1000u32, + &0i128, + &None, + &f.token, + &None, + ); let status = f.client.get_funding_status(&id); assert_eq!(status.min_threshold, 80_000);