diff --git a/.env.example b/.env.example index ab210ee82..72d7b4621 100644 --- a/.env.example +++ b/.env.example @@ -8,6 +8,10 @@ ETHERSCAN_API_KEY= HERMES_RA2_NODE_URL=https://hermes.pyth.network/ +CHAINLINK_DATA_STREAMS_API_URL=https://api.dataengine.chain.link +CHAINLINK_DATA_STREAMS_API_KEY= +CHAINLINK_DATA_STREAMS_API_SECRET= + # Replace with URL of custom trufflehog detector for EVM secrets TRUFFLEHOG_URL=http://... @@ -29,6 +33,10 @@ CHAINLINK_GAS_PRICE_ADDRESS= CHAINLINK_GAS_PRICE_VALIDITY= GET_WSTETH= -# Optional variables for transfering ownership +# Optional variables for fork deployment +UNDERLYING_ADDRESS= +START_PRICE= + +# Optional variables for transferring ownership NEW_OWNER_ADDRESS= USDN_PROTOCOL_ADDRESS= diff --git a/.github/workflows/heavy_ci.yml b/.github/workflows/heavy_ci.yml index 7d7399703..4f5ef0cb5 100644 --- a/.github/workflows/heavy_ci.yml +++ b/.github/workflows/heavy_ci.yml @@ -9,6 +9,9 @@ env: FOUNDRY_PROFILE: heavy_ci URL_ETH_MAINNET: ${{ secrets.URL_ETH_MAINNET }} HERMES_RA2_NODE_URL: ${{ secrets.HERMES_RA2_NODE_URL }} + CHAINLINK_DATA_STREAMS_API_URL: ${{ secrets.CHAINLINK_DATA_STREAMS_API_URL }} + CHAINLINK_DATA_STREAMS_API_KEY: ${{ secrets.CHAINLINK_DATA_STREAMS_API_KEY }} + CHAINLINK_DATA_STREAMS_API_SECRET: ${{ secrets.CHAINLINK_DATA_STREAMS_API_SECRET }} jobs: heavy-ci: diff --git a/.github/workflows/natspec.yml b/.github/workflows/natspec.yml index 365424108..175d82da4 100644 --- a/.github/workflows/natspec.yml +++ b/.github/workflows/natspec.yml @@ -17,7 +17,7 @@ jobs: uses: beeb/lintspec@main with: fail-on-problem: "false" # we handle failure manually to be able to trigger the notification - version: "0.3.0" + version: "0.4.1" - name: Fail on findings if: ${{ steps.lintspec-action.outputs.total-diags > 0 }} run: exit 1 diff --git a/.github/workflows/sync_template.yml b/.github/workflows/sync_template.yml index 76d3b27e3..7c743e894 100644 --- a/.github/workflows/sync_template.yml +++ b/.github/workflows/sync_template.yml @@ -17,9 +17,9 @@ jobs: - uses: actions/create-github-app-token@v1 id: app-token with: - app-id: ${{ secrets.APP_ID }} - private-key: ${{ secrets.APP_PRIVATE_KEY }} - repositories: "${{ github.event.repository.name }},foundry-template" + app-id: ${{ secrets.FOUNDRY_TEMPLATE_APP_ID }} + private-key: ${{ secrets.FOUNDRY_TEMPLATE_APP_PRIVATE_KEY }} + owner: "Blockchain-RA2-Tech" - name: Checkout uses: actions/checkout@v4 @@ -31,6 +31,7 @@ jobs: with: source_gh_token: ${{ steps.app-token.outputs.token }} source_repo_path: Blockchain-RA2-Tech/foundry-template + target_gh_token: ${{ secrets.GITHUB_TOKEN }} upstream_branch: main # PR settings is_pr_cleanup: true # will remove previous update PR (if not merged) and create a new one diff --git a/.gitignore b/.gitignore index ea0ed1174..ea8d12569 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,7 @@ docs/ # IDE .idea/ +.vscode/ # Removed for now, might enable after v1.0 .gas-snapshot diff --git a/.lintspec.toml b/.lintspec.toml index e3c73778a..0432024b1 100644 --- a/.lintspec.toml +++ b/.lintspec.toml @@ -1,10 +1,17 @@ +[lintspec] paths = ["src"] exclude = [ "src/OracleMiddleware/mock", "src/Usdn/Usdn.sol", "src/UsdnProtocol/libraries", ] -constructor = true -struct_params = true -enum_params = true -enforce_all = true +notice_or_dev = true + +[enum] +param = "required" + +[struct] +param = "required" + +[variable.public] +return = "ignored" diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 8d7e5f136..5fdd88304 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "1.0.1" + ".": "1.1.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 2262076e4..de0da80f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,54 @@ # Changelog +## [1.1.0](https://github.com/SmarDex-Ecosystem/usdn-contracts/compare/v1.0.1...v1.1.0) (2025-03-27) + + +### ⚠ BREAKING CHANGES + +* The UsdnProtocolFallback contract now requires 2 parameters to be deployed + +### Features + +* add a version of the USDN token with no rebase ([#878](https://github.com/SmarDex-Ecosystem/usdn-contracts/issues/878)) ([bb798f3](https://github.com/SmarDex-Ecosystem/usdn-contracts/commit/bb798f31f88e45d4ffdedc9c2bcd3031d36a859f)) +* deploy SetRebaseHandlerManager ([#869](https://github.com/SmarDex-Ecosystem/usdn-contracts/issues/869)) ([33fd954](https://github.com/SmarDex-Ecosystem/usdn-contracts/commit/33fd9546e9c6bdea452172bcce044ef90f9c4fcf)) +* deployment configuration file ([#876](https://github.com/SmarDex-Ecosystem/usdn-contracts/issues/876)) ([6060feb](https://github.com/SmarDex-Ecosystem/usdn-contracts/commit/6060feb476a51294dd6d25d452cceca41961cc64)) +* **middleware:** add shortDN middleware using Chainlink Data Streams ([#892](https://github.com/SmarDex-Ecosystem/usdn-contracts/issues/892)) ([7739a52](https://github.com/SmarDex-Ecosystem/usdn-contracts/commit/7739a525d0ad6a654f3943a3a1ae7fc2bbccfe29)) +* **middleware:** chainlink data streams middleware ([#879](https://github.com/SmarDex-Ecosystem/usdn-contracts/issues/879)) ([2dff4ca](https://github.com/SmarDex-Ecosystem/usdn-contracts/commit/2dff4ca8ded13214a380c056a5854bbfa03757dc)) +* **middleware:** short oracle middleware ([#875](https://github.com/SmarDex-Ecosystem/usdn-contracts/issues/875)) ([5550996](https://github.com/SmarDex-Ecosystem/usdn-contracts/commit/555099647ffc0efc75e1930ffa00e64d6c78ae89)) +* new liquidation rewards manager for short DN ([#882](https://github.com/SmarDex-Ecosystem/usdn-contracts/issues/882)) ([1bea0c3](https://github.com/SmarDex-Ecosystem/usdn-contracts/commit/1bea0c32d9a265a6341e9a78556eef864a729b6b)) +* **script:** set all current parameters to a new protocol ([#884](https://github.com/SmarDex-Ecosystem/usdn-contracts/issues/884)) ([eb382e0](https://github.com/SmarDex-Ecosystem/usdn-contracts/commit/eb382e09a9b6b35bdb07771a72585cbb94a86742)) +* set asset bound setters limits on deployment ([#889](https://github.com/SmarDex-Ecosystem/usdn-contracts/issues/889)) ([f30d98a](https://github.com/SmarDex-Ecosystem/usdn-contracts/commit/f30d98a47c853ee7a7c4325d2224a03aa5f5b9f2)) +* SetRebaseHandlerManager contract ([#862](https://github.com/SmarDex-Ecosystem/usdn-contracts/issues/862)) ([b96e510](https://github.com/SmarDex-Ecosystem/usdn-contracts/commit/b96e5100a1ab1a8a103e48b56536ef76f6ef85e9)) + + +### Bug Fixes + +* fork deployment ([#870](https://github.com/SmarDex-Ecosystem/usdn-contracts/issues/870)) ([0c69af9](https://github.com/SmarDex-Ecosystem/usdn-contracts/commit/0c69af964207b4bf8345807d6abd6ec656d4c8e2)) +* jsr exports path ([#865](https://github.com/SmarDex-Ecosystem/usdn-contracts/issues/865)) ([c34d8ce](https://github.com/SmarDex-Ecosystem/usdn-contracts/commit/c34d8cedc8fa0d276183de9dbbe4a122146062b1)) +* remove backend edits ([#873](https://github.com/SmarDex-Ecosystem/usdn-contracts/issues/873)) ([c7f5bd0](https://github.com/SmarDex-Ecosystem/usdn-contracts/commit/c7f5bd0e3c5aaf48151045d35d0523c7264f4dc8)) + + +### Miscellaneous Chores + +* adjust lintspec config ([#880](https://github.com/SmarDex-Ecosystem/usdn-contracts/issues/880)) ([2a151a5](https://github.com/SmarDex-Ecosystem/usdn-contracts/commit/2a151a5843be242d3bee2bef09fd069737ba4db6)) +* deployment script and config for the `WusdnToEth` USDN ([#881](https://github.com/SmarDex-Ecosystem/usdn-contracts/issues/881)) ([a659133](https://github.com/SmarDex-Ecosystem/usdn-contracts/commit/a6591339d59477657aff372dc4a8cd67b63b7966)) +* force release version ([#894](https://github.com/SmarDex-Ecosystem/usdn-contracts/issues/894)) ([5346d14](https://github.com/SmarDex-Ecosystem/usdn-contracts/commit/5346d14f687b29e6d49abe410a5a913aeb0909e8)) +* fork deployment script ([#886](https://github.com/SmarDex-Ecosystem/usdn-contracts/issues/886)) ([f42abef](https://github.com/SmarDex-Ecosystem/usdn-contracts/commit/f42abefac310cded88366d827e12f2d7bef58102)) +* replace natspec-smells with lintspec ([#877](https://github.com/SmarDex-Ecosystem/usdn-contracts/issues/877)) ([eb7905a](https://github.com/SmarDex-Ecosystem/usdn-contracts/commit/eb7905a5d3db1f1afa3fc2f298fd13246321d505)) +* **template:** sync from template ([#871](https://github.com/SmarDex-Ecosystem/usdn-contracts/issues/871)) ([a45bbd1](https://github.com/SmarDex-Ecosystem/usdn-contracts/commit/a45bbd10dae6f0508c39c793ed853cf6798b065f)) +* update lintspec ([#887](https://github.com/SmarDex-Ecosystem/usdn-contracts/issues/887)) ([309f47c](https://github.com/SmarDex-Ecosystem/usdn-contracts/commit/309f47cdc0a19128a14e0e0e5e4577d14d650d4c)) + + +### Code Refactoring + +* **script:** split the prod/fork deployments ([#874](https://github.com/SmarDex-Ecosystem/usdn-contracts/issues/874)) ([9f0d931](https://github.com/SmarDex-Ecosystem/usdn-contracts/commit/9f0d9317c7d9c774418aa1e76c9819c66b466b9f)) + + +### Build System + +* **cargo:** update dependencies ([#885](https://github.com/SmarDex-Ecosystem/usdn-contracts/issues/885)) ([fa5a256](https://github.com/SmarDex-Ecosystem/usdn-contracts/commit/fa5a256b629ccf3dc93b3aff17cbe4acfc1bf12b)) +* **nix:** use stable foundry version ([#867](https://github.com/SmarDex-Ecosystem/usdn-contracts/issues/867)) ([4451dab](https://github.com/SmarDex-Ecosystem/usdn-contracts/commit/4451dab7cf4266cd5f1306740691715a4d577226)) + ## [1.0.1](https://github.com/SmarDex-Ecosystem/usdn-contracts/compare/v1.0.0...v1.0.1) (2025-02-07) diff --git a/Cargo.lock b/Cargo.lock index 7d526bcf8..2c0c726e6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,18 +1,27 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 4 +version = 3 [[package]] -name = "adler2" -version = "2.0.0" +name = "addr2line" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "alloy-primitives" -version = "0.7.7" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccb3ead547f4532bc8af961649942f0b9c16ee9226e26caa3f38420651cc0bf4" +checksum = "f783611babedbbe90db3478c120fb5f5daacceffc210b39adc0af4fe0da70bad" dependencies = [ "alloy-rlp", "bytes", @@ -32,9 +41,9 @@ dependencies = [ [[package]] name = "alloy-rlp" -version = "0.3.11" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6c1d995bff8d011f7cd6c81820d51825e6e06d6db73914c1630ecf544d83d6" +checksum = "b155716bab55763c95ba212806cf43d05bcc70e5f35b02bad20cf5ec7fe11fed" dependencies = [ "arrayvec", "bytes", @@ -42,23 +51,23 @@ dependencies = [ [[package]] name = "alloy-sol-macro" -version = "0.7.7" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b40397ddcdcc266f59f959770f601ce1280e699a91fc1862f29cef91707cd09" +checksum = "4bad41a7c19498e3f6079f7744656328699f8ea3e783bdd10d85788cd439f572" dependencies = [ "alloy-sol-macro-expander", "alloy-sol-macro-input", "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.67", ] [[package]] name = "alloy-sol-macro-expander" -version = "0.7.7" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "867a5469d61480fea08c7333ffeca52d5b621f5ca2e44f271b117ec1fc9a0525" +checksum = "fd9899da7d011b4fe4c406a524ed3e3f963797dbc93b45479d60341d3a27b252" dependencies = [ "alloy-sol-macro-input", "const-hex", @@ -67,31 +76,31 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.67", "syn-solidity", "tiny-keccak", ] [[package]] name = "alloy-sol-macro-input" -version = "0.7.7" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e482dc33a32b6fadbc0f599adea520bd3aaa585c141a80b404d0a3e3fa72528" +checksum = "d32d595768fdc61331a132b6f65db41afae41b9b97d36c21eb1b955c422a7e60" dependencies = [ "const-hex", "dunce", "heck", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.67", "syn-solidity", ] [[package]] name = "alloy-sol-types" -version = "0.7.7" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a91ca40fa20793ae9c3841b83e74569d1cc9af29a2f5237314fd3452d51e38c7" +checksum = "a49042c6d3b66a9fe6b2b5a8bf0d39fc2ae1ee0310a2a26ffedd79fb097878dd" dependencies = [ "alloy-primitives", "alloy-sol-macro", @@ -101,9 +110,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.18" +version = "0.6.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" dependencies = [ "anstyle", "anstyle-parse", @@ -116,44 +125,43 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.10" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" +checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" [[package]] name = "anstyle-parse" -version = "0.2.6" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.2" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.7" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" +checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" dependencies = [ "anstyle", - "once_cell", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] name = "anyhow" -version = "1.0.97" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" [[package]] name = "ark-ff" @@ -189,7 +197,7 @@ dependencies = [ "num-bigint", "num-traits", "paste", - "rustc_version 0.4.1", + "rustc_version 0.4.0", "zeroize", ] @@ -281,26 +289,26 @@ dependencies = [ [[package]] name = "arrayvec" -version = "0.7.6" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" [[package]] name = "auto_impl" -version = "1.2.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e12882f59de5360c748c4cbf569a042d5fb0eb515f7bea9c1f470b47f6ffbd73" +checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.67", ] [[package]] name = "autocfg" -version = "1.4.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "az" @@ -308,12 +316,33 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973" +[[package]] +name = "backtrace" +version = "0.3.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + [[package]] name = "base16ct" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + [[package]] name = "base64" version = "0.22.1" @@ -322,30 +351,36 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" -version = "1.7.3" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e25b6adfb930f02d1981565a6e5d9c547ac15a96606256d3b59040e5cd4ca3" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] name = "bit-set" -version = "0.8.0" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" dependencies = [ "bit-vec", ] [[package]] name = "bit-vec" -version = "0.8.0" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + +[[package]] +name = "bitflags" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" [[package]] name = "bitvec" @@ -368,11 +403,17 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + [[package]] name = "byte-slice-cast" -version = "1.2.3" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7575182f7272186991736b70173b0ea045398f984bf5ebbb3804736ce1330c9d" +checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" [[package]] name = "byteorder" @@ -382,18 +423,15 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.10.1" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" [[package]] name = "cc" -version = "1.2.16" +version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c" -dependencies = [ - "shlex", -] +checksum = "96c51067fd44124faa7f870b4b1c969379ad32b2ba805aa959430ceaa384f695" [[package]] name = "cfg-if" @@ -403,9 +441,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.5.32" +version = "4.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6088f3ae8c3608d19260cd7445411865a485688711b78b5be70d78cd96136f83" +checksum = "5db83dced34638ad474f39f250d7fea9598bdd239eaced1bdf45d597da0f433f" dependencies = [ "clap_builder", "clap_derive", @@ -413,9 +451,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.32" +version = "4.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22a7ef7f676155edfb82daa97f99441f3ebf4a58d5e32f295a56259f1b6facc8" +checksum = "f7e204572485eb3fbf28f871612191521df159bc3e15a9f5064c66dba3a8c05f" dependencies = [ "anstream", "anstyle", @@ -425,33 +463,33 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.32" +version = "4.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" +checksum = "c780290ccf4fb26629baa7a1081e68ced113f1d3ec302fa5948f1c381ebf06c6" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.67", ] [[package]] name = "clap_lex" -version = "0.7.4" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" +checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" [[package]] name = "colorchoice" -version = "1.0.3" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" [[package]] name = "const-hex" -version = "1.14.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b0485bab839b018a8f1723fc5391819fea5f8f0f32288ef8a735fd096b6160c" +checksum = "94fb8a24a26d37e1ffd45343323dc9fe6654ceea44c12f2fcb3d7ac29e610bc6" dependencies = [ "cfg-if", "cpufeatures", @@ -467,36 +505,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] -name = "const_format" -version = "0.2.34" +name = "convert_case" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "126f97965c8ad46d6d9163268ff28432e8f6a1196a55578867832e3049df63dd" -dependencies = [ - "const_format_proc_macros", -] +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" [[package]] -name = "const_format_proc_macros" -version = "0.2.34" +name = "core-foundation" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" dependencies = [ - "proc-macro2", - "quote", - "unicode-xid", + "core-foundation-sys", + "libc", ] [[package]] -name = "convert_case" -version = "0.4.0" +name = "core-foundation-sys" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.17" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" dependencies = [ "libc", ] @@ -512,9 +546,9 @@ dependencies = [ [[package]] name = "crunchy" -version = "0.2.3" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" [[package]] name = "crypto-bigint" @@ -538,6 +572,48 @@ dependencies = [ "typenum", ] +[[package]] +name = "data-encoding" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e60eed09d8c01d3cee5b7d30acb059b76614c918fa0f992e0dd6eeb10daad6f" + +[[package]] +name = "data-streams-report" +version = "0.0.1" +source = "git+https://github.com/smartcontractkit/data-streams-sdk.git#e0788716cbefd927ff11b23216681593db86470d" +dependencies = [ + "hex", + "num-bigint", + "serde", + "serde_json", + "snap", + "thiserror", +] + +[[package]] +name = "data-streams-sdk" +version = "0.0.1" +source = "git+https://github.com/smartcontractkit/data-streams-sdk.git#e0788716cbefd927ff11b23216681593db86470d" +dependencies = [ + "byteorder", + "data-streams-report", + "futures", + "futures-util", + "hex", + "hmac", + "reqwest", + "serde", + "serde_json", + "serde_urlencoded", + "sha2", + "thiserror", + "tokio", + "tokio-tungstenite", + "tracing", + "zeroize", +] + [[package]] name = "der" version = "0.7.9" @@ -561,15 +637,15 @@ dependencies = [ [[package]] name = "derive_more" -version = "0.99.19" +version = "0.99.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3da29a38df43d6f156149c9b43ded5e018ddff2a855cf2cfd62e8cd7d079c69f" +checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce" dependencies = [ "convert_case", "proc-macro2", "quote", - "rustc_version 0.4.1", - "syn 2.0.100", + "rustc_version 0.4.0", + "syn 2.0.67", ] [[package]] @@ -593,22 +669,11 @@ dependencies = [ "subtle", ] -[[package]] -name = "displaydoc" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.100", -] - [[package]] name = "dunce" -version = "1.0.5" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" +checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" [[package]] name = "ecdsa" @@ -626,9 +691,9 @@ dependencies = [ [[package]] name = "either" -version = "1.15.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" [[package]] name = "elliptic-curve" @@ -649,27 +714,36 @@ dependencies = [ "zeroize", ] +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + [[package]] name = "equivalent" -version = "1.0.2" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.10" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] name = "fastrand" -version = "2.3.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" [[package]] name = "fastrlp" @@ -682,22 +756,11 @@ dependencies = [ "bytes", ] -[[package]] -name = "fastrlp" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce8dba4714ef14b8274c371879b175aa55b16b30f269663f19d576f380018dc4" -dependencies = [ - "arrayvec", - "auto_impl", - "bytes", -] - [[package]] name = "ff" -version = "0.13.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" dependencies = [ "rand_core", "subtle", @@ -717,9 +780,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.1.0" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11faaf5a5236997af9848be0bef4db95824b1d534ebc64d0f0c6cf3e67bd38dc" +checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" dependencies = [ "crc32fast", "miniz_oxide", @@ -731,6 +794,21 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.2.1" @@ -746,6 +824,95 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.67", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -765,20 +932,14 @@ checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", ] [[package]] -name = "getrandom" -version = "0.3.2" +name = "gimli" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" -dependencies = [ - "cfg-if", - "libc", - "r-efi", - "wasi 0.14.2+wasi-0.2.4", -] +checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" [[package]] name = "gmp-mpfr-sys" @@ -801,11 +962,30 @@ dependencies = [ "subtle", ] +[[package]] +name = "h2" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "hashbrown" -version = "0.15.2" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] name = "heck" @@ -813,6 +993,12 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + [[package]] name = "hex" version = "0.4.3" @@ -835,142 +1021,98 @@ dependencies = [ ] [[package]] -name = "icu_collections" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locid" -version = "1.5.0" +name = "http" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", + "bytes", + "fnv", + "itoa", ] [[package]] -name = "icu_locid_transform" -version = "1.5.0" +name = "http-body" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ - "displaydoc", - "icu_locid", - "icu_locid_transform_data", - "icu_provider", - "tinystr", - "zerovec", + "bytes", + "http", + "pin-project-lite", ] [[package]] -name = "icu_locid_transform_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" - -[[package]] -name = "icu_normalizer" -version = "1.5.0" +name = "httparse" +version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "utf16_iter", - "utf8_iter", - "write16", - "zerovec", -] +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" [[package]] -name = "icu_normalizer_data" -version = "1.5.0" +name = "httpdate" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] -name = "icu_properties" -version = "1.5.1" +name = "hyper" +version = "0.14.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" dependencies = [ - "displaydoc", - "icu_collections", - "icu_locid_transform", - "icu_properties_data", - "icu_provider", - "tinystr", - "zerovec", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", ] [[package]] -name = "icu_properties_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" - -[[package]] -name = "icu_provider" -version = "1.5.0" +name = "hyper-rustls" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ - "displaydoc", - "icu_locid", - "icu_provider_macros", - "stable_deref_trait", - "tinystr", - "writeable", - "yoke", - "zerofrom", - "zerovec", + "futures-util", + "http", + "hyper", + "rustls 0.21.12", + "tokio", + "tokio-rustls", ] [[package]] -name = "icu_provider_macros" -version = "1.5.0" +name = "hyper-tls" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.100", + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", ] [[package]] name = "idna" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" -dependencies = [ - "idna_adapter", - "smallvec", - "utf8_iter", -] - -[[package]] -name = "idna_adapter" -version = "1.2.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" dependencies = [ - "icu_normalizer", - "icu_properties", + "unicode-bidi", + "unicode-normalization", ] [[package]] @@ -984,30 +1126,36 @@ dependencies = [ [[package]] name = "impl-trait-for-tuples" -version = "0.2.3" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" +checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 1.0.109", ] [[package]] name = "indexmap" -version = "2.8.0" +version = "2.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", "hashbrown", ] +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + [[package]] name = "is_terminal_polyfill" -version = "1.70.1" +version = "1.70.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" [[package]] name = "itertools" @@ -1020,15 +1168,25 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.15" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "js-sys" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] [[package]] name = "k256" -version = "0.13.4" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" +checksum = "956ff9b67e26e1a6a866cb758f12c6f8746208489e3e4a4b5580802f2f0a587b" dependencies = [ "cfg-if", "ecdsa", @@ -1039,9 +1197,9 @@ dependencies = [ [[package]] name = "keccak-asm" -version = "0.1.4" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "505d1856a39b200489082f90d897c3f07c455563880bc5952e38eabf731c83b6" +checksum = "47a3633291834c4fbebf8673acbc1b04ec9d151418ff9b8e26dcd79129928758" dependencies = [ "digest 0.10.7", "sha3-asm", @@ -1049,39 +1207,43 @@ dependencies = [ [[package]] name = "lazy_static" -version = "1.5.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.171" +version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "libm" -version = "0.2.11" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" [[package]] name = "linux-raw-sys" -version = "0.9.3" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe7db12097d22ec582439daf8618b8fdd1a7bef6270e9af3b1ebcd30893cf413" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] -name = "litemap" -version = "0.7.5" +name = "lock_api" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] [[package]] name = "log" -version = "0.4.26" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" [[package]] name = "memchr" @@ -1089,20 +1251,55 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + [[package]] name = "miniz_oxide" -version = "0.8.5" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" dependencies = [ - "adler2", + "adler", +] + +[[package]] +name = "mio" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +dependencies = [ + "hermit-abi", + "libc", + "wasi", + "windows-sys 0.52.0", +] + +[[package]] +name = "native-tls" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dab59f8e050d5df8e4dd87d9206fb6f65a483e20ac9fda365ade4fab353196c" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", ] [[package]] name = "num-bigint" -version = "0.4.6" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +checksum = "c165a9ab64cf766f73521c0dd2cfdff64f488b8f0b3e621face3462d3db536d7" dependencies = [ "num-integer", "num-traits", @@ -1127,38 +1324,112 @@ dependencies = [ "libm", ] +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell" -version = "1.21.1" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d75b0bedcc4fe52caa0e03d9f1151a323e4aa5e2d78ba3580400cd3c9e2bc4bc" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "openssl" +version = "0.10.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5e534d133a060a3c19daec1eb3e98ec6f4685978834f2dbadfe2ec215bab64e" +dependencies = [ + "bitflags 2.5.0", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.67", +] + +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[package]] +name = "openssl-sys" +version = "0.9.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] [[package]] name = "parity-scale-codec" -version = "3.7.4" +version = "3.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9fde3d0718baf5bc92f577d652001da0f8d54cd03a7974e118d04fc888dc23d" +checksum = "306800abfa29c7f16596b5970a588435e3d5b3149683d00c12b699cc19f895ee" dependencies = [ "arrayvec", "bitvec", "byte-slice-cast", - "const_format", "impl-trait-for-tuples", "parity-scale-codec-derive", - "rustversion", "serde", ] [[package]] name = "parity-scale-codec-derive" -version = "3.7.4" +version = "3.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581c837bb6b9541ce7faa9377c20616e4fb7650f6b0f68bc93c827ee504fb7b3" +checksum = "d830939c76d294956402033aee57a6da7b438f2294eb94864c37b0569053a42c" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.100", + "syn 1.0.109", +] + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.5", ] [[package]] @@ -1175,15 +1446,27 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.15" +version = "2.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b7cafe60d6cf8e62e1b9b2ea516a089c008945bb5a275416789e7db0bc199dc" +checksum = "560131c633294438da9f7c4b08189194b20946c8274c6b9e38881a7874dc8ee8" dependencies = [ "memchr", "thiserror", "ucd-trie", ] +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "pkcs8" version = "0.10.2" @@ -1194,14 +1477,17 @@ dependencies = [ "spki", ] +[[package]] +name = "pkg-config" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" + [[package]] name = "ppv-lite86" -version = "0.2.21" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" -dependencies = [ - "zerocopy", -] +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "primitive-types" @@ -1216,9 +1502,9 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "3.3.0" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" +checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" dependencies = [ "toml_edit", ] @@ -1249,22 +1535,22 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.94" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] [[package]] name = "proptest" -version = "1.6.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14cae93065090804185d3b75f0bf93b8eeda30c7a9b4a33d3bdb3988d6229e50" +checksum = "31b476131c3c86cb68032fdc5cb6d5a1045e3e42d96b69fa599fd77701e1f5bf" dependencies = [ "bit-set", "bit-vec", - "bitflags", + "bitflags 2.5.0", "lazy_static", "num-traits", "rand", @@ -1284,19 +1570,13 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" -version = "1.0.40" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] -[[package]] -name = "r-efi" -version = "5.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" - [[package]] name = "radium" version = "0.7.0" @@ -1330,7 +1610,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.15", + "getrandom", ] [[package]] @@ -1342,11 +1622,64 @@ dependencies = [ "rand_core", ] +[[package]] +name = "redox_syscall" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" +dependencies = [ + "bitflags 2.5.0", +] + [[package]] name = "regex-syntax" -version = "0.8.5" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" + +[[package]] +name = "reqwest" +version = "0.11.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" +dependencies = [ + "base64 0.21.7", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-rustls", + "hyper-tls", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls 0.21.12", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "system-configuration", + "tokio", + "tokio-native-tls", + "tokio-rustls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots 0.25.4", + "winreg", +] [[package]] name = "rfc6979" @@ -1360,14 +1693,15 @@ dependencies = [ [[package]] name = "ring" -version = "0.17.14" +version = "0.17.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", "cfg-if", - "getrandom 0.2.15", + "getrandom", "libc", + "spin", "untrusted", "windows-sys 0.52.0", ] @@ -1384,9 +1718,9 @@ dependencies = [ [[package]] name = "rug" -version = "1.27.0" +version = "1.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4207e8d668e5b8eb574bda8322088ccd0d7782d3d03c7e8d562e82ed82bdcbc3" +checksum = "a8df4099c6fa90a1a7f5ddc0c7fba50991080fa2084d5a78808a5a3cab406bb9" dependencies = [ "az", "gmp-mpfr-sys", @@ -1396,18 +1730,16 @@ dependencies = [ [[package]] name = "ruint" -version = "1.13.1" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "825df406ec217a8116bd7b06897c6cc8f65ffefc15d030ae2c9540acc9ed50b6" +checksum = "2c3cc4c2511671f327125da14133d0c5c5d137f006a1017a16f557bc85b16286" dependencies = [ "alloy-rlp", "ark-ff 0.3.0", "ark-ff 0.4.2", "bytes", - "fastrlp 0.3.1", - "fastrlp 0.4.0", + "fastrlp", "num-bigint", - "num-integer", "num-traits", "parity-scale-codec", "primitive-types", @@ -1426,6 +1758,12 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + [[package]] name = "rustc-hex" version = "2.1.0" @@ -1443,52 +1781,94 @@ dependencies = [ [[package]] name = "rustc_version" -version = "0.4.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver 1.0.26", + "semver 1.0.23", ] [[package]] name = "rustix" -version = "1.0.3" +version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e56a18552996ac8d29ecc3b190b4fdbb2d91ca4ec396de7bbffaf43f3d637e96" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ - "bitflags", + "bitflags 2.5.0", "errno", "libc", "linux-raw-sys", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] name = "rustls" -version = "0.23.25" +version = "0.21.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "822ee9188ac4ec04a2f0531e55d035fb2de73f18b41a63c70c2712503b6fb13c" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" +dependencies = [ + "log", + "ring", + "rustls-webpki 0.101.7", + "sct", +] + +[[package]] +name = "rustls" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" dependencies = [ "log", - "once_cell", "ring", "rustls-pki-types", - "rustls-webpki", + "rustls-webpki 0.102.4", "subtle", "zeroize", ] +[[package]] +name = "rustls-native-certs" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" +dependencies = [ + "openssl-probe", + "rustls-pemfile", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.7", +] + [[package]] name = "rustls-pki-types" -version = "1.11.0" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" + +[[package]] +name = "rustls-webpki" +version = "0.101.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", +] [[package]] name = "rustls-webpki" -version = "0.103.0" +version = "0.102.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0aa4eeac2588ffff23e9d7a7e9b3f971c5fb5b7ebc9452745e0c232c64f83b2f" +checksum = "ff448f7e92e913c4b7d4c6d8e4540a1724b319b4152b8aef6d4cf8339712b33e" dependencies = [ "ring", "rustls-pki-types", @@ -1497,9 +1877,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.20" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" +checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" [[package]] name = "rusty-fork" @@ -1515,9 +1895,34 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.20" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "schannel" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] [[package]] name = "sec1" @@ -1533,6 +1938,29 @@ dependencies = [ "zeroize", ] +[[package]] +name = "security-framework" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0" +dependencies = [ + "bitflags 2.5.0", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "semver" version = "0.11.0" @@ -1544,51 +1972,73 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.26" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "semver-parser" -version = "0.10.3" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9900206b54a3527fdc7b8a938bffd94a568bac4f4aa8113b209df75a09c0dec2" +checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" dependencies = [ "pest", ] [[package]] name = "serde" -version = "1.0.219" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.67", ] [[package]] name = "serde_json" -version = "1.0.140" +version = "1.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" dependencies = [ "itoa", - "memchr", "ryu", "serde", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.7", +] + [[package]] name = "sha2" version = "0.10.8" @@ -1602,19 +2052,22 @@ dependencies = [ [[package]] name = "sha3-asm" -version = "0.1.4" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28efc5e327c837aa837c59eae585fc250715ef939ac32881bcc11677cd02d46" +checksum = "a9b57fd861253bff08bb1919e995f90ba8f4889de2726091c8876f3a4e823b40" dependencies = [ "cc", "cfg-if", ] [[package]] -name = "shlex" -version = "1.3.0" +name = "signal-hook-registry" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] [[package]] name = "signature" @@ -1626,11 +2079,42 @@ dependencies = [ "rand_core", ] +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + [[package]] name = "smallvec" -version = "1.14.0" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "snap" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b6b67fb9a61334225b5b790716f609cd58395f895b3fe8b328786812a40bc3b" + +[[package]] +name = "socket2" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" [[package]] name = "spki" @@ -1642,12 +2126,6 @@ dependencies = [ "der", ] -[[package]] -name = "stable_deref_trait" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" - [[package]] name = "static_assertions" version = "1.1.0" @@ -1662,9 +2140,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "subtle" -version = "2.6.1" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +checksum = "0d0208408ba0c3df17ed26eb06992cb1a1268d41b2c0e12e65203fbe3972cee5" [[package]] name = "syn" @@ -1679,9 +2157,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.100" +version = "2.0.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +checksum = "ff8655ed1d86f3af4ee3fd3263786bc14245ad17c4c7e85ba7187fb3ae028c90" dependencies = [ "proc-macro2", "quote", @@ -1690,25 +2168,41 @@ dependencies = [ [[package]] name = "syn-solidity" -version = "0.7.7" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c837dc8852cb7074e46b444afb81783140dab12c58867b49fb3898fbafedf7ea" +checksum = "8d71e19bca02c807c9faa67b5a47673ff231b6e7449b251695188522f1dc44b2" dependencies = [ "paste", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.67", ] [[package]] -name = "synstructure" -version = "0.13.1" +name = "sync_wrapper" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.100", + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", ] [[package]] @@ -1719,15 +2213,14 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tempfile" -version = "3.19.0" +version = "3.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488960f40a3fd53d72c2a29a58722561dee8afdd175bd88e3db4677d7b2ba600" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ + "cfg-if", "fastrand", - "getrandom 0.3.2", - "once_cell", "rustix", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -1739,29 +2232,34 @@ dependencies = [ "anyhow", "clap", "const-hex", + "data-streams-report", + "data-streams-sdk", + "hex", + "hmac", "rug", "serde", + "sha2", "ureq", ] [[package]] name = "thiserror" -version = "2.0.12" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.12" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.67", ] [[package]] @@ -1774,43 +2272,188 @@ dependencies = [ ] [[package]] -name = "tinystr" -version = "0.7.6" +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-macros" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ - "displaydoc", - "zerovec", + "proc-macro2", + "quote", + "syn 2.0.67", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls 0.21.12", + "tokio", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c" +dependencies = [ + "futures-util", + "log", + "rustls 0.21.12", + "rustls-native-certs", + "tokio", + "tokio-rustls", + "tungstenite", +] + +[[package]] +name = "tokio-util" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", ] [[package]] name = "toml_datetime" -version = "0.6.8" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" [[package]] name = "toml_edit" -version = "0.22.24" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" +checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" dependencies = [ "indexmap", "toml_datetime", "winnow", ] +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.67", +] + +[[package]] +name = "tracing-core" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "tungstenite" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e3dac10fd62eaf6617d3a904ae222845979aec67c615d1c842b4002c7666fb9" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "rand", + "rustls 0.21.12", + "sha1", + "thiserror", + "url", + "utf-8", +] + [[package]] name = "typenum" -version = "1.18.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "ucd-trie" -version = "0.1.7" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" +checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" [[package]] name = "uint" @@ -1830,17 +2473,26 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + [[package]] name = "unicode-ident" -version = "1.0.18" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] -name = "unicode-xid" -version = "0.2.6" +name = "unicode-normalization" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +dependencies = [ + "tinyvec", +] [[package]] name = "untrusted" @@ -1850,27 +2502,28 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "ureq" -version = "2.12.1" +version = "2.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d" +checksum = "d11a831e3c0b56e438a28308e7c810799e3c118417f342d30ecec080105395cd" dependencies = [ - "base64", + "base64 0.22.1", "flate2", "log", "once_cell", - "rustls", + "rustls 0.22.4", "rustls-pki-types", + "rustls-webpki 0.102.4", "serde", "serde_json", "url", - "webpki-roots", + "webpki-roots 0.26.3", ] [[package]] name = "url" -version = "2.5.4" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" dependencies = [ "form_urlencoded", "idna", @@ -1878,16 +2531,10 @@ dependencies = [ ] [[package]] -name = "utf16_iter" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" - -[[package]] -name = "utf8_iter" -version = "1.0.4" +name = "utf-8" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" [[package]] name = "utf8parse" @@ -1897,25 +2544,40 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "valuable" -version = "0.1.1" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "version_check" -version = "0.9.5" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "wait-timeout" -version = "0.2.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" dependencies = [ "libc", ] +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -1923,207 +2585,266 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] -name = "wasi" -version = "0.14.2+wasi-0.2.4" +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn 2.0.67", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ - "wit-bindgen-rt", + "proc-macro2", + "quote", + "syn 2.0.67", + "wasm-bindgen-backend", + "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-roots" +version = "0.25.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" + [[package]] name = "webpki-roots" -version = "0.26.8" +version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2210b291f7ea53617fbafcc4939f10914214ec15aace5ba62293a668f322c5c9" +checksum = "bd7c23921eeb1713a4e851530e9b9756e4fb0e89978582942612524cf09f01cd" dependencies = [ "rustls-pki-types", ] [[package]] name = "windows-sys" -version = "0.52.0" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets", + "windows-targets 0.48.5", ] [[package]] name = "windows-sys" -version = "0.59.0" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets", + "windows-targets 0.52.5", ] [[package]] name = "windows-targets" -version = "0.52.6" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", ] [[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" +name = "windows-targets" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +dependencies = [ + "windows_aarch64_gnullvm 0.52.5", + "windows_aarch64_msvc 0.52.5", + "windows_i686_gnu 0.52.5", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.5", + "windows_x86_64_gnu 0.52.5", + "windows_x86_64_gnullvm 0.52.5", + "windows_x86_64_msvc 0.52.5", +] [[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" +name = "windows_aarch64_gnullvm" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] -name = "windows_i686_gnu" -version = "0.52.6" +name = "windows_aarch64_gnullvm" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" [[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" +name = "windows_aarch64_msvc" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] -name = "windows_i686_msvc" -version = "0.52.6" +name = "windows_aarch64_msvc" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" [[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" +name = "windows_i686_gnu" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" +name = "windows_i686_gnu" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" [[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" +name = "windows_i686_gnullvm" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" [[package]] -name = "winnow" -version = "0.7.4" +name = "windows_i686_msvc" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e97b544156e9bebe1a0ffbc03484fc1ffe3100cbce3ffb17eac35f7cdd7ab36" -dependencies = [ - "memchr", -] +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] -name = "wit-bindgen-rt" -version = "0.39.0" +name = "windows_i686_msvc" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" -dependencies = [ - "bitflags", -] +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" [[package]] -name = "write16" -version = "1.0.0" +name = "windows_x86_64_gnu" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] -name = "writeable" -version = "0.5.5" +name = "windows_x86_64_gnu" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" [[package]] -name = "wyz" -version = "0.5.1" +name = "windows_x86_64_gnullvm" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" -dependencies = [ - "tap", -] +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] -name = "yoke" -version = "0.7.5" +name = "windows_x86_64_gnullvm" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" -dependencies = [ - "serde", - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" [[package]] -name = "yoke-derive" -version = "0.7.5" +name = "windows_x86_64_msvc" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.100", - "synstructure", -] +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] -name = "zerocopy" -version = "0.8.23" +name = "windows_x86_64_msvc" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd97444d05a4328b90e75e503a34bad781f14e28a823ad3557f0750df1ebcbc6" -dependencies = [ - "zerocopy-derive", -] +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" [[package]] -name = "zerocopy-derive" -version = "0.8.23" +name = "winnow" +version = "0.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6352c01d0edd5db859a63e2605f4ea3183ddbd15e2c4a9e7d32184df75e4f154" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.100", + "memchr", ] [[package]] -name = "zerofrom" -version = "0.1.6" +name = "winreg" +version = "0.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" dependencies = [ - "zerofrom-derive", + "cfg-if", + "windows-sys 0.48.0", ] [[package]] -name = "zerofrom-derive" -version = "0.1.6" +name = "wyz" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.100", - "synstructure", + "tap", ] [[package]] @@ -2143,27 +2864,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", -] - -[[package]] -name = "zerovec" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" -dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.100", + "syn 2.0.67", ] diff --git a/README.md b/README.md index edc927d36..9840bcf44 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,32 @@ [![Main workflow](https://github.com/SmarDex-Ecosystem/usdn-contracts/actions/workflows/ci.yml/badge.svg)](https://github.com/SmarDex-Ecosystem/usdn-contracts/actions/workflows/ci.yml) [![Release Workflow](https://github.com/SmarDex-Ecosystem/usdn-contracts/actions/workflows/release.yml/badge.svg)](https://github.com/SmarDex-Ecosystem/usdn-contracts/actions/workflows/release.yml) -## Installation +## Using from Other Projects + +To import contracts, interfaces or ABIs into other projects, you can use one of the following options: + +### Soldeer + +```bash +[forge] soldeer install @smardex-usdn-contracts~1.1.0 +``` + +### NPM, pnpm + +```bash +npm i @smardex/usdn-contracts +pnpm add @smardex/usdn-contracts +``` + +### JSR + +```bash +deno add jsr:@smardex/usdn-contracts +bunx jsr add @smardex/usdn-contracts +pnpm i jsr:@smardex/usdn-contracts +``` + +## Install Development Environment ### Foundry @@ -57,21 +82,21 @@ The environment provides the following tools: - load `.env` file as environment variables - foundry -- solc v0.8.26 - lcov - Node 20 + Typescript - Rust toolchain - just +- lintspec - mdbook - trufflehog -- typist (with gyre-fonts) +- typst (with gyre-fonts) - `test_utils` dependencies ## Usage ### Tests -Compile the test utils by running the following command at the root of the repo: +Compile the test utils by running the following command at the root of the repo (requires the [Rust toolchain](https://rustup.rs/)): ```bash cargo build --release @@ -82,9 +107,9 @@ everything. To run tests, use `forge test -vvv` or `npm run test`. -### Deployment scripts +### Deployment Scripts -Deployment for anvil forks should be done with a custom bash script at `script/deployFork.sh` which can be run without +Deployment for anvil forks should be done with a custom bash script at `script/fork/deployFork.sh` which can be run without arguments. It must set up any environment variable required by the foundry deployment script. Deployment for mainnet should be done with a custom bash script at `script/deployMainnet.sh`. To know which variables are required, run the following command: diff --git a/flake.lock b/flake.lock index 9264b1826..679088c81 100644 --- a/flake.lock +++ b/flake.lock @@ -57,11 +57,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1740916017, - "narHash": "sha256-Ze/3XCElkVljFCnBKezLldOz2ZaGp7eozxRqFzACnMI=", + "lastModified": 1742456341, + "narHash": "sha256-yvdnTnROddjHxoQqrakUQWDZSzVchczfsuuMOxg476c=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "b58e19b11fe72175fd7a9e014a4786a91e99da5f", + "rev": "7344a3b78128f7b1765dba89060b015fb75431a7", "type": "github" }, "original": { @@ -86,11 +86,11 @@ ] }, "locked": { - "lastModified": 1740969088, - "narHash": "sha256-BajboqzFnDhxVT0SXTDKVJCKtFP96lZXccBlT/43mao=", + "lastModified": 1742524367, + "narHash": "sha256-KzTwk/5ETJavJZYV1DEWdCx05M4duFCxCpRbQSKWpng=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "20fdb02098fdda9a25a2939b975abdd7bc03f62d", + "rev": "70bf752d176b2ce07417e346d85486acea9040ef", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 69f187dc7..e25d8014b 100644 --- a/flake.nix +++ b/flake.nix @@ -28,9 +28,11 @@ devShells.default = pkgs.mkShell.override { inherit stdenv; } { nativeBuildInputs = with pkgs; [ gnum4 + openssl ]; buildInputs = [ pkgs.rust-analyzer-unwrapped + pkgs.pkg-config toolchain ]; packages = with pkgs; [ @@ -53,7 +55,7 @@ ''; RUST_SRC_PATH = "${toolchain}/lib/rustlib/src/rust/library"; - LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath [ pkgs.gnum4 ]; + LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath [ pkgs.gnum4 pkgs.openssl]; TYPST_FONT_PATHS = "${pkgs.gyre-fonts}/share/fonts"; }; }); diff --git a/foundry.toml b/foundry.toml index e00e46783..ee692e74c 100644 --- a/foundry.toml +++ b/foundry.toml @@ -33,8 +33,10 @@ remappings = [ "@perimetersec/fuzzlib/=test/fuzzing/fuzzLib/@perimetersec-0.3.1/", "@pythnetwork/pyth-sdk-solidity/=dependencies/@pythnetwork-pyth-sdk-solidity-3.1.0/", "@redstone-finance/evm-connector/=dependencies/@redstone-finance-evm-connector-0.6.2/", + "@smardex-dex-contracts/=dependencies/@smardex-dex-contracts-1.0.1/", "@smardex-solidity-libraries-1/=dependencies/@smardex-solidity-libraries-1.0.1/src/", "@uniswap/permit2/=dependencies/@uniswap-permit2-1.0.0/", + "@uniswapV3=dependencies/@uniswap-v3-core-1.0.2-solc-0.8-simulate/", "forge-std/=dependencies/forge-std-1.9.4/src/", "openzeppelin-foundry-upgrades/=dependencies/openzeppelin-foundry-upgrades-0.3.6/src/", "solady/src/=dependencies/solady-0.0.228/src/", @@ -153,3 +155,5 @@ forge-std = "1" openzeppelin-foundry-upgrades = "0.3" solady = "0.0.228" "@smardex-solidity-libraries" = "1" +"@uniswap-v3-core" = "1.0.2-solc-0.8-simulate" +"@smardex-dex-contracts" = "1.0.1" diff --git a/jsr.json b/jsr.json index 89208750c..cf1ba53c4 100644 --- a/jsr.json +++ b/jsr.json @@ -1,6 +1,6 @@ { "name": "@smardex/usdn-contracts", - "version": "1.0.1", + "version": "1.1.0", "exports": "./dist/abi/index.ts", "publish": { "include": [ diff --git a/package-lock.json b/package-lock.json index 714ec37c3..e785c9fad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@smardex/usdn-contracts", - "version": "1.0.1", + "version": "1.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@smardex/usdn-contracts", - "version": "1.0.1", + "version": "1.1.0", "license": "BUSL-1.1", "dependencies": { "viem": "^2.18.5" @@ -24,7 +24,8 @@ "picocolors": "^1.0.1", "solhint": "^5.0.2", "tsup": "^8.3.5", - "tsx": "^4.19.1" + "tsx": "^4.19.1", + "typescript": "^5.8.2" }, "engines": { "node": ">=20.0.0" @@ -226,9 +227,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.0.tgz", - "integrity": "sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.1.tgz", + "integrity": "sha512-kfYGy8IdzTGy+z0vFGvExZtxkFlA4zAxgKEahG9KE1ScBjpQnFsNOX8KTU5ojNru5ed5CVoJYXFtoxaq5nFbjQ==", "cpu": [ "ppc64" ], @@ -243,9 +244,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.0.tgz", - "integrity": "sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.1.tgz", + "integrity": "sha512-dp+MshLYux6j/JjdqVLnMglQlFu+MuVeNrmT5nk6q07wNhCdSnB7QZj+7G8VMUGh1q+vj2Bq8kRsuyA00I/k+Q==", "cpu": [ "arm" ], @@ -260,9 +261,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.0.tgz", - "integrity": "sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.1.tgz", + "integrity": "sha512-50tM0zCJW5kGqgG7fQ7IHvQOcAn9TKiVRuQ/lN0xR+T2lzEFvAi1ZcS8DiksFcEpf1t/GYOeOfCAgDHFpkiSmA==", "cpu": [ "arm64" ], @@ -277,9 +278,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.0.tgz", - "integrity": "sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.1.tgz", + "integrity": "sha512-GCj6WfUtNldqUzYkN/ITtlhwQqGWu9S45vUXs7EIYf+7rCiiqH9bCloatO9VhxsL0Pji+PF4Lz2XXCES+Q8hDw==", "cpu": [ "x64" ], @@ -294,9 +295,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.0.tgz", - "integrity": "sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.1.tgz", + "integrity": "sha512-5hEZKPf+nQjYoSr/elb62U19/l1mZDdqidGfmFutVUjjUZrOazAtwK+Kr+3y0C/oeJfLlxo9fXb1w7L+P7E4FQ==", "cpu": [ "arm64" ], @@ -311,9 +312,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.0.tgz", - "integrity": "sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.1.tgz", + "integrity": "sha512-hxVnwL2Dqs3fM1IWq8Iezh0cX7ZGdVhbTfnOy5uURtao5OIVCEyj9xIzemDi7sRvKsuSdtCAhMKarxqtlyVyfA==", "cpu": [ "x64" ], @@ -328,9 +329,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.0.tgz", - "integrity": "sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.1.tgz", + "integrity": "sha512-1MrCZs0fZa2g8E+FUo2ipw6jw5qqQiH+tERoS5fAfKnRx6NXH31tXBKI3VpmLijLH6yriMZsxJtaXUyFt/8Y4A==", "cpu": [ "arm64" ], @@ -345,9 +346,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.0.tgz", - "integrity": "sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.1.tgz", + "integrity": "sha512-0IZWLiTyz7nm0xuIs0q1Y3QWJC52R8aSXxe40VUxm6BB1RNmkODtW6LHvWRrGiICulcX7ZvyH6h5fqdLu4gkww==", "cpu": [ "x64" ], @@ -362,9 +363,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.0.tgz", - "integrity": "sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.1.tgz", + "integrity": "sha512-NdKOhS4u7JhDKw9G3cY6sWqFcnLITn6SqivVArbzIaf3cemShqfLGHYMx8Xlm/lBit3/5d7kXvriTUGa5YViuQ==", "cpu": [ "arm" ], @@ -379,9 +380,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.0.tgz", - "integrity": "sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.1.tgz", + "integrity": "sha512-jaN3dHi0/DDPelk0nLcXRm1q7DNJpjXy7yWaWvbfkPvI+7XNSc/lDOnCLN7gzsyzgu6qSAmgSvP9oXAhP973uQ==", "cpu": [ "arm64" ], @@ -396,9 +397,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.0.tgz", - "integrity": "sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.1.tgz", + "integrity": "sha512-OJykPaF4v8JidKNGz8c/q1lBO44sQNUQtq1KktJXdBLn1hPod5rE/Hko5ugKKZd+D2+o1a9MFGUEIUwO2YfgkQ==", "cpu": [ "ia32" ], @@ -413,9 +414,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.0.tgz", - "integrity": "sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.1.tgz", + "integrity": "sha512-nGfornQj4dzcq5Vp835oM/o21UMlXzn79KobKlcs3Wz9smwiifknLy4xDCLUU0BWp7b/houtdrgUz7nOGnfIYg==", "cpu": [ "loong64" ], @@ -430,9 +431,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.0.tgz", - "integrity": "sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.1.tgz", + "integrity": "sha512-1osBbPEFYwIE5IVB/0g2X6i1qInZa1aIoj1TdL4AaAb55xIIgbg8Doq6a5BzYWgr+tEcDzYH67XVnTmUzL+nXg==", "cpu": [ "mips64el" ], @@ -447,9 +448,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.0.tgz", - "integrity": "sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.1.tgz", + "integrity": "sha512-/6VBJOwUf3TdTvJZ82qF3tbLuWsscd7/1w+D9LH0W/SqUgM5/JJD0lrJ1fVIfZsqB6RFmLCe0Xz3fmZc3WtyVg==", "cpu": [ "ppc64" ], @@ -464,9 +465,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.0.tgz", - "integrity": "sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.1.tgz", + "integrity": "sha512-nSut/Mx5gnilhcq2yIMLMe3Wl4FK5wx/o0QuuCLMtmJn+WeWYoEGDN1ipcN72g1WHsnIbxGXd4i/MF0gTcuAjQ==", "cpu": [ "riscv64" ], @@ -481,9 +482,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.0.tgz", - "integrity": "sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.1.tgz", + "integrity": "sha512-cEECeLlJNfT8kZHqLarDBQso9a27o2Zd2AQ8USAEoGtejOrCYHNtKP8XQhMDJMtthdF4GBmjR2au3x1udADQQQ==", "cpu": [ "s390x" ], @@ -498,9 +499,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.0.tgz", - "integrity": "sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.1.tgz", + "integrity": "sha512-xbfUhu/gnvSEg+EGovRc+kjBAkrvtk38RlerAzQxvMzlB4fXpCFCeUAYzJvrnhFtdeyVCDANSjJvOvGYoeKzFA==", "cpu": [ "x64" ], @@ -515,9 +516,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.0.tgz", - "integrity": "sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.1.tgz", + "integrity": "sha512-O96poM2XGhLtpTh+s4+nP7YCCAfb4tJNRVZHfIE7dgmax+yMP2WgMd2OecBuaATHKTHsLWHQeuaxMRnCsH8+5g==", "cpu": [ "arm64" ], @@ -532,9 +533,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.0.tgz", - "integrity": "sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.1.tgz", + "integrity": "sha512-X53z6uXip6KFXBQ+Krbx25XHV/NCbzryM6ehOAeAil7X7oa4XIq+394PWGnwaSQ2WRA0KI6PUO6hTO5zeF5ijA==", "cpu": [ "x64" ], @@ -549,9 +550,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.0.tgz", - "integrity": "sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.1.tgz", + "integrity": "sha512-Na9T3szbXezdzM/Kfs3GcRQNjHzM6GzFBeU1/6IV/npKP5ORtp9zbQjvkDJ47s6BCgaAZnnnu/cY1x342+MvZg==", "cpu": [ "arm64" ], @@ -566,9 +567,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.0.tgz", - "integrity": "sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.1.tgz", + "integrity": "sha512-T3H78X2h1tszfRSf+txbt5aOp/e7TAz3ptVKu9Oyir3IAOFPGV6O9c2naym5TOriy1l0nNf6a4X5UXRZSGX/dw==", "cpu": [ "x64" ], @@ -583,9 +584,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.0.tgz", - "integrity": "sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.1.tgz", + "integrity": "sha512-2H3RUvcmULO7dIE5EWJH8eubZAI4xw54H1ilJnRNZdeo8dTADEZ21w6J22XBkXqGJbe0+wnNJtw3UXRoLJnFEg==", "cpu": [ "x64" ], @@ -600,9 +601,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.0.tgz", - "integrity": "sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.1.tgz", + "integrity": "sha512-GE7XvrdOzrb+yVKB9KsRMq+7a2U/K5Cf/8grVFRAGJmfADr/e/ODQ134RK2/eeHqYV5eQRFxb1hY7Nr15fv1NQ==", "cpu": [ "arm64" ], @@ -617,9 +618,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.0.tgz", - "integrity": "sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.1.tgz", + "integrity": "sha512-uOxSJCIcavSiT6UnBhBzE8wy3n0hOkJsBOzy7HDAuTDE++1DJMRRVCPGisULScHL+a/ZwdXPpXD3IyFKjA7K8A==", "cpu": [ "ia32" ], @@ -634,9 +635,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.0.tgz", - "integrity": "sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.1.tgz", + "integrity": "sha512-Y1EQdcfwMSeQN/ujR5VayLOJ1BHaK+ssyk0AEzPjC+t1lITgsnccPqFjb6V+LsTp/9Iov4ysfjxLaGJ9RPtkVg==", "cpu": [ "x64" ], @@ -798,9 +799,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.34.9", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.9.tgz", - "integrity": "sha512-qZdlImWXur0CFakn2BJ2znJOdqYZKiedEPEVNTBrpfPjc/YuTGcaYZcdmNFTkUj3DU0ZM/AElcM8Ybww3xVLzA==", + "version": "4.36.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.36.0.tgz", + "integrity": "sha512-jgrXjjcEwN6XpZXL0HUeOVGfjXhPyxAbbhD0BlXUB+abTOpbPiN5Wb3kOT7yb+uEtATNYF5x5gIfwutmuBA26w==", "cpu": [ "arm" ], @@ -812,9 +813,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.34.9", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.34.9.tgz", - "integrity": "sha512-4KW7P53h6HtJf5Y608T1ISKvNIYLWRKMvfnG0c44M6In4DQVU58HZFEVhWINDZKp7FZps98G3gxwC1sb0wXUUg==", + "version": "4.36.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.36.0.tgz", + "integrity": "sha512-NyfuLvdPdNUfUNeYKUwPwKsE5SXa2J6bCt2LdB/N+AxShnkpiczi3tcLJrm5mA+eqpy0HmaIY9F6XCa32N5yzg==", "cpu": [ "arm64" ], @@ -826,9 +827,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.34.9", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.9.tgz", - "integrity": "sha512-0CY3/K54slrzLDjOA7TOjN1NuLKERBgk9nY5V34mhmuu673YNb+7ghaDUs6N0ujXR7fz5XaS5Aa6d2TNxZd0OQ==", + "version": "4.36.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.36.0.tgz", + "integrity": "sha512-JQ1Jk5G4bGrD4pWJQzWsD8I1n1mgPXq33+/vP4sk8j/z/C2siRuxZtaUA7yMTf71TCZTZl/4e1bfzwUmFb3+rw==", "cpu": [ "arm64" ], @@ -840,9 +841,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.34.9", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.9.tgz", - "integrity": "sha512-eOojSEAi/acnsJVYRxnMkPFqcxSMFfrw7r2iD9Q32SGkb/Q9FpUY1UlAu1DH9T7j++gZ0lHjnm4OyH2vCI7l7Q==", + "version": "4.36.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.36.0.tgz", + "integrity": "sha512-6c6wMZa1lrtiRsbDziCmjE53YbTkxMYhhnWnSW8R/yqsM7a6mSJ3uAVT0t8Y/DGt7gxUWYuFM4bwWk9XCJrFKA==", "cpu": [ "x64" ], @@ -854,9 +855,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.34.9", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.34.9.tgz", - "integrity": "sha512-2lzjQPJbN5UnHm7bHIUKFMulGTQwdvOkouJDpPysJS+QFBGDJqcfh+CxxtG23Ik/9tEvnebQiylYoazFMAgrYw==", + "version": "4.36.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.36.0.tgz", + "integrity": "sha512-KXVsijKeJXOl8QzXTsA+sHVDsFOmMCdBRgFmBb+mfEb/7geR7+C8ypAml4fquUt14ZyVXaw2o1FWhqAfOvA4sg==", "cpu": [ "arm64" ], @@ -868,9 +869,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.34.9", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.34.9.tgz", - "integrity": "sha512-SLl0hi2Ah2H7xQYd6Qaiu01kFPzQ+hqvdYSoOtHYg/zCIFs6t8sV95kaoqjzjFwuYQLtOI0RZre/Ke0nPaQV+g==", + "version": "4.36.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.36.0.tgz", + "integrity": "sha512-dVeWq1ebbvByI+ndz4IJcD4a09RJgRYmLccwlQ8bPd4olz3Y213uf1iwvc7ZaxNn2ab7bjc08PrtBgMu6nb4pQ==", "cpu": [ "x64" ], @@ -882,9 +883,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.34.9", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.34.9.tgz", - "integrity": "sha512-88I+D3TeKItrw+Y/2ud4Tw0+3CxQ2kLgu3QvrogZ0OfkmX/DEppehus7L3TS2Q4lpB+hYyxhkQiYPJ6Mf5/dPg==", + "version": "4.36.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.36.0.tgz", + "integrity": "sha512-bvXVU42mOVcF4le6XSjscdXjqx8okv4n5vmwgzcmtvFdifQ5U4dXFYaCB87namDRKlUL9ybVtLQ9ztnawaSzvg==", "cpu": [ "arm" ], @@ -896,9 +897,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.34.9", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.34.9.tgz", - "integrity": "sha512-3qyfWljSFHi9zH0KgtEPG4cBXHDFhwD8kwg6xLfHQ0IWuH9crp005GfoUUh/6w9/FWGBwEHg3lxK1iHRN1MFlA==", + "version": "4.36.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.36.0.tgz", + "integrity": "sha512-JFIQrDJYrxOnyDQGYkqnNBtjDwTgbasdbUiQvcU8JmGDfValfH1lNpng+4FWlhaVIR4KPkeddYjsVVbmJYvDcg==", "cpu": [ "arm" ], @@ -910,9 +911,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.34.9", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.9.tgz", - "integrity": "sha512-6TZjPHjKZUQKmVKMUowF3ewHxctrRR09eYyvT5eFv8w/fXarEra83A2mHTVJLA5xU91aCNOUnM+DWFMSbQ0Nxw==", + "version": "4.36.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.36.0.tgz", + "integrity": "sha512-KqjYVh3oM1bj//5X7k79PSCZ6CvaVzb7Qs7VMWS+SlWB5M8p3FqufLP9VNp4CazJ0CsPDLwVD9r3vX7Ci4J56A==", "cpu": [ "arm64" ], @@ -924,9 +925,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.34.9", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.9.tgz", - "integrity": "sha512-LD2fytxZJZ6xzOKnMbIpgzFOuIKlxVOpiMAXawsAZ2mHBPEYOnLRK5TTEsID6z4eM23DuO88X0Tq1mErHMVq0A==", + "version": "4.36.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.36.0.tgz", + "integrity": "sha512-QiGnhScND+mAAtfHqeT+cB1S9yFnNQ/EwCg5yE3MzoaZZnIV0RV9O5alJAoJKX/sBONVKeZdMfO8QSaWEygMhw==", "cpu": [ "arm64" ], @@ -938,9 +939,9 @@ ] }, "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.34.9", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.9.tgz", - "integrity": "sha512-dRAgTfDsn0TE0HI6cmo13hemKpVHOEyeciGtvlBTkpx/F65kTvShtY/EVyZEIfxFkV5JJTuQ9tP5HGBS0hfxIg==", + "version": "4.36.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.36.0.tgz", + "integrity": "sha512-1ZPyEDWF8phd4FQtTzMh8FQwqzvIjLsl6/84gzUxnMNFBtExBtpL51H67mV9xipuxl1AEAerRBgBwFNpkw8+Lg==", "cpu": [ "loong64" ], @@ -952,9 +953,9 @@ ] }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.34.9", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.34.9.tgz", - "integrity": "sha512-PHcNOAEhkoMSQtMf+rJofwisZqaU8iQ8EaSps58f5HYll9EAY5BSErCZ8qBDMVbq88h4UxaNPlbrKqfWP8RfJA==", + "version": "4.36.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.36.0.tgz", + "integrity": "sha512-VMPMEIUpPFKpPI9GZMhJrtu8rxnp6mJR3ZzQPykq4xc2GmdHj3Q4cA+7avMyegXy4n1v+Qynr9fR88BmyO74tg==", "cpu": [ "ppc64" ], @@ -966,9 +967,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.34.9", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.9.tgz", - "integrity": "sha512-Z2i0Uy5G96KBYKjeQFKbbsB54xFOL5/y1P5wNBsbXB8yE+At3oh0DVMjQVzCJRJSfReiB2tX8T6HUFZ2k8iaKg==", + "version": "4.36.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.36.0.tgz", + "integrity": "sha512-ttE6ayb/kHwNRJGYLpuAvB7SMtOeQnVXEIpMtAvx3kepFQeowVED0n1K9nAdraHUPJ5hydEMxBpIR7o4nrm8uA==", "cpu": [ "riscv64" ], @@ -980,9 +981,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.34.9", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.9.tgz", - "integrity": "sha512-U+5SwTMoeYXoDzJX5dhDTxRltSrIax8KWwfaaYcynuJw8mT33W7oOgz0a+AaXtGuvhzTr2tVKh5UO8GVANTxyQ==", + "version": "4.36.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.36.0.tgz", + "integrity": "sha512-4a5gf2jpS0AIe7uBjxDeUMNcFmaRTbNv7NxI5xOCs4lhzsVyGR/0qBXduPnoWf6dGC365saTiwag8hP1imTgag==", "cpu": [ "s390x" ], @@ -994,9 +995,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.34.9", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.9.tgz", - "integrity": "sha512-FwBHNSOjUTQLP4MG7y6rR6qbGw4MFeQnIBrMe161QGaQoBQLqSUEKlHIiVgF3g/mb3lxlxzJOpIBhaP+C+KP2A==", + "version": "4.36.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.36.0.tgz", + "integrity": "sha512-5KtoW8UWmwFKQ96aQL3LlRXX16IMwyzMq/jSSVIIyAANiE1doaQsx/KRyhAvpHlPjPiSU/AYX/8m+lQ9VToxFQ==", "cpu": [ "x64" ], @@ -1008,9 +1009,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.34.9", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.9.tgz", - "integrity": "sha512-cYRpV4650z2I3/s6+5/LONkjIz8MBeqrk+vPXV10ORBnshpn8S32bPqQ2Utv39jCiDcO2eJTuSlPXpnvmaIgRA==", + "version": "4.36.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.36.0.tgz", + "integrity": "sha512-sycrYZPrv2ag4OCvaN5js+f01eoZ2U+RmT5as8vhxiFz+kxwlHrsxOwKPSA8WyS+Wc6Epid9QeI/IkQ9NkgYyQ==", "cpu": [ "x64" ], @@ -1022,9 +1023,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.34.9", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.9.tgz", - "integrity": "sha512-z4mQK9dAN6byRA/vsSgQiPeuO63wdiDxZ9yg9iyX2QTzKuQM7T4xlBoeUP/J8uiFkqxkcWndWi+W7bXdPbt27Q==", + "version": "4.36.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.36.0.tgz", + "integrity": "sha512-qbqt4N7tokFwwSVlWDsjfoHgviS3n/vZ8LK0h1uLG9TYIRuUTJC88E1xb3LM2iqZ/WTqNQjYrtmtGmrmmawB6A==", "cpu": [ "arm64" ], @@ -1036,9 +1037,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.34.9", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.34.9.tgz", - "integrity": "sha512-KB48mPtaoHy1AwDNkAJfHXvHp24H0ryZog28spEs0V48l3H1fr4i37tiyHsgKZJnCmvxsbATdZGBpbmxTE3a9w==", + "version": "4.36.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.36.0.tgz", + "integrity": "sha512-t+RY0JuRamIocMuQcfwYSOkmdX9dtkr1PbhKW42AMvaDQa+jOdpUYysroTF/nuPpAaQMWp7ye+ndlmmthieJrQ==", "cpu": [ "ia32" ], @@ -1050,9 +1051,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.34.9", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.9.tgz", - "integrity": "sha512-AyleYRPU7+rgkMWbEh71fQlrzRfeP6SyMnRf9XX4fCdDPAJumdSBqYEcWPMzVQ4ScAl7E4oFfK0GUVn77xSwbw==", + "version": "4.36.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.36.0.tgz", + "integrity": "sha512-aRXd7tRZkWLqGbChgcMMDEHjOKudo1kChb1Jt1IfR8cY/KIpgNviLeJy5FUb9IpSuQj8dU2fAYNMPW/hLKOSTw==", "cpu": [ "x64" ], @@ -1154,9 +1155,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.13.8", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.8.tgz", - "integrity": "sha512-G3EfaZS+iOGYWLLRCEAXdWK9my08oHNZ+FHluRiggIYJPOXzhOiDgpVCUHaUvyIC5/fj7C/p637jdzC666AOKQ==", + "version": "22.13.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.10.tgz", + "integrity": "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw==", "dev": true, "license": "MIT", "dependencies": { @@ -1428,9 +1429,9 @@ } }, "node_modules/consola": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.0.tgz", - "integrity": "sha512-EiPU8G6dQG0GFHNR8ljnZFki/8a+cQwEQ+7wpxdChl02Q8HXlwEZWD5lqAF8vC2sEC3Tehr8hy7vErz88LHyUA==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", + "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", "dev": true, "license": "MIT", "engines": { @@ -1571,9 +1572,9 @@ } }, "node_modules/esbuild": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.0.tgz", - "integrity": "sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.1.tgz", + "integrity": "sha512-BGO5LtrGC7vxnqucAe/rmvKdJllfGaYWdyABvyMoXQlfYMb2bbRuReWR5tEGE//4LcNJj9XrkovTqNYRFZHAMQ==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -1584,31 +1585,31 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.0", - "@esbuild/android-arm": "0.25.0", - "@esbuild/android-arm64": "0.25.0", - "@esbuild/android-x64": "0.25.0", - "@esbuild/darwin-arm64": "0.25.0", - "@esbuild/darwin-x64": "0.25.0", - "@esbuild/freebsd-arm64": "0.25.0", - "@esbuild/freebsd-x64": "0.25.0", - "@esbuild/linux-arm": "0.25.0", - "@esbuild/linux-arm64": "0.25.0", - "@esbuild/linux-ia32": "0.25.0", - "@esbuild/linux-loong64": "0.25.0", - "@esbuild/linux-mips64el": "0.25.0", - "@esbuild/linux-ppc64": "0.25.0", - "@esbuild/linux-riscv64": "0.25.0", - "@esbuild/linux-s390x": "0.25.0", - "@esbuild/linux-x64": "0.25.0", - "@esbuild/netbsd-arm64": "0.25.0", - "@esbuild/netbsd-x64": "0.25.0", - "@esbuild/openbsd-arm64": "0.25.0", - "@esbuild/openbsd-x64": "0.25.0", - "@esbuild/sunos-x64": "0.25.0", - "@esbuild/win32-arm64": "0.25.0", - "@esbuild/win32-ia32": "0.25.0", - "@esbuild/win32-x64": "0.25.0" + "@esbuild/aix-ppc64": "0.25.1", + "@esbuild/android-arm": "0.25.1", + "@esbuild/android-arm64": "0.25.1", + "@esbuild/android-x64": "0.25.1", + "@esbuild/darwin-arm64": "0.25.1", + "@esbuild/darwin-x64": "0.25.1", + "@esbuild/freebsd-arm64": "0.25.1", + "@esbuild/freebsd-x64": "0.25.1", + "@esbuild/linux-arm": "0.25.1", + "@esbuild/linux-arm64": "0.25.1", + "@esbuild/linux-ia32": "0.25.1", + "@esbuild/linux-loong64": "0.25.1", + "@esbuild/linux-mips64el": "0.25.1", + "@esbuild/linux-ppc64": "0.25.1", + "@esbuild/linux-riscv64": "0.25.1", + "@esbuild/linux-s390x": "0.25.1", + "@esbuild/linux-x64": "0.25.1", + "@esbuild/netbsd-arm64": "0.25.1", + "@esbuild/netbsd-x64": "0.25.1", + "@esbuild/openbsd-arm64": "0.25.1", + "@esbuild/openbsd-x64": "0.25.1", + "@esbuild/sunos-x64": "0.25.1", + "@esbuild/win32-arm64": "0.25.1", + "@esbuild/win32-ia32": "0.25.1", + "@esbuild/win32-x64": "0.25.1" } }, "node_modules/eval": { @@ -2233,9 +2234,9 @@ } }, "node_modules/ox": { - "version": "0.6.7", - "resolved": "https://registry.npmjs.org/ox/-/ox-0.6.7.tgz", - "integrity": "sha512-17Gk/eFsFRAZ80p5eKqv89a57uXjd3NgIf1CaXojATPBuujVc/fQSVhBeAU9JCRB+k7J50WQAyWTxK19T9GgbA==", + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/ox/-/ox-0.6.9.tgz", + "integrity": "sha512-wi5ShvzE4eOcTwQVsIPdFr+8ycyX+5le/96iAJutaZAvCes1J0+RvpEPg5QDPDiaR0XQQAvZVl7AwqQcINuUug==", "funding": [ { "type": "github", @@ -2618,9 +2619,9 @@ } }, "node_modules/rollup": { - "version": "4.34.9", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.34.9.tgz", - "integrity": "sha512-nF5XYqWWp9hx/LrpC8sZvvvmq0TeTjQgaZHYmAgwysT9nh8sWnZhBnM8ZyVbbJFIQBLwHDNoMqsBZBbUo4U8sQ==", + "version": "4.36.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.36.0.tgz", + "integrity": "sha512-zwATAXNQxUcd40zgtQG0ZafcRK4g004WtEl7kbuhTWPvf07PsfohXl39jVUvPF7jvNAIkKPQ2XrsDlWuxBd++Q==", "dev": true, "license": "MIT", "dependencies": { @@ -2634,25 +2635,25 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.34.9", - "@rollup/rollup-android-arm64": "4.34.9", - "@rollup/rollup-darwin-arm64": "4.34.9", - "@rollup/rollup-darwin-x64": "4.34.9", - "@rollup/rollup-freebsd-arm64": "4.34.9", - "@rollup/rollup-freebsd-x64": "4.34.9", - "@rollup/rollup-linux-arm-gnueabihf": "4.34.9", - "@rollup/rollup-linux-arm-musleabihf": "4.34.9", - "@rollup/rollup-linux-arm64-gnu": "4.34.9", - "@rollup/rollup-linux-arm64-musl": "4.34.9", - "@rollup/rollup-linux-loongarch64-gnu": "4.34.9", - "@rollup/rollup-linux-powerpc64le-gnu": "4.34.9", - "@rollup/rollup-linux-riscv64-gnu": "4.34.9", - "@rollup/rollup-linux-s390x-gnu": "4.34.9", - "@rollup/rollup-linux-x64-gnu": "4.34.9", - "@rollup/rollup-linux-x64-musl": "4.34.9", - "@rollup/rollup-win32-arm64-msvc": "4.34.9", - "@rollup/rollup-win32-ia32-msvc": "4.34.9", - "@rollup/rollup-win32-x64-msvc": "4.34.9", + "@rollup/rollup-android-arm-eabi": "4.36.0", + "@rollup/rollup-android-arm64": "4.36.0", + "@rollup/rollup-darwin-arm64": "4.36.0", + "@rollup/rollup-darwin-x64": "4.36.0", + "@rollup/rollup-freebsd-arm64": "4.36.0", + "@rollup/rollup-freebsd-x64": "4.36.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.36.0", + "@rollup/rollup-linux-arm-musleabihf": "4.36.0", + "@rollup/rollup-linux-arm64-gnu": "4.36.0", + "@rollup/rollup-linux-arm64-musl": "4.36.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.36.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.36.0", + "@rollup/rollup-linux-riscv64-gnu": "4.36.0", + "@rollup/rollup-linux-s390x-gnu": "4.36.0", + "@rollup/rollup-linux-x64-gnu": "4.36.0", + "@rollup/rollup-linux-x64-musl": "4.36.0", + "@rollup/rollup-win32-arm64-msvc": "4.36.0", + "@rollup/rollup-win32-ia32-msvc": "4.36.0", + "@rollup/rollup-win32-x64-msvc": "4.36.0", "fsevents": "~2.3.2" } }, @@ -3322,6 +3323,20 @@ "fsevents": "~2.3.3" } }, + "node_modules/typescript": { + "version": "5.8.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", + "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/undici-types": { "version": "6.20.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", @@ -3340,9 +3355,9 @@ } }, "node_modules/viem": { - "version": "2.23.5", - "resolved": "https://registry.npmjs.org/viem/-/viem-2.23.5.tgz", - "integrity": "sha512-cUfBHdFQHmBlPW0loFXda0uZcoU+uJw3NRYQRwYgkrpH6PgovH8iuVqDn6t1jZk82zny4wQL54c9dCX2W9kLMg==", + "version": "2.23.13", + "resolved": "https://registry.npmjs.org/viem/-/viem-2.23.13.tgz", + "integrity": "sha512-f3RkcrzGhU79GfBb9GHUL0m3e3LUsNudXIQTFp4fit5hUGb0ew9KOYZ6cCY5d4Melj3noBy2zq0K2fV+mp+Cpg==", "funding": [ { "type": "github", @@ -3357,8 +3372,8 @@ "@scure/bip39": "1.5.4", "abitype": "1.0.8", "isows": "1.0.6", - "ox": "0.6.7", - "ws": "8.18.0" + "ox": "0.6.9", + "ws": "8.18.1" }, "peerDependencies": { "typescript": ">=5.0.4" @@ -3507,9 +3522,9 @@ "license": "ISC" }, "node_modules/ws": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", - "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==", "license": "MIT", "engines": { "node": ">=10.0.0" diff --git a/package.json b/package.json index 3958c6268..84d0bca1e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@smardex/usdn-contracts", - "version": "1.0.1", + "version": "1.1.0", "description": "Contracts for the USDN token and derivatives protocol", "repository": { "type": "git", @@ -55,7 +55,8 @@ "picocolors": "^1.0.1", "solhint": "^5.0.2", "tsup": "^8.3.5", - "tsx": "^4.19.1" + "tsx": "^4.19.1", + "typescript": "^5.8.2" }, "dependencies": { "viem": "^2.18.5" diff --git a/script/01_DeployUsdnWsteth.s.sol b/script/01_DeployUsdnWstethUsd.s.sol similarity index 87% rename from script/01_DeployUsdnWsteth.s.sol rename to script/01_DeployUsdnWstethUsd.s.sol index b4b6bfe03..eadcb9e4e 100644 --- a/script/01_DeployUsdnWsteth.s.sol +++ b/script/01_DeployUsdnWstethUsd.s.sol @@ -7,11 +7,11 @@ import { HugeUint } from "@smardex-solidity-libraries-1/HugeUint.sol"; import { Options, Upgrades } from "openzeppelin-foundry-upgrades/Upgrades.sol"; import { FixedPointMathLib } from "solady/src/utils/FixedPointMathLib.sol"; -import { UsdnWstethConfig } from "./deploymentConfigs/UsdnWstethConfig.sol"; +import { UsdnWstethUsdConfig } from "./deploymentConfigs/UsdnWstethUsdConfig.sol"; import { Utils } from "./utils/Utils.s.sol"; -import { LiquidationRewardsManager } from "../src/LiquidationRewardsManager/LiquidationRewardsManager.sol"; -import { WstEthOracleMiddleware } from "../src/OracleMiddleware/WstEthOracleMiddleware.sol"; +import { LiquidationRewardsManagerWstEth } from "../src/LiquidationRewardsManager/LiquidationRewardsManagerWstEth.sol"; +import { WstEthOracleMiddlewareWithPyth } from "../src/OracleMiddleware/WstEthOracleMiddlewareWithPyth.sol"; import { Rebalancer } from "../src/Rebalancer/Rebalancer.sol"; import { Usdn } from "../src/Usdn/Usdn.sol"; import { Wusdn } from "../src/Usdn/Wusdn.sol"; @@ -19,16 +19,13 @@ import { UsdnProtocolFallback } from "../src/UsdnProtocol/UsdnProtocolFallback.s import { UsdnProtocolImpl } from "../src/UsdnProtocol/UsdnProtocolImpl.sol"; import { UsdnProtocolConstantsLibrary as Constants } from "../src/UsdnProtocol/libraries/UsdnProtocolConstantsLibrary.sol"; -import { IWstETH } from "../src/interfaces/IWstETH.sol"; import { IUsdnProtocol } from "../src/interfaces/UsdnProtocol/IUsdnProtocol.sol"; import { IUsdnProtocolTypes as Types } from "../src/interfaces/UsdnProtocol/IUsdnProtocolTypes.sol"; -contract DeployUsdnWsteth is UsdnWstethConfig, Script { - IWstETH immutable WSTETH; +contract DeployUsdnWstethUsd is UsdnWstethUsdConfig, Script { Utils utils; constructor() { - WSTETH = IWstETH(address(UNDERLYING_ASSET)); utils = new Utils(); } @@ -44,8 +41,8 @@ contract DeployUsdnWsteth is UsdnWstethConfig, Script { function run() external returns ( - WstEthOracleMiddleware wstEthOracleMiddleware_, - LiquidationRewardsManager liquidationRewardsManager_, + WstEthOracleMiddlewareWithPyth wstEthOracleMiddleware_, + LiquidationRewardsManagerWstEth liquidationRewardsManager_, Rebalancer rebalancer_, Usdn usdn_, Wusdn wusdn_, @@ -78,15 +75,15 @@ contract DeployUsdnWsteth is UsdnWstethConfig, Script { function _deployAndSetPeripheralContracts() internal returns ( - WstEthOracleMiddleware wstEthOracleMiddleware_, - LiquidationRewardsManager liquidationRewardsManager_, + WstEthOracleMiddlewareWithPyth wstEthOracleMiddleware_, + LiquidationRewardsManagerWstEth liquidationRewardsManager_, Usdn usdn_, Wusdn wusdn_ ) { vm.startBroadcast(); - liquidationRewardsManager_ = new LiquidationRewardsManager(WSTETH); - wstEthOracleMiddleware_ = new WstEthOracleMiddleware( + liquidationRewardsManager_ = new LiquidationRewardsManagerWstEth(WSTETH); + wstEthOracleMiddleware_ = new WstEthOracleMiddlewareWithPyth( PYTH_ADDRESS, PYTH_ETH_FEED_ID, CHAINLINK_ETH_PRICE, address(WSTETH), CHAINLINK_PRICE_VALIDITY ); usdn_ = new Usdn(address(0), address(0)); @@ -108,11 +105,11 @@ contract DeployUsdnWsteth is UsdnWstethConfig, Script { vm.startBroadcast(); - UsdnProtocolFallback protocolFallback = new UsdnProtocolFallback(); + UsdnProtocolFallback protocolFallback = new UsdnProtocolFallback(MAX_SDEX_BURN_RATIO, MAX_MIN_LONG_POSITION); _setProtocolFallback(protocolFallback); address proxy = Upgrades.deployUUPSProxy( - "UsdnProtocolImpl.sol", abi.encodeCall(UsdnProtocolImpl.initializeStorage, (initStorage)), opts + "UsdnProtocolImpl.sol", abi.encodeCall(UsdnProtocolImpl.initializeStorage, initStorage), opts ); vm.stopBroadcast(); @@ -123,6 +120,7 @@ contract DeployUsdnWsteth is UsdnWstethConfig, Script { /** * @notice Set the rebalancer and give the minting and rebasing roles to the USDN protocol. * @param usdnProtocol The USDN protocol. + * @param usdn The USDN token. * @return rebalancer_ The rebalancer. */ function _setRebalancerAndHandleUsdnRoles(IUsdnProtocol usdnProtocol, Usdn usdn) @@ -147,7 +145,9 @@ contract DeployUsdnWsteth is UsdnWstethConfig, Script { * @param usdnProtocol The USDN protocol. * @param wstEthOracleMiddleware The WstETH oracle middleware. */ - function _initializeProtocol(IUsdnProtocol usdnProtocol, WstEthOracleMiddleware wstEthOracleMiddleware) internal { + function _initializeProtocol(IUsdnProtocol usdnProtocol, WstEthOracleMiddlewareWithPyth wstEthOracleMiddleware) + internal + { uint24 liquidationPenalty = usdnProtocol.getLiquidationPenalty(); int24 tickSpacing = usdnProtocol.getTickSpacing(); uint256 price = wstEthOracleMiddleware.parseAndValidatePrice( diff --git a/script/01_DeployUsdnWusdnEth.s.sol b/script/01_DeployUsdnWusdnEth.s.sol new file mode 100644 index 000000000..f7417d8f0 --- /dev/null +++ b/script/01_DeployUsdnWusdnEth.s.sol @@ -0,0 +1,198 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.26; + +import { Script } from "forge-std/Script.sol"; + +import { HugeUint } from "@smardex-solidity-libraries-1/HugeUint.sol"; +import { Options, Upgrades } from "openzeppelin-foundry-upgrades/Upgrades.sol"; +import { FixedPointMathLib } from "solady/src/utils/FixedPointMathLib.sol"; + +import { UsdnWusdnEthConfig } from "./deploymentConfigs/UsdnWusdnEthConfig.sol"; +import { Utils } from "./utils/Utils.s.sol"; + +import { LiquidationRewardsManagerWusdn } from "../src/LiquidationRewardsManager/LiquidationRewardsManagerWusdn.sol"; +import { WusdnToEthOracleMiddlewareWithPyth } from "../src/OracleMiddleware/WusdnToEthOracleMiddlewareWithPyth.sol"; +import { Rebalancer } from "../src/Rebalancer/Rebalancer.sol"; +import { UsdnNoRebase } from "../src/Usdn/UsdnNoRebase.sol"; +import { UsdnProtocolFallback } from "../src/UsdnProtocol/UsdnProtocolFallback.sol"; +import { UsdnProtocolImpl } from "../src/UsdnProtocol/UsdnProtocolImpl.sol"; +import { UsdnProtocolConstantsLibrary as Constants } from + "../src/UsdnProtocol/libraries/UsdnProtocolConstantsLibrary.sol"; +import { IWusdn } from "../src/interfaces/Usdn/IWusdn.sol"; +import { IUsdnProtocol } from "../src/interfaces/UsdnProtocol/IUsdnProtocol.sol"; +import { IUsdnProtocolTypes as Types } from "../src/interfaces/UsdnProtocol/IUsdnProtocolTypes.sol"; + +contract DeployUsdnWusdnEth is UsdnWusdnEthConfig, Script { + IWusdn immutable WUSDN; + Utils utils; + + constructor() { + WUSDN = IWusdn(address(UNDERLYING_ASSET)); + utils = new Utils(); + } + + /** + * @notice Deploy the USDN ecosystem with WUSDN as the underlying token. + * @return wusdnToEthOracleMiddleware_ The oracle middleware to get the price of the WUSDN in ETH. + * @return liquidationRewardsManagerWusdn_ The liquidation rewards manager. + * @return rebalancer_ The rebalancer. + * @return usdnNoRebase_ The USDN token contract. + * @return usdnProtocol_ The USDN protocol contract. + */ + function run() + external + returns ( + WusdnToEthOracleMiddlewareWithPyth wusdnToEthOracleMiddleware_, + LiquidationRewardsManagerWusdn liquidationRewardsManagerWusdn_, + Rebalancer rebalancer_, + UsdnNoRebase usdnNoRebase_, + IUsdnProtocol usdnProtocol_ + ) + { + utils.validateProtocol("UsdnProtocolImpl", "UsdnProtocolFallback"); + + _setFeeCollector(msg.sender); + + (wusdnToEthOracleMiddleware_, liquidationRewardsManagerWusdn_, usdnNoRebase_) = + _deployAndSetPeripheralContracts(); + + usdnProtocol_ = _deployProtocol(initStorage); + _grantRequiredRoles(usdnProtocol_, usdnNoRebase_); + + rebalancer_ = _setRebalancer(usdnProtocol_); + + _initializeProtocol(usdnProtocol_, wusdnToEthOracleMiddleware_); + _revokeRoles(usdnProtocol_); + + utils.validateProtocolConfig(usdnProtocol_, msg.sender); + } + + /** + * @notice Deploy the oracle middleware, liquidation rewards manager and UsdnNoRebase contracts. Add them to the + * initialization struct. + * @dev As the USDN token doesn't rebase, there's no need to deploy the WUSDN contract, as wrapping is only useful + * to avoid messing with the token balances in smart contracts. + * @return wusdnToEthOracleMiddleware_ The oracle middleware that gets the price of the WUSDN in Eth. + * @return liquidationRewardsManagerWusdn_ The liquidation rewards manager. + * @return usdnNoRebase_ The USDN contract. + */ + function _deployAndSetPeripheralContracts() + internal + returns ( + WusdnToEthOracleMiddlewareWithPyth wusdnToEthOracleMiddleware_, + LiquidationRewardsManagerWusdn liquidationRewardsManagerWusdn_, + UsdnNoRebase usdnNoRebase_ + ) + { + vm.startBroadcast(); + liquidationRewardsManagerWusdn_ = new LiquidationRewardsManagerWusdn(WUSDN); + wusdnToEthOracleMiddleware_ = new WusdnToEthOracleMiddlewareWithPyth( + PYTH_ADDRESS, PYTH_ETH_FEED_ID, CHAINLINK_ETH_PRICE, address(WUSDN.USDN()), CHAINLINK_PRICE_VALIDITY + ); + + usdnNoRebase_ = new UsdnNoRebase("Synthetic ETH", "syntETH"); + vm.stopBroadcast(); + + _setPeripheralContracts(wusdnToEthOracleMiddleware_, liquidationRewardsManagerWusdn_, usdnNoRebase_); + } + + /** + * @notice Deploy the USDN protocol. + * @param initStorage The initialization parameters struct. + * @return usdnProtocol_ The USDN protocol proxy. + */ + function _deployProtocol(Types.InitStorage storage initStorage) internal returns (IUsdnProtocol usdnProtocol_) { + // we need to allow external library linking and immutable variables in the openzeppelin module + Options memory opts; + opts.unsafeAllow = "external-library-linking,state-variable-immutable"; + + vm.startBroadcast(); + + UsdnProtocolFallback protocolFallback = new UsdnProtocolFallback(MAX_SDEX_BURN_RATIO, MAX_MIN_LONG_POSITION); + _setProtocolFallback(protocolFallback); + + address proxy = Upgrades.deployUUPSProxy( + "UsdnProtocolImpl.sol", abi.encodeCall(UsdnProtocolImpl.initializeStorage, initStorage), opts + ); + + vm.stopBroadcast(); + + usdnProtocol_ = IUsdnProtocol(proxy); + } + + /** + * @notice Deploys and sets the rebalancer. + * @param usdnProtocol The USDN protocol. + * @return rebalancer_ The rebalancer. + */ + function _setRebalancer(IUsdnProtocol usdnProtocol) internal returns (Rebalancer rebalancer_) { + vm.startBroadcast(); + + rebalancer_ = new Rebalancer(usdnProtocol); + usdnProtocol.setRebalancer(rebalancer_); + + vm.stopBroadcast(); + } + + /** + * @notice Initializes the USDN protocol with a ~2x leverage long position. + * @param usdnProtocol The USDN protocol. + * @param wusdnToEthOracleMiddleware The WstETH oracle middleware. + */ + function _initializeProtocol( + IUsdnProtocol usdnProtocol, + WusdnToEthOracleMiddlewareWithPyth wusdnToEthOracleMiddleware + ) internal { + uint24 liquidationPenalty = usdnProtocol.getLiquidationPenalty(); + int24 tickSpacing = usdnProtocol.getTickSpacing(); + uint256 price = wusdnToEthOracleMiddleware.parseAndValidatePrice( + "", uint128(block.timestamp), Types.ProtocolAction.Initialize, "" + ).price; + + // we want a leverage of ~2x so we get the current price from the middleware and divide it by two + uint128 desiredLiqPrice = uint128(price / 2); + // get the liquidation price with the tick rounding + uint128 liqPriceWithoutPenalty = usdnProtocol.getLiqPriceFromDesiredLiqPrice( + desiredLiqPrice, price, 0, HugeUint.wrap(0), tickSpacing, liquidationPenalty + ); + // get the total exposure of the wanted long position + uint256 positionTotalExpo = + FixedPointMathLib.fullMulDiv(INITIAL_LONG_AMOUNT, price, price - liqPriceWithoutPenalty); + // get the amount to deposit to reach a balanced state + uint256 depositAmount = positionTotalExpo - INITIAL_LONG_AMOUNT; + + vm.startBroadcast(); + WUSDN.approve(address(usdnProtocol), depositAmount + INITIAL_LONG_AMOUNT); + usdnProtocol.initialize(uint128(depositAmount), uint128(INITIAL_LONG_AMOUNT), desiredLiqPrice, ""); + vm.stopBroadcast(); + } + + /** + * @dev Grants the required roles for the deployment. + * @param usdnProtocol The deployed USDN protocol. + * @param usdnNoRebase The USDN token of the protocol. + */ + function _grantRequiredRoles(IUsdnProtocol usdnProtocol, UsdnNoRebase usdnNoRebase) internal { + vm.startBroadcast(); + + usdnProtocol.grantRole(Constants.ADMIN_SET_EXTERNAL_ROLE, msg.sender); + usdnProtocol.grantRole(Constants.SET_EXTERNAL_ROLE, msg.sender); + + usdnNoRebase.transferOwnership(address(usdnProtocol)); + + vm.stopBroadcast(); + } + + /** + * @dev Revokes the roles that were only necessary during the deployment. + * @param usdnProtocol The deployed USDN protocol. + */ + function _revokeRoles(IUsdnProtocol usdnProtocol) internal { + vm.startBroadcast(); + + usdnProtocol.revokeRole(Constants.SET_EXTERNAL_ROLE, msg.sender); + usdnProtocol.revokeRole(Constants.ADMIN_SET_EXTERNAL_ROLE, msg.sender); + + vm.stopBroadcast(); + } +} diff --git a/script/55_UpgradeV2.s.sol b/script/55_UpgradeV2.s.sol new file mode 100644 index 000000000..a40f2fc77 --- /dev/null +++ b/script/55_UpgradeV2.s.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.26; + +import { Script } from "forge-std/Script.sol"; + +import { UsdnWstethUsdConfig } from "./deploymentConfigs/UsdnWstethUsdConfig.sol"; +import { Utils } from "./utils/Utils.s.sol"; + +import { UsdnProtocolFallback } from "../src/UsdnProtocol/UsdnProtocolFallback.sol"; +import { UsdnProtocolImpl } from "../src/UsdnProtocol/UsdnProtocolImpl.sol"; +import { UsdnProtocolConstantsLibrary as Constants } from + "../src/UsdnProtocol/libraries/UsdnProtocolConstantsLibrary.sol"; +import { IUsdnProtocol } from "../src/interfaces/UsdnProtocol/IUsdnProtocol.sol"; + +/** + * @title Upgrade script + * @notice This script is only made for upgrading the Usdn protocol from v1.x.x to v2.0.0. + * @dev The sender must already have the `PROXY_UPGRADE_ROLE` before launching this script. + */ +contract UpgradeV2 is UsdnWstethUsdConfig, Script { + IUsdnProtocol constant USDN_PROTOCOL = IUsdnProtocol(0x656cB8C6d154Aad29d8771384089be5B5141f01a); + + /// @dev this is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1. + bytes32 constant IMPL_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; + + Utils utils; + + constructor() { + utils = new Utils(); + } + + function run() + external + returns (UsdnProtocolFallback newUsdnProtocolFallback_, UsdnProtocolImpl newUsdnProtocolImpl_) + { + utils.validateProtocol("UsdnProtocolImpl", "UsdnProtocolFallback"); + + require( + USDN_PROTOCOL.hasRole(Constants.PROXY_UPGRADE_ROLE, msg.sender), + "Sender does not have the permission to upgrade the protocol" + ); + bytes32 oldImpl = vm.load(address(USDN_PROTOCOL), IMPL_SLOT); + + vm.startBroadcast(); + + newUsdnProtocolFallback_ = new UsdnProtocolFallback(MAX_SDEX_BURN_RATIO, MAX_MIN_LONG_POSITION); + newUsdnProtocolImpl_ = new UsdnProtocolImpl(); + USDN_PROTOCOL.upgradeToAndCall( + address(newUsdnProtocolImpl_), + // abi.encodeWithSelector(UsdnProtocolImpl.initializeStorageV2.selector, address(newUsdnProtocolFallback_)) + abi.encodeWithSelector(UsdnProtocolImpl.initializeStorage.selector, address(newUsdnProtocolFallback_)) + ); + + bytes32 newImpl = vm.load(address(USDN_PROTOCOL), IMPL_SLOT); + require(oldImpl != newImpl, "Upgrade failed"); + require(address(uint160(uint256(newImpl))) == address(newUsdnProtocolImpl_), "Upgrade failed"); + require(USDN_PROTOCOL.getSdexBurnOnDepositRatio() > 0, "New storage not initialized"); + require(USDN_PROTOCOL.getFallbackAddress() == address(newUsdnProtocolFallback_), "New fallback not set"); + + vm.stopBroadcast(); + } +} diff --git a/script/99_DeployProtocolFork.s.sol b/script/99_DeployProtocolFork.s.sol deleted file mode 100644 index 1e42b881a..000000000 --- a/script/99_DeployProtocolFork.s.sol +++ /dev/null @@ -1,404 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity 0.8.26; - -import { Script } from "forge-std/Script.sol"; - -import { HugeUint } from "@smardex-solidity-libraries-1/HugeUint.sol"; -import { Options, Upgrades } from "openzeppelin-foundry-upgrades/Upgrades.sol"; -import { FixedPointMathLib } from "solady/src/utils/FixedPointMathLib.sol"; - -import { Sdex } from "../test/utils/Sdex.sol"; -import { WstETH } from "../test/utils/WstEth.sol"; - -import { Utils } from "./utils/Utils.s.sol"; - -import { LiquidationRewardsManager } from "../src/LiquidationRewardsManager/LiquidationRewardsManager.sol"; -import { WstEthOracleMiddleware } from "../src/OracleMiddleware/WstEthOracleMiddleware.sol"; -import { MockWstEthOracleMiddleware } from "../src/OracleMiddleware/mock/MockWstEthOracleMiddleware.sol"; -import { Rebalancer } from "../src/Rebalancer/Rebalancer.sol"; -import { Usdn } from "../src/Usdn/Usdn.sol"; -import { Wusdn } from "../src/Usdn/Wusdn.sol"; -import { UsdnProtocolFallback } from "../src/UsdnProtocol/UsdnProtocolFallback.sol"; -import { UsdnProtocolImpl } from "../src/UsdnProtocol/UsdnProtocolImpl.sol"; -import { UsdnProtocolConstantsLibrary as Constants } from - "../src/UsdnProtocol/libraries/UsdnProtocolConstantsLibrary.sol"; -import { IWstETH } from "../src/interfaces/IWstETH.sol"; -import { IUsdnProtocol } from "../src/interfaces/UsdnProtocol/IUsdnProtocol.sol"; -import { IUsdnProtocolTypes as Types } from "../src/interfaces/UsdnProtocol/IUsdnProtocolTypes.sol"; - -contract DeployProtocol is Script { - address constant WSTETH_MAINNET = 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0; - address constant SDEX_MAINNET = 0x5DE8ab7E27f6E7A1fFf3E5B337584Aa43961BEeF; - address constant USDN_MAINNET = 0xde17a000BA631c5d7c2Bd9FB692EFeA52D90DEE2; - address constant WUSDN_MAINNET = 0x99999999999999Cc837C997B882957daFdCb1Af9; - address constant PYTH_MAINNET = 0x4305FB66699C3B2702D4d05CF36551390A4c69C6; - bytes32 constant PYTH_ETH_FEED_ID = 0xff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace; - address constant CHAINLINK_ETH_PRICE_MAINNET = 0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419; - uint256 constant CHAINLINK_PRICE_VALIDITY = 1 hours + 2 minutes; - uint256 constant CHAINLINK_GAS_PRICE_VALIDITY = 2 hours + 5 minutes; - - Utils internal _utils = new Utils(); - address internal _deployerAddress; - address internal _feeCollector; - bool internal _isProdEnv; - uint256 internal _longAmount; - address internal _safeAddress; - - /** - * @notice Deploy the USDN ecosystem - * @return WstETH_ The WstETH token - * @return Sdex_ The SDEX token - * @return WstEthOracleMiddleware_ The WstETH oracle middleware - * @return LiquidationRewardsManager_ The liquidation rewards manager - * @return Rebalancer_ The rebalancer - * @return Usdn_ The USDN token - * @return Wusdn_ The WUSDN token - * @return UsdnProtocol_ The USDN protocol with fallback - */ - function run() - external - returns ( - WstETH WstETH_, - Sdex Sdex_, - WstEthOracleMiddleware WstEthOracleMiddleware_, - LiquidationRewardsManager LiquidationRewardsManager_, - Rebalancer Rebalancer_, - Usdn Usdn_, - Wusdn Wusdn_, - IUsdnProtocol UsdnProtocol_ - ) - { - _handleEnvVariables(); - - // internal validation of the Usdn protocol - _utils.validateProtocol("UsdnProtocolImpl", "UsdnProtocolFallback"); - - vm.startBroadcast(_deployerAddress); - - (Usdn_, Wusdn_, Sdex_, WstETH_) = _handlePeripheryDeployment(); - - WstEthOracleMiddleware_ = _deployWstEthOracleMiddleware(address(WstETH_)); - - LiquidationRewardsManager_ = _deployLiquidationRewardsManager(address(WstETH_)); - - // we need to stop the broadcast before the OZ validation of the Usdn protocol - vm.stopBroadcast(); - - Types.InitStorage memory initStorage; - UsdnProtocol_ = _deployProtocol(initStorage); - - vm.startBroadcast(_deployerAddress); - - Rebalancer_ = _deployRebalancer(UsdnProtocol_); - - if (_isProdEnv) { - _handlePostDeployment(UsdnProtocol_, Rebalancer_, WstEthOracleMiddleware_, LiquidationRewardsManager_); - } else { - _initializeUsdnProtocolFork(UsdnProtocol_, WstETH_, WstEthOracleMiddleware_, Usdn_, Rebalancer_); - } - - vm.stopBroadcast(); - } - - /** - * @notice Deploy the USDN protocol - * @param initStorage The storage initialization parameters - * @return usdnProtocol_ The deployed protocol - */ - function _deployProtocol(Types.InitStorage memory initStorage) internal returns (IUsdnProtocol usdnProtocol_) { - // clean and build contracts for openzeppelin module - _utils.cleanAndBuildContracts(); - - // we need to allow external library linking and immutable variables in the openzeppelin module - Options memory opts; - opts.unsafeAllow = "external-library-linking,state-variable-immutable"; - - vm.startBroadcast(_deployerAddress); - // UsdnProtocolFallback protocolFallback = new UsdnProtocolFallback(); - address proxy = Upgrades.deployUUPSProxy( - "UsdnProtocolImpl.sol", abi.encodeCall(UsdnProtocolImpl.initializeStorage, (initStorage)), opts - ); - - vm.stopBroadcast(); - - usdnProtocol_ = IUsdnProtocol(proxy); - } - - /** - * @notice Deploy the WstETH oracle middleware if necessary - * @dev Will return the already deployed one if an address is in the env variables - * @param wstETHAddress The address of the WstETH token - * @return wstEthOracleMiddleware_ The deployed contract - */ - function _deployWstEthOracleMiddleware(address wstETHAddress) - internal - returns (WstEthOracleMiddleware wstEthOracleMiddleware_) - { - address middlewareAddress = vm.envOr("MIDDLEWARE_ADDRESS", address(0)); - - if (middlewareAddress != address(0)) { - if (_isProdEnv) { - wstEthOracleMiddleware_ = WstEthOracleMiddleware(middlewareAddress); - } else { - wstEthOracleMiddleware_ = MockWstEthOracleMiddleware(middlewareAddress); - } - } else { - address pythAddress = vm.envOr("PYTH_ADDRESS", PYTH_MAINNET); - bytes32 pythFeedId = vm.envOr("PYTH_ETH_FEED_ID", PYTH_ETH_FEED_ID); - address chainlinkPriceAddress = vm.envOr("CHAINLINK_ETH_PRICE_ADDRESS", CHAINLINK_ETH_PRICE_MAINNET); - uint256 chainlinkPriceValidity = vm.envOr("CHAINLINK_ETH_PRICE_VALIDITY", CHAINLINK_PRICE_VALIDITY); - - if (_isProdEnv) { - wstEthOracleMiddleware_ = new WstEthOracleMiddleware( - pythAddress, pythFeedId, chainlinkPriceAddress, wstETHAddress, chainlinkPriceValidity - ); - } else { - wstEthOracleMiddleware_ = new MockWstEthOracleMiddleware( - pythAddress, pythFeedId, chainlinkPriceAddress, wstETHAddress, chainlinkPriceValidity - ); - uint256 initialWSTETHMockedPrice = vm.envOr("INITIAL_WSTETH_MOCKED_PRICE", uint256(0)); - if (initialWSTETHMockedPrice > 0) { - MockWstEthOracleMiddleware(address(wstEthOracleMiddleware_)).setVerifySignature(false); - MockWstEthOracleMiddleware(address(wstEthOracleMiddleware_)).setWstethMockedPrice( - initialWSTETHMockedPrice - ); - } - } - } - } - - /** - * @notice Deploy the liquidation rewards manager if necessary - * @dev Will return the already deployed one if an address is in the env variables - * @param wstETHAddress The address of the WstETH token - * @return liquidationRewardsManager_ The deployed contract - */ - function _deployLiquidationRewardsManager(address wstETHAddress) - internal - returns (LiquidationRewardsManager liquidationRewardsManager_) - { - address liquidationRewardsManagerAddress = vm.envOr("LIQUIDATION_REWARDS_MANAGER_ADDRESS", address(0)); - - if (liquidationRewardsManagerAddress != address(0)) { - liquidationRewardsManager_ = LiquidationRewardsManager(liquidationRewardsManagerAddress); - } else { - liquidationRewardsManager_ = new LiquidationRewardsManager(IWstETH(wstETHAddress)); - } - } - - /** - * @notice Deploy the USDN token and the WUSDN token - * @dev Will return the already deployed ones if an address is in the env variables - * On mainnet the `WUSDN_ADDRESS` env variable is required - * @return usdn_ The deployed Usdn contract - * @return wusdn_ The deployed Wusdn contract - */ - function _deployUsdnAndWusdn() internal returns (Usdn usdn_, Wusdn wusdn_) { - address usdnAddress = payable(vm.envOr("USDN_ADDRESS", address(0))); - address wusdnAddress = payable(vm.envOr("WUSDN_ADDRESS", address(0))); - - if (usdnAddress != address(0)) { - usdn_ = Usdn(usdnAddress); - } else { - if (_isProdEnv) { - usdn_ = Usdn(USDN_MAINNET); - } else { - usdn_ = new Usdn(address(0), address(0)); - } - } - - if (wusdnAddress != address(0)) { - wusdn_ = Wusdn(wusdnAddress); - } else { - if (_isProdEnv) { - wusdn_ = Wusdn(WUSDN_MAINNET); - } else { - wusdn_ = new Wusdn(usdn_); - } - } - } - - /** - * @notice Deploy the SDEX token - * @dev Will return the already deployed one if an address is in the env variables - * Will use the mainnet address if the chainId is mainnet - * @return sdex_ The deployed contract - */ - function _deploySdex() internal returns (Sdex sdex_) { - if (_isProdEnv) { - return Sdex(SDEX_MAINNET); - } - - address sdexAddress = payable(vm.envOr("SDEX_ADDRESS", address(0))); - if (sdexAddress != address(0)) { - sdex_ = Sdex(sdexAddress); - } else { - sdex_ = new Sdex(); - } - } - - /** - * @notice Deploy the WstETH token - * @dev Will return the already deployed one if an address is in the env variables - * Will return the mainnet address if the chain is mainnet - * @return wstEth_ The deployed contract - */ - function _deployWstETH() internal returns (WstETH wstEth_) { - if (_isProdEnv) { - return WstETH(payable(WSTETH_MAINNET)); - } - - address payable wstETHAddress = payable(vm.envOr("WSTETH_ADDRESS", address(0))); - if (wstETHAddress != address(0)) { - wstEth_ = WstETH(wstETHAddress); - } else { - wstEth_ = new WstETH(); - } - } - - /** - * @notice Deploy the Rebalancer contract if necessary - * @dev Will return the already deployed one if an address is in the env variables - * @param usdnProtocol The USDN protocol - * @return rebalancer_ The deployed contract - */ - function _deployRebalancer(IUsdnProtocol usdnProtocol) internal returns (Rebalancer rebalancer_) { - address payable rebalancerAddress = payable(vm.envOr("REBALANCER_ADDRESS", address(0))); - - if (rebalancerAddress != address(0)) { - rebalancer_ = Rebalancer(rebalancerAddress); - } else { - rebalancer_ = new Rebalancer(usdnProtocol); - } - } - - /** - * @notice Initialize the USDN Protocol by opening a long and depositing the necessary amount - * @dev The deposit amount is calculated to reach a balanced state with a leverage of ~2x on the long position - * @param usdnProtocol The USDN protocol - * @param wstETH The WstETH token - * @param wstEthOracleMiddleware The WstETH oracle middleware - */ - function _initializeUsdnProtocolFork( - IUsdnProtocol usdnProtocol, - WstETH wstETH, - WstEthOracleMiddleware wstEthOracleMiddleware, - Usdn usdn, - Rebalancer rebalancer - ) internal { - usdnProtocol.grantRole(Constants.ADMIN_SET_EXTERNAL_ROLE, _deployerAddress); - usdnProtocol.grantRole(Constants.SET_EXTERNAL_ROLE, _deployerAddress); - - usdnProtocol.setRebalancer(rebalancer); - - usdn.grantRole(usdn.MINTER_ROLE(), address(usdnProtocol)); - usdn.grantRole(usdn.REBASER_ROLE(), address(usdnProtocol)); - - uint24 liquidationPenalty = usdnProtocol.getLiquidationPenalty(); - int24 tickSpacing = usdnProtocol.getTickSpacing(); - uint256 price = wstEthOracleMiddleware.parseAndValidatePrice( - "", uint128(block.timestamp), Types.ProtocolAction.Initialize, "" - ).price; - - // we want a leverage of ~2x so we get the current price from the middleware and divide it by two - uint128 desiredLiqPrice = uint128(price / 2); - // get the liquidation price with the tick rounding - uint128 liqPriceWithoutPenalty = usdnProtocol.getLiqPriceFromDesiredLiqPrice( - desiredLiqPrice, price, 0, HugeUint.wrap(0), tickSpacing, liquidationPenalty - ); - // get the total exposure of the wanted long position - uint256 positionTotalExpo = FixedPointMathLib.fullMulDiv(_longAmount, price, price - liqPriceWithoutPenalty); - // get the amount to deposit to reach a balanced state - uint256 depositAmount = positionTotalExpo - _longAmount; - - if (vm.envOr("GET_WSTETH", false)) { - uint256 ethAmount = (depositAmount + _longAmount + 10_000) * wstETH.stEthPerToken() / 1 ether; - (bool result,) = address(wstETH).call{ value: ethAmount }(hex""); - require(result, "Failed to mint wstETH"); - } - - wstETH.approve(address(usdnProtocol), depositAmount + _longAmount); - - usdnProtocol.initialize(uint128(depositAmount), uint128(_longAmount), desiredLiqPrice, ""); - } - - /** - * @notice Handle post-deployment tasks - * @param usdnProtocol The USDN protocol - * @param rebalancer The rebalancer - */ - function _handlePostDeployment( - IUsdnProtocol usdnProtocol, - Rebalancer rebalancer, - WstEthOracleMiddleware wstEthOracleMiddleware, - LiquidationRewardsManager liquidationRewardsManager - ) internal { - // grant the necessary roles to the deployer to set the rebalancer and then revoke them - bytes32 ADMIN_SET_EXTERNAL_ROLE = Constants.ADMIN_SET_EXTERNAL_ROLE; - bytes32 SET_EXTERNAL_ROLE = Constants.SET_EXTERNAL_ROLE; - bytes32 MIDDLEWARE_ADMIN_ROLE = wstEthOracleMiddleware.ADMIN_ROLE(); - - usdnProtocol.grantRole(ADMIN_SET_EXTERNAL_ROLE, _deployerAddress); - usdnProtocol.grantRole(SET_EXTERNAL_ROLE, _deployerAddress); - - usdnProtocol.setRebalancer(rebalancer); - - usdnProtocol.revokeRole(SET_EXTERNAL_ROLE, _deployerAddress); - usdnProtocol.revokeRole(ADMIN_SET_EXTERNAL_ROLE, _deployerAddress); - wstEthOracleMiddleware.revokeRole(MIDDLEWARE_ADMIN_ROLE, _deployerAddress); - - // transfer the ownership of the contracts to the safe address - usdnProtocol.beginDefaultAdminTransfer(_safeAddress); - wstEthOracleMiddleware.beginDefaultAdminTransfer(_safeAddress); - liquidationRewardsManager.transferOwnership(_safeAddress); - rebalancer.transferOwnership(_safeAddress); - } - - /** - * @notice Handle the deployment of the periphery contracts - * @return usdn_ The USDN token - * @return wusdn_ The WUSDN token - * @return sdex_ The SDEX token - * @return wstETH_ The WstETH token - */ - function _handlePeripheryDeployment() internal returns (Usdn usdn_, Wusdn wusdn_, Sdex sdex_, WstETH wstETH_) { - (usdn_, wusdn_) = _deployUsdnAndWusdn(); - wstETH_ = _deployWstETH(); - sdex_ = _deploySdex(); - } - - /// @notice Handle the environment variables - function _handleEnvVariables() internal { - // mandatory env variables : DEPLOYER_ADDRESS and IS_PROD_ENV - try vm.envAddress("DEPLOYER_ADDRESS") returns (address deployerAddress_) { - _deployerAddress = deployerAddress_; - } catch { - revert("DEPLOYER_ADDRESS is required"); - } - - try vm.envBool("IS_PROD_ENV") returns (bool isProdEnv_) { - _isProdEnv = isProdEnv_; - } catch { - revert("IS_PROD_ENV is required"); - } - - if (_isProdEnv) { - try vm.envAddress("SAFE_ADDRESS") returns (address safeAddress_) { - _safeAddress = safeAddress_; - } catch { - revert("SAFE_ADDRESS is required"); - } - _feeCollector = vm.envOr("FEE_COLLECTOR", _safeAddress); - } else { - try vm.envUint("INIT_LONG_AMOUNT") returns (uint256 initLongAmount_) { - _longAmount = initLongAmount_; - } catch { - revert("INIT_LONG_AMOUNT is required"); - } - _feeCollector = vm.envOr("FEE_COLLECTOR", _deployerAddress); - } - - string memory etherscanApiKey = vm.envOr("ETHERSCAN_API_KEY", string("XXXXXXXXXXXXXXXXX")); - vm.setEnv("ETHERSCAN_API_KEY", etherscanApiKey); - } -} diff --git a/script/README.md b/script/README.md index 0f79153e4..1a2f099cb 100644 --- a/script/README.md +++ b/script/README.md @@ -4,10 +4,10 @@ ### Production mode -For a mainnet deployment, you can use the `01_DeployUsdnWsteth.s.sol` script with: +For a mainnet deployment, you can use the `01_DeployUsdnWstethUsd.s.sol` script with: ```bash -forge clean && forge script -f RPC_URL script/01_DeployUsdnWsteth.s.sol:DeployUsdnWsteth --broadcast -i 1 --batch-size 5 +forge clean && forge script -f RPC_URL script/01_DeployUsdnWstethUsd.s.sol:DeployUsdnWstethUsd --broadcast -i 1 --batch-size 5 ``` You can use `-t` or `-l` options instead of `-i 1` for trezor or ledger hardware wallet. The `forge clean` command is necessary to use the OpenZeppelin verification tool. @@ -17,9 +17,12 @@ You can use `-t` or `-l` options instead of `-i 1` for trezor or ledger hardware The deployment script for the fork mode does not require any input: ```bash -deployFork.sh +./script/fork/deployFork.sh ``` +You can define `UNDERLYING_ADDRESS` and/or `START_PRICE` env variables. +If you do so, the `wStEth` asset will be replaced by `UNDERLYING_ADDRESS` and the underlying price will then be defined to `START_PRICE`. + ## Upgrade protocol Each upgrade logic depends on the implementation, so no boilerplate can be used for every upgrade version. This means you need to checkout to the corresponding tag version to see the exact upgrade script used. diff --git a/script/deploymentConfigs/DeploymentConfig.sol b/script/deploymentConfigs/DeploymentConfig.sol index f266bcb1f..c971a9e5f 100644 --- a/script/deploymentConfigs/DeploymentConfig.sol +++ b/script/deploymentConfigs/DeploymentConfig.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity 0.8.26; +pragma solidity ^0.8.0; import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import { ILiquidationRewardsManager } from "../../src/interfaces/LiquidationRewardsManager/ILiquidationRewardsManager.sol"; -import { IOracleMiddleware } from "../../src/interfaces/OracleMiddleware/IOracleMiddleware.sol"; +import { IOracleMiddlewareWithPyth } from "../../src/interfaces/OracleMiddleware/IOracleMiddlewareWithPyth.sol"; import { IUsdn } from "../../src/interfaces/Usdn/IUsdn.sol"; import { IUsdnProtocolFallback } from "../../src/interfaces/UsdnProtocol/IUsdnProtocolFallback.sol"; import { IUsdnProtocolTypes as Types } from "../../src/interfaces/UsdnProtocol/IUsdnProtocolTypes.sol"; @@ -26,7 +26,7 @@ abstract contract DeploymentConfig { * @param usdn The USDN token contract. */ function _setPeripheralContracts( - IOracleMiddleware oracleMiddleware, + IOracleMiddlewareWithPyth oracleMiddleware, ILiquidationRewardsManager liquidationRewardsManager, IUsdn usdn ) internal virtual; diff --git a/script/deploymentConfigs/UsdnWstethConfig.sol b/script/deploymentConfigs/UsdnWstethUsdConfig.sol similarity index 89% rename from script/deploymentConfigs/UsdnWstethConfig.sol rename to script/deploymentConfigs/UsdnWstethUsdConfig.sol index 903d504fb..f2c67d9ae 100644 --- a/script/deploymentConfigs/UsdnWstethConfig.sol +++ b/script/deploymentConfigs/UsdnWstethUsdConfig.sol @@ -8,23 +8,26 @@ import { UsdnProtocolConstantsLibrary as Constants } from import { IWstETH } from "../../src/interfaces/IWstETH.sol"; import { ILiquidationRewardsManager } from "../../src/interfaces/LiquidationRewardsManager/ILiquidationRewardsManager.sol"; -import { IOracleMiddleware } from "../../src/interfaces/OracleMiddleware/IOracleMiddleware.sol"; +import { IOracleMiddlewareWithPyth } from "../../src/interfaces/OracleMiddleware/IOracleMiddlewareWithPyth.sol"; import { IUsdn } from "../../src/interfaces/Usdn/IUsdn.sol"; import { IUsdnProtocolFallback } from "../../src/interfaces/UsdnProtocol/IUsdnProtocolFallback.sol"; import { Sdex } from "../../test/utils/Sdex.sol"; /// @notice Configuration contract for the USDN protocol backed with WSTETH deployment. -contract UsdnWstethConfig is DeploymentConfig { +contract UsdnWstethUsdConfig is DeploymentConfig { address constant CHAINLINK_ETH_PRICE = 0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419; address constant PYTH_ADDRESS = 0x4305FB66699C3B2702D4d05CF36551390A4c69C6; + IWstETH constant WSTETH = IWstETH(0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0); bytes32 constant PYTH_ETH_FEED_ID = 0xff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace; uint256 constant CHAINLINK_GAS_PRICE_VALIDITY = 2 hours + 5 minutes; uint256 constant CHAINLINK_PRICE_VALIDITY = 1 hours + 2 minutes; + uint256 constant MAX_SDEX_BURN_RATIO = Constants.SDEX_BURN_ON_DEPOSIT_DIVISOR / 10; // 10% + uint256 constant MAX_MIN_LONG_POSITION = 10 ether; constructor() { INITIAL_LONG_AMOUNT = 200 ether; SDEX = Sdex(0x5DE8ab7E27f6E7A1fFf3E5B337584Aa43961BEeF); - UNDERLYING_ASSET = IWstETH(0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0); + UNDERLYING_ASSET = WSTETH; initStorage.minLeverage = 10 ** Constants.LEVERAGE_DECIMALS + 10 ** (Constants.LEVERAGE_DECIMALS - 1); // x1.1 initStorage.maxLeverage = 10 * 10 ** Constants.LEVERAGE_DECIMALS; // x10 @@ -58,7 +61,7 @@ contract UsdnWstethConfig is DeploymentConfig { /// @inheritdoc DeploymentConfig function _setPeripheralContracts( - IOracleMiddleware oracleMiddleware, + IOracleMiddlewareWithPyth oracleMiddleware, ILiquidationRewardsManager liquidationRewardsManager, IUsdn usdn ) internal override { diff --git a/script/deploymentConfigs/UsdnWusdnEthConfig.sol b/script/deploymentConfigs/UsdnWusdnEthConfig.sol new file mode 100644 index 000000000..514b414ee --- /dev/null +++ b/script/deploymentConfigs/UsdnWusdnEthConfig.sol @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.26; + +import { DeploymentConfig } from "./DeploymentConfig.sol"; + +import { UsdnProtocolConstantsLibrary as Constants } from + "../../src/UsdnProtocol/libraries/UsdnProtocolConstantsLibrary.sol"; + +import { ILiquidationRewardsManager } from + "../../src/interfaces/LiquidationRewardsManager/ILiquidationRewardsManager.sol"; +import { IOracleMiddlewareWithPyth } from "../../src/interfaces/OracleMiddleware/IOracleMiddlewareWithPyth.sol"; +import { IUsdn } from "../../src/interfaces/Usdn/IUsdn.sol"; +import { IWusdn } from "../../src/interfaces/Usdn/IWusdn.sol"; +import { IUsdnProtocolFallback } from "../../src/interfaces/UsdnProtocol/IUsdnProtocolFallback.sol"; +import { Sdex } from "../../test/utils/Sdex.sol"; + +/// @notice Configuration contract for the USDN protocol backed with WUSDN deployment. +contract UsdnWusdnEthConfig is DeploymentConfig { + address constant CHAINLINK_ETH_PRICE = 0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419; + address constant PYTH_ADDRESS = 0x4305FB66699C3B2702D4d05CF36551390A4c69C6; + bytes32 constant PYTH_ETH_FEED_ID = 0xff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace; + uint256 constant CHAINLINK_PRICE_VALIDITY = 1 hours + 2 minutes; + /// @dev As the ratio is already high by default, the max value needs to be even higher. + uint256 constant MAX_SDEX_BURN_RATIO = type(uint32).max; // ~4294% + uint256 constant MAX_MIN_LONG_POSITION = 10_000 ether; + + constructor() { + // TODO decide of an initial amount + INITIAL_LONG_AMOUNT = 250_000 ether; + SDEX = Sdex(0x5DE8ab7E27f6E7A1fFf3E5B337584Aa43961BEeF); + UNDERLYING_ASSET = IWusdn(0x99999999999999Cc837C997B882957daFdCb1Af9); + + initStorage.minLeverage = 10 ** Constants.LEVERAGE_DECIMALS + 10 ** (Constants.LEVERAGE_DECIMALS - 1); // x1.1 + initStorage.maxLeverage = 25 * 10 ** Constants.LEVERAGE_DECIMALS; // x10 + initStorage.lowLatencyValidatorDeadline = 15 minutes; + initStorage.onChainValidatorDeadline = 65 minutes; // slightly more than chainlink's heartbeat + initStorage.safetyMarginBps = 200; // 2% + initStorage.liquidationIteration = 1; + initStorage.protocolFeeBps = 800; // 8% + initStorage.rebalancerBonusBps = 8750; // 87.5% + initStorage.liquidationPenalty = 200; // 200 ticks -> ~2.02% + initStorage.emaPeriod = 16 hours; + initStorage.fundingSF = 75 * 10 ** (Constants.FUNDING_SF_DECIMALS - 2); // 0.75 + initStorage.feeThreshold = 2000 ether; + initStorage.openExpoImbalanceLimitBps = 400; // 4% + initStorage.withdrawalExpoImbalanceLimitBps = 600; // 6% + initStorage.depositExpoImbalanceLimitBps = 400; // 4% + initStorage.closeExpoImbalanceLimitBps = 600; // 6% + initStorage.rebalancerCloseExpoImbalanceLimitBps = 250; // 2.5% + initStorage.longImbalanceTargetBps = 300; // 3% + initStorage.positionFeeBps = 1; // 0.01% + initStorage.vaultFeeBps = 4; // 0.04% + initStorage.sdexRewardsRatioBps = 100; // 1% + // for each syntETH, 75 SDEX will be burned + initStorage.sdexBurnOnDepositRatio = uint64(75 * Constants.SDEX_BURN_ON_DEPOSIT_DIVISOR); // x75 + initStorage.securityDepositValue = 0.15 ether; + initStorage.EMA = int256(3 * 10 ** (Constants.FUNDING_RATE_DECIMALS - 4)); // 0.0003 + initStorage.tickSpacing = 100; + initStorage.sdex = SDEX; + initStorage.asset = UNDERLYING_ASSET; + // 1400 wusdn (to roughly match the current minLongPosition of the wstETH/USD version) + initStorage.minLongPosition = 1400 * 10 ** UNDERLYING_ASSET.decimals(); + } + + /// @inheritdoc DeploymentConfig + function _setPeripheralContracts( + IOracleMiddlewareWithPyth oracleMiddleware, + ILiquidationRewardsManager liquidationRewardsManager, + IUsdn usdnNoRebase + ) internal override { + initStorage.oracleMiddleware = oracleMiddleware; + initStorage.liquidationRewardsManager = liquidationRewardsManager; + uint8 priceFeedDecimals = oracleMiddleware.getDecimals(); + // set usdn prices for compatibility as the usdn token used does not rebase + initStorage.targetUsdnPrice = uint128(10 ** priceFeedDecimals); + initStorage.usdnRebaseThreshold = uint128(10 ** priceFeedDecimals); + initStorage.usdn = usdnNoRebase; + } + + /// @inheritdoc DeploymentConfig + function _setFeeCollector(address feeCollector) internal override { + initStorage.feeCollector = feeCollector; + } + + /// @inheritdoc DeploymentConfig + function _setProtocolFallback(IUsdnProtocolFallback protocolFallback) internal override { + initStorage.protocolFallbackAddr = address(protocolFallback); + } +} diff --git a/script/fork/DeployUsdnWstethFork.s.sol b/script/fork/DeployUsdnWstethFork.s.sol new file mode 100644 index 000000000..eff99d9f8 --- /dev/null +++ b/script/fork/DeployUsdnWstethFork.s.sol @@ -0,0 +1,201 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.26; + +import { Script } from "forge-std/Script.sol"; + +import { HugeUint } from "@smardex-solidity-libraries-1/HugeUint.sol"; +import { UnsafeUpgrades } from "openzeppelin-foundry-upgrades/Upgrades.sol"; +import { FixedPointMathLib } from "solady/src/utils/FixedPointMathLib.sol"; + +import { MockChainlinkOnChain } from "../../test/unit/Middlewares/utils/MockChainlinkOnChain.sol"; +import { WstETH } from "../../test/utils/WstEth.sol"; +import { UsdnWstethUsdConfig } from "../deploymentConfigs/UsdnWstethUsdConfig.sol"; +import { Utils } from "../utils/Utils.s.sol"; + +import { LiquidationRewardsManagerWstEth } from + "../../src/LiquidationRewardsManager/LiquidationRewardsManagerWstEth.sol"; +import { MockWstEthOracleMiddlewareWithPyth } from + "../../src/OracleMiddleware/mock/MockWstEthOracleMiddlewareWithPyth.sol"; +import { Rebalancer } from "../../src/Rebalancer/Rebalancer.sol"; +import { Usdn } from "../../src/Usdn/Usdn.sol"; +import { Wusdn } from "../../src/Usdn/Wusdn.sol"; +import { UsdnProtocolFallback } from "../../src/UsdnProtocol/UsdnProtocolFallback.sol"; +import { UsdnProtocolImpl } from "../../src/UsdnProtocol/UsdnProtocolImpl.sol"; +import { UsdnProtocolConstantsLibrary as Constants } from + "../../src/UsdnProtocol/libraries/UsdnProtocolConstantsLibrary.sol"; +import { IWstETH } from "../../src/interfaces/IWstETH.sol"; +import { IUsdnProtocol } from "../../src/interfaces/UsdnProtocol/IUsdnProtocol.sol"; +import { IUsdnProtocolTypes as Types } from "../../src/interfaces/UsdnProtocol/IUsdnProtocolTypes.sol"; + +contract DeployUsdnWstethFork is UsdnWstethUsdConfig, Script { + address immutable CHAINLINK_ETH_PRICE_MOCKED = address(new MockChainlinkOnChain()); + uint256 price = 3000 ether; + Utils utils; + + constructor() UsdnWstethUsdConfig() { + UNDERLYING_ASSET = IWstETH(vm.envOr("UNDERLYING_ADDRESS", address(WSTETH))); + utils = new Utils(); + price = vm.envOr("START_PRICE", price); + } + + /** + * @notice Deploy the USDN ecosystem with the WstETH as underlying + * @return wstEthOracleMiddleware_ The WstETH oracle middleware + * @return liquidationRewardsManager_ The liquidation rewards manager + * @return rebalancer_ The rebalancer + * @return usdn_ The USDN contract + * @return wusdn_ The WUSDN contract + * @return usdnProtocol_ The USDN protocol + */ + function run() + external + returns ( + MockWstEthOracleMiddlewareWithPyth wstEthOracleMiddleware_, + LiquidationRewardsManagerWstEth liquidationRewardsManager_, + Rebalancer rebalancer_, + Usdn usdn_, + Wusdn wusdn_, + IUsdnProtocol usdnProtocol_, + address underlying_, + address sdex_ + ) + { + _setFeeCollector(msg.sender); + + (wstEthOracleMiddleware_, liquidationRewardsManager_, usdn_, wusdn_) = _deployAndSetPeripheralContracts(); + + usdnProtocol_ = _deployProtocol(initStorage); + + rebalancer_ = _setRebalancerAndHandleUsdnRoles(usdnProtocol_, usdn_); + + _initializeProtocol(usdnProtocol_); + + utils.validateProtocolConfig(usdnProtocol_, msg.sender); + + underlying_ = address(UNDERLYING_ASSET); + + sdex_ = address(SDEX); + } + + /** + * @notice Deploy the oracle middleware, liquidation rewards manager, USDN and WUSDN contracts. Add then to the + * initialization struct. + * @return wstEthOracleMiddleware_ The WstETH oracle middleware + * @return liquidationRewardsManager_ The liquidation rewards manager + * @return usdn_ The USDN contract + * @return wusdn_ The WUSDN contract + */ + function _deployAndSetPeripheralContracts() + internal + returns ( + MockWstEthOracleMiddlewareWithPyth wstEthOracleMiddleware_, + LiquidationRewardsManagerWstEth liquidationRewardsManager_, + Usdn usdn_, + Wusdn wusdn_ + ) + { + vm.startBroadcast(); + liquidationRewardsManager_ = new LiquidationRewardsManagerWstEth(WSTETH); + wstEthOracleMiddleware_ = new MockWstEthOracleMiddlewareWithPyth( + PYTH_ADDRESS, PYTH_ETH_FEED_ID, CHAINLINK_ETH_PRICE_MOCKED, address(WSTETH), CHAINLINK_PRICE_VALIDITY + ); + MockWstEthOracleMiddlewareWithPyth(wstEthOracleMiddleware_).setVerifySignature(false); + MockWstEthOracleMiddlewareWithPyth(wstEthOracleMiddleware_).setWstethMockedPrice(price); + usdn_ = new Usdn(address(0), address(0)); + wusdn_ = new Wusdn(usdn_); + vm.stopBroadcast(); + + _setPeripheralContracts(wstEthOracleMiddleware_, liquidationRewardsManager_, usdn_); + } + + /** + * @notice Deploy the USDN protocol. + * @param initStorage The initialization parameters struct. + * @return usdnProtocol_ The USDN protocol proxy. + */ + function _deployProtocol(Types.InitStorage storage initStorage) internal returns (IUsdnProtocol usdnProtocol_) { + vm.startBroadcast(); + + UsdnProtocolFallback protocolFallback = new UsdnProtocolFallback(MAX_SDEX_BURN_RATIO, MAX_MIN_LONG_POSITION); + _setProtocolFallback(protocolFallback); + + address proxy = UnsafeUpgrades.deployUUPSProxy( + address(new UsdnProtocolImpl()), abi.encodeCall(UsdnProtocolImpl.initializeStorage, initStorage) + ); + + vm.stopBroadcast(); + + usdnProtocol_ = IUsdnProtocol(proxy); + } + + /** + * @notice Set the rebalancer and give the minting and rebasing roles to the USDN protocol. + * @param usdnProtocol The USDN protocol. + * @param usdn The USDN token. + * @return rebalancer_ The rebalancer. + */ + function _setRebalancerAndHandleUsdnRoles(IUsdnProtocol usdnProtocol, Usdn usdn) + internal + returns (Rebalancer rebalancer_) + { + vm.startBroadcast(); + + rebalancer_ = new Rebalancer(usdnProtocol); + usdnProtocol.grantRole(Constants.ADMIN_SET_EXTERNAL_ROLE, msg.sender); + usdnProtocol.grantRole(Constants.ADMIN_SET_OPTIONS_ROLE, msg.sender); + usdnProtocol.grantRole(Constants.ADMIN_SET_PROTOCOL_PARAMS_ROLE, msg.sender); + usdnProtocol.grantRole(Constants.ADMIN_SET_USDN_PARAMS_ROLE, msg.sender); + usdnProtocol.grantRole(Constants.SET_EXTERNAL_ROLE, msg.sender); + usdnProtocol.grantRole(Constants.SET_OPTIONS_ROLE, msg.sender); + usdnProtocol.grantRole(Constants.SET_PROTOCOL_PARAMS_ROLE, msg.sender); + usdnProtocol.grantRole(Constants.SET_USDN_PARAMS_ROLE, msg.sender); + usdnProtocol.grantRole(Constants.ADMIN_CRITICAL_FUNCTIONS_ROLE, msg.sender); + usdnProtocol.grantRole(Constants.ADMIN_PROXY_UPGRADE_ROLE, msg.sender); + usdnProtocol.grantRole(Constants.ADMIN_PAUSER_ROLE, msg.sender); + usdnProtocol.grantRole(Constants.ADMIN_UNPAUSER_ROLE, msg.sender); + usdnProtocol.grantRole(Constants.CRITICAL_FUNCTIONS_ROLE, msg.sender); + usdnProtocol.grantRole(Constants.PROXY_UPGRADE_ROLE, msg.sender); + usdnProtocol.grantRole(Constants.PAUSER_ROLE, msg.sender); + usdnProtocol.grantRole(Constants.UNPAUSER_ROLE, msg.sender); + + usdnProtocol.setRebalancer(rebalancer_); + + usdn.grantRole(usdn.MINTER_ROLE(), address(usdnProtocol)); + usdn.grantRole(usdn.REBASER_ROLE(), address(usdnProtocol)); + usdn.grantRole(usdn.MINTER_ROLE(), msg.sender); + usdn.grantRole(usdn.REBASER_ROLE(), msg.sender); + + vm.stopBroadcast(); + } + + /** + * @notice Initialize the USDN protocol with a ~2x leverage long position. + * @param usdnProtocol The USDN protocol. + */ + function _initializeProtocol(IUsdnProtocol usdnProtocol) internal { + uint24 liquidationPenalty = usdnProtocol.getLiquidationPenalty(); + int24 tickSpacing = usdnProtocol.getTickSpacing(); + + // we want a leverage of ~2x so we get the current price from the middleware and divide it by two + uint128 desiredLiqPrice = uint128(price / 2); + // get the liquidation price with the tick rounding + uint128 liqPriceWithoutPenalty = usdnProtocol.getLiqPriceFromDesiredLiqPrice( + desiredLiqPrice, price, 0, HugeUint.wrap(0), tickSpacing, liquidationPenalty + ); + // get the total exposure of the wanted long position + uint256 positionTotalExpo = + FixedPointMathLib.fullMulDiv(INITIAL_LONG_AMOUNT, price, price - liqPriceWithoutPenalty); + // get the amount to deposit to reach a balanced state + uint256 depositAmount = positionTotalExpo - INITIAL_LONG_AMOUNT; + + uint256 ethAmount = (depositAmount + INITIAL_LONG_AMOUNT + 10_000) * WSTETH.stEthPerToken() / 1 ether; + + vm.startBroadcast(); + (bool result,) = address(WSTETH).call{ value: ethAmount }(hex""); + require(result, "Failed to mint wstETH"); + + WSTETH.approve(address(usdnProtocol), depositAmount + INITIAL_LONG_AMOUNT); + usdnProtocol.initialize(uint128(depositAmount), uint128(INITIAL_LONG_AMOUNT), desiredLiqPrice, ""); + vm.stopBroadcast(); + } +} diff --git a/script/deployFork.sh b/script/fork/deployFork.sh similarity index 65% rename from script/deployFork.sh rename to script/fork/deployFork.sh index afd62d020..86ba1b60a 100755 --- a/script/deployFork.sh +++ b/script/fork/deployFork.sh @@ -2,41 +2,34 @@ # Path of the script folder (so that the script can be invoked from somewhere else than the project's root) SCRIPT_DIR=$(dirname -- "$(readlink -f -- "$BASH_SOURCE")") # Execute in the context of the project's root -pushd $SCRIPT_DIR/.. >/dev/null +pushd $SCRIPT_DIR/../.. >/dev/null # Anvil RPC URL rpcUrl=http://localhost:8545 # Anvil first test private key deployerPrivateKey=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 -# Setup deployment script environment variables -export DEPLOYER_ADDRESS=$(cast wallet address --private-key "$deployerPrivateKey") #0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 -export INIT_LONG_AMOUNT=1000000000000000000000 -export INITIAL_WSTETH_MOCKED_PRICE=3000000000000000000000 -export GET_WSTETH=true -export IS_PROD_ENV=false - -forge script --non-interactive --private-key $deployerPrivateKey -f "$rpcUrl" script/01_DeployProtocol.s.sol:DeployProtocol --broadcast +forge script --non-interactive --private-key $deployerPrivateKey -f "$rpcUrl" ./script/fork/DeployUsdnWstethFork.s.sol:DeployUsdnWstethFork --broadcast chainId=$(cast chain-id -r "$rpcUrl") -DEPLOYMENT_LOG=$(cat "broadcast/01_DeployProtocol.s.sol/$chainId/run-latest.json") +DEPLOYMENT_LOG=$(cat "./broadcast/DeployUsdnWstethFork.s.sol/$chainId/run-latest.json") USDN_TX_HASH=$(echo "$DEPLOYMENT_LOG" | jq '.transactions[] | select(.contractName == "Usdn" and .transactionType == "CREATE") | .hash') USDN_RECEIPT=$(echo "$DEPLOYMENT_LOG" | jq ".receipts[] | select(.transactionHash == $USDN_TX_HASH)") USDN_PROTOCOL_TX_HASH=$(echo "$DEPLOYMENT_LOG" | jq '.transactions[] | select(.contractName == "ERC1967Proxy" and .transactionType == "CREATE") | .hash') USDN_PROTOCOL_RECEIPT=$(echo "$DEPLOYMENT_LOG" | jq ".receipts[] | select(.transactionHash == $USDN_PROTOCOL_TX_HASH)") -USDN_PROTOCOL_ADDRESS=$(echo "$DEPLOYMENT_LOG" | jq '.returns.UsdnProtocol_.value' | xargs printf "%s\n") +USDN_PROTOCOL_ADDRESS=$(echo "$DEPLOYMENT_LOG" | jq '.returns.usdnProtocol_.value' | xargs printf "%s\n") FORK_ENV_DUMP=$( cat <.env.fork +echo "$FORK_ENV_DUMP" > .env.fork popd >/dev/null diff --git a/soldeer.lock b/soldeer.lock index 5ff2e9c31..42e1d8d21 100644 --- a/soldeer.lock +++ b/soldeer.lock @@ -33,6 +33,13 @@ url = "https://soldeer-revisions.s3.amazonaws.com/@redstone-finance-evm-connecto checksum = "2c65c8850894b2e6420827cc1c03ab755d832230679be1bf0835c05aaefbc2bc" integrity = "91b013140e1099e6e5d92ef741bcffe7ba5642bc412e616b1dca9801cf3acd6e" +[[dependencies]] +name = "@smardex-dex-contracts" +version = "1.0.1" +url = "https://soldeer-revisions.s3.amazonaws.com/@smardex-dex-contracts/1_0_1_24-04-2025_11:42:41_dex-smart-contracts.zip" +checksum = "1a09d443f6c6c532382e9bb6a1576f9f72c5952808e02de5ba0ebf5c9ea74902" +integrity = "77d464e5e39c1d2c5f34a8e4d7ae9c95a47c58307e60ccc5f989d06de808ea83" + [[dependencies]] name = "@smardex-solidity-libraries" version = "1.0.1" @@ -47,6 +54,13 @@ url = "https://github.com/Uniswap/permit2/archive/cc56ad0f3439c502c246fc5cfcc3db checksum = "180e009e8abfc5ed43383418f2593ac790655b58ec18ae52a92a636d5f298ad4" integrity = "393047bfd13476671149d98263cf25e3ba7d301ef3f9e83f073c0183ea6b6586" +[[dependencies]] +name = "@uniswap-v3-core" +version = "1.0.2-solc-0.8-simulate" +url = "https://soldeer-revisions.s3.amazonaws.com/@uniswap-v3-core/1_0_2-solc-0_8-simulate_22-01-2024_13:19:54_v3-core.zip" +checksum = "68949c67a4f22044e4c8dc716362b6d79b07e8990e720bec9b116f54b233ef57" +integrity = "697a089ffe197580b6cfe07f0c4dd8a35da0836fd1d9e522f73efda4b7314e01" + [[dependencies]] name = "forge-std" version = "1.9.4" diff --git a/src/LiquidationRewardsManager/LiquidationRewardsManager.sol b/src/LiquidationRewardsManager/LiquidationRewardsManager.sol index 036eab728..8b7c51ece 100644 --- a/src/LiquidationRewardsManager/LiquidationRewardsManager.sol +++ b/src/LiquidationRewardsManager/LiquidationRewardsManager.sol @@ -1,11 +1,10 @@ // SPDX-License-Identifier: BUSL-1.1 -pragma solidity 0.8.26; +pragma solidity ^0.8.0; -import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; import { Ownable2Step } from "@openzeppelin/contracts/access/Ownable2Step.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { FixedPointMathLib } from "solady/src/utils/FixedPointMathLib.sol"; -import { IWstETH } from "../interfaces/IWstETH.sol"; import { IBaseLiquidationRewardsManager } from "../interfaces/LiquidationRewardsManager/IBaseLiquidationRewardsManager.sol"; import { ILiquidationRewardsManager } from "../interfaces/LiquidationRewardsManager/ILiquidationRewardsManager.sol"; @@ -13,10 +12,10 @@ import { IUsdnProtocolTypes as Types } from "../interfaces/UsdnProtocol/IUsdnPro /** * @title Liquidation Rewards Manager - * @notice This contract calculates rewards for liquidators within the USDN protocol. - * @dev Rewards are computed based on gas costs, position size, and other parameters. + * @dev This abstract contract calculates the bonus portion of the rewards based on the size of the liquidated ticks. + * The actual reward calculation is left to the implementing contract. */ -contract LiquidationRewardsManager is ILiquidationRewardsManager, Ownable2Step { +abstract contract LiquidationRewardsManager is ILiquidationRewardsManager, Ownable2Step { /* -------------------------------------------------------------------------- */ /* Constants */ /* -------------------------------------------------------------------------- */ @@ -43,30 +42,14 @@ contract LiquidationRewardsManager is ILiquidationRewardsManager, Ownable2Step { /* Storage Variables */ /* -------------------------------------------------------------------------- */ - /// @notice The address of the wrapped stETH (wstETH) token contract. - IWstETH private immutable _wstEth; + /// @notice The address of the reward asset. + IERC20 internal immutable _rewardAsset; /** * @notice Holds the parameters used for rewards calculation. * @dev Parameters should be updated to reflect changes in gas costs or protocol adjustments. */ - RewardsParameters private _rewardsParameters; - - /// @param wstETH The address of the wstETH token. - constructor(IWstETH wstETH) Ownable(msg.sender) { - _wstEth = wstETH; - _rewardsParameters = RewardsParameters({ - gasUsedPerTick: 53_094, - otherGasUsed: 469_537, - rebaseGasUsed: 13_765, - rebalancerGasUsed: 279_349, - baseFeeOffset: 2 gwei, - gasMultiplierBps: 10_500, // 1.05 - positionBonusMultiplierBps: 200, // 0.02 - fixedReward: 0.001 ether, - maxReward: 0.5 ether - }); - } + RewardsParameters internal _rewardsParameters; /// @inheritdoc IBaseLiquidationRewardsManager function getLiquidationRewards( @@ -74,40 +57,10 @@ contract LiquidationRewardsManager is ILiquidationRewardsManager, Ownable2Step { uint256 currentPrice, bool rebased, Types.RebalancerAction rebalancerAction, - Types.ProtocolAction, - bytes calldata, - bytes calldata - ) external view returns (uint256 wstETHRewards_) { - if (liquidatedTicks.length == 0) { - return 0; - } - - RewardsParameters memory rewardsParameters = _rewardsParameters; - // calculate the amount of gas spent during the liquidation - uint256 gasUsed = rewardsParameters.otherGasUsed + BASE_GAS_COST - + uint256(rewardsParameters.gasUsedPerTick) * liquidatedTicks.length; - if (rebased) { - gasUsed += rewardsParameters.rebaseGasUsed; - } - if (uint8(rebalancerAction) > uint8(Types.RebalancerAction.NoCloseNoOpen)) { - gasUsed += rewardsParameters.rebalancerGasUsed; - } - - uint256 totalRewardETH = rewardsParameters.fixedReward - + _calcGasPrice(rewardsParameters.baseFeeOffset) * gasUsed * rewardsParameters.gasMultiplierBps / BPS_DIVISOR; - - uint256 wstEthBonus = - _calcPositionSizeBonus(liquidatedTicks, currentPrice, rewardsParameters.positionBonusMultiplierBps); - - totalRewardETH += _wstEth.getStETHByWstETH(wstEthBonus); - - if (totalRewardETH > rewardsParameters.maxReward) { - totalRewardETH = rewardsParameters.maxReward; - } - - // convert to wstETH - wstETHRewards_ = _wstEth.getWstETHByStETH(totalRewardETH); - } + Types.ProtocolAction action, + bytes calldata rebaseCallbackResult, + bytes calldata priceData + ) external view virtual returns (uint256 rewards_); /// @inheritdoc ILiquidationRewardsManager function getRewardsParameters() external view returns (RewardsParameters memory) { @@ -178,7 +131,7 @@ contract LiquidationRewardsManager is ILiquidationRewardsManager, Ownable2Step { * @param liquidatedTicks Information about the liquidated ticks. * @param currentPrice The current asset price. * @param multiplier The bonus multiplier (in BPS). - * @return bonus_ The calculated bonus (in wstETH). + * @return bonus_ The calculated bonus (in _rewardAsset). */ function _calcPositionSizeBonus( Types.LiqTickInfo[] calldata liquidatedTicks, diff --git a/src/LiquidationRewardsManager/LiquidationRewardsManagerWstEth.sol b/src/LiquidationRewardsManager/LiquidationRewardsManagerWstEth.sol new file mode 100644 index 000000000..bcc6e4f9f --- /dev/null +++ b/src/LiquidationRewardsManager/LiquidationRewardsManagerWstEth.sol @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.26; + +import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; + +import { IWstETH } from "../interfaces/IWstETH.sol"; +import { IBaseLiquidationRewardsManager } from + "../interfaces/LiquidationRewardsManager/IBaseLiquidationRewardsManager.sol"; +import { IUsdnProtocolTypes as Types } from "../interfaces/UsdnProtocol/IUsdnProtocolTypes.sol"; +import { LiquidationRewardsManager } from "./LiquidationRewardsManager.sol"; + +/** + * @title Liquidation Rewards Manager for Wrapped stETH + * @notice This contract calculates rewards for liquidators within the USDN protocol, using wstETH as the underlying + * asset. Rewards are computed based on gas costs, which are converted to wstETH using the current conversion rate + * between wstETH and stETH, assuming a one-to-one rate between stETH and ETH. Additionally a fixed reward is added + * along with a bonus based on the size of the liquidated ticks. + */ +contract LiquidationRewardsManagerWstEth is LiquidationRewardsManager { + /// @param wstETH The address of the wstETH token. + constructor(IWstETH wstETH) Ownable(msg.sender) { + _rewardAsset = wstETH; + _rewardsParameters = RewardsParameters({ + gasUsedPerTick: 53_094, + otherGasUsed: 469_537, + rebaseGasUsed: 13_765, + rebalancerGasUsed: 279_349, + baseFeeOffset: 2 gwei, + gasMultiplierBps: 10_500, // 1.05 + positionBonusMultiplierBps: 200, // 0.02 + fixedReward: 0.001 ether, + maxReward: 0.5 ether + }); + } + + /// @inheritdoc IBaseLiquidationRewardsManager + function getLiquidationRewards( + Types.LiqTickInfo[] calldata liquidatedTicks, + uint256 currentPrice, + bool rebased, + Types.RebalancerAction rebalancerAction, + Types.ProtocolAction, + bytes calldata, + bytes calldata + ) external view override returns (uint256 wstETHRewards_) { + if (liquidatedTicks.length == 0) { + return 0; + } + + RewardsParameters memory rewardsParameters = _rewardsParameters; + // calculate the amount of gas spent during the liquidation + uint256 gasUsed = rewardsParameters.otherGasUsed + BASE_GAS_COST + + uint256(rewardsParameters.gasUsedPerTick) * liquidatedTicks.length; + if (rebased) { + gasUsed += rewardsParameters.rebaseGasUsed; + } + if (uint8(rebalancerAction) > uint8(Types.RebalancerAction.NoCloseNoOpen)) { + gasUsed += rewardsParameters.rebalancerGasUsed; + } + + uint256 totalRewardETH = rewardsParameters.fixedReward + + _calcGasPrice(rewardsParameters.baseFeeOffset) * gasUsed * rewardsParameters.gasMultiplierBps / BPS_DIVISOR; + + uint256 wstEthBonus = + _calcPositionSizeBonus(liquidatedTicks, currentPrice, rewardsParameters.positionBonusMultiplierBps); + + totalRewardETH += IWstETH(address(_rewardAsset)).getStETHByWstETH(wstEthBonus); + + if (totalRewardETH > rewardsParameters.maxReward) { + totalRewardETH = rewardsParameters.maxReward; + } + + // convert to wstETH + wstETHRewards_ = IWstETH(address(_rewardAsset)).getWstETHByStETH(totalRewardETH); + } +} diff --git a/src/LiquidationRewardsManager/LiquidationRewardsManagerWusdn.sol b/src/LiquidationRewardsManager/LiquidationRewardsManagerWusdn.sol new file mode 100644 index 000000000..c0c9b5c89 --- /dev/null +++ b/src/LiquidationRewardsManager/LiquidationRewardsManagerWusdn.sol @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.26; + +import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; +import { FixedPointMathLib } from "solady/src/utils/FixedPointMathLib.sol"; + +import { IBaseLiquidationRewardsManager } from + "../interfaces/LiquidationRewardsManager/IBaseLiquidationRewardsManager.sol"; +import { IWusdn } from "../interfaces/Usdn/IWusdn.sol"; +import { IUsdnProtocolTypes as Types } from "../interfaces/UsdnProtocol/IUsdnProtocolTypes.sol"; +import { LiquidationRewardsManager } from "./LiquidationRewardsManager.sol"; + +/** + * @title Liquidation Rewards Manager for Wrapped USDN + * @notice This contract calculates rewards for liquidators within the USDN protocol, using wUSDN as the underlying + * asset. Rewards are computed based on gas costs which are converted to wUSDN using the current price. Additionally a + * fixed reward is added along with a bonus based on the size of the liquidated ticks. + */ +contract LiquidationRewardsManagerWusdn is LiquidationRewardsManager { + /// @notice The precision used for the price. + uint256 internal constant PRICE_PRECISION = 1e18; + + /// @param wusdn The address of the wUSDN token. + constructor(IWusdn wusdn) Ownable(msg.sender) { + _rewardAsset = wusdn; + _rewardsParameters = RewardsParameters({ + gasUsedPerTick: 53_094, + otherGasUsed: 469_537, + rebaseGasUsed: 0, + rebalancerGasUsed: 279_349, + baseFeeOffset: 2 gwei, + gasMultiplierBps: 10_500, // 1.05 + positionBonusMultiplierBps: 200, // 0.02 + fixedReward: 2 ether, + maxReward: 1000 ether + }); + } + + /// @inheritdoc IBaseLiquidationRewardsManager + function getLiquidationRewards( + Types.LiqTickInfo[] calldata liquidatedTicks, + uint256 currentPrice, + bool, + Types.RebalancerAction rebalancerAction, + Types.ProtocolAction, + bytes calldata, + bytes calldata + ) external view override returns (uint256 wUsdnRewards_) { + if (liquidatedTicks.length == 0) { + return 0; + } + + RewardsParameters memory rewardsParameters = _rewardsParameters; + // calculate the amount of gas spent during the liquidation + uint256 gasUsed = rewardsParameters.otherGasUsed + BASE_GAS_COST + + uint256(rewardsParameters.gasUsedPerTick) * liquidatedTicks.length; + if (uint8(rebalancerAction) > uint8(Types.RebalancerAction.NoCloseNoOpen)) { + gasUsed += rewardsParameters.rebalancerGasUsed; + } + + uint256 gasRewards = + _calcGasPrice(rewardsParameters.baseFeeOffset) * gasUsed * rewardsParameters.gasMultiplierBps / BPS_DIVISOR; + + wUsdnRewards_ = rewardsParameters.fixedReward + + _calcPositionSizeBonus(liquidatedTicks, currentPrice, rewardsParameters.positionBonusMultiplierBps); + + wUsdnRewards_ += FixedPointMathLib.fullMulDiv(gasRewards, PRICE_PRECISION, currentPrice); + + if (wUsdnRewards_ > rewardsParameters.maxReward) { + wUsdnRewards_ = rewardsParameters.maxReward; + } + } +} diff --git a/src/OracleMiddleware/OracleMiddleware.sol b/src/OracleMiddleware/CommonOracleMiddleware.sol similarity index 55% rename from src/OracleMiddleware/OracleMiddleware.sol rename to src/OracleMiddleware/CommonOracleMiddleware.sol index c0846a658..047008b59 100644 --- a/src/OracleMiddleware/OracleMiddleware.sol +++ b/src/OracleMiddleware/CommonOracleMiddleware.sol @@ -1,15 +1,15 @@ // SPDX-License-Identifier: BUSL-1.1 -pragma solidity 0.8.26; +pragma solidity ^0.8.0; import { AccessControlDefaultAdminRules } from "@openzeppelin/contracts/access/extensions/AccessControlDefaultAdminRules.sol"; import { IBaseOracleMiddleware } from "../interfaces/OracleMiddleware/IBaseOracleMiddleware.sol"; -import { IOracleMiddleware } from "../interfaces/OracleMiddleware/IOracleMiddleware.sol"; +import { ICommonOracleMiddleware } from "../interfaces/OracleMiddleware/ICommonOracleMiddleware.sol"; import { ChainlinkPriceInfo, - ConfidenceInterval, FormattedPythPrice, + PriceAdjustment, PriceInfo } from "../interfaces/OracleMiddleware/IOracleMiddlewareTypes.sol"; import { IUsdnProtocol } from "../interfaces/UsdnProtocol/IUsdnProtocol.sol"; @@ -18,22 +18,28 @@ import { ChainlinkOracle } from "./oracles/ChainlinkOracle.sol"; import { PythOracle } from "./oracles/PythOracle.sol"; /** - * @title Middleware Between Oracles And The USDN Protocol - * @notice This contract is used to get the price of an asset from different oracles. - * It is used by the USDN protocol to get the price of the USDN underlying asset. + * @title Common Middleware Contract + * @notice This contract serves as a common base that must be implemented by other middleware contracts. */ -contract OracleMiddleware is IOracleMiddleware, PythOracle, ChainlinkOracle, AccessControlDefaultAdminRules { - /// @inheritdoc IOracleMiddleware - uint16 public constant BPS_DIVISOR = 10_000; +abstract contract CommonOracleMiddleware is + ICommonOracleMiddleware, + AccessControlDefaultAdminRules, + ChainlinkOracle, + PythOracle +{ + /* -------------------------------------------------------------------------- */ + /* CONSTANT */ + /* -------------------------------------------------------------------------- */ - /// @inheritdoc IOracleMiddleware - uint16 public constant MAX_CONF_RATIO = BPS_DIVISOR * 2; + /// @inheritdoc ICommonOracleMiddleware + bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE"); /// @notice The number of decimals for the returned price. uint8 internal constant MIDDLEWARE_DECIMALS = 18; - /// @inheritdoc IOracleMiddleware - bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE"); + /* -------------------------------------------------------------------------- */ + /* Params */ + /* -------------------------------------------------------------------------- */ /** * @notice The delay (in seconds) between the moment an action is initiated and the timestamp of the @@ -41,15 +47,16 @@ contract OracleMiddleware is IOracleMiddleware, PythOracle, ChainlinkOracle, Acc */ uint256 internal _validationDelay = 24 seconds; - /// @notice Ratio to applied to the Pyth confidence interval (in basis points). - uint16 internal _confRatioBps = 4000; // to divide by BPS_DIVISOR - /** * @notice The amount of time during which a low latency oracle price validation is available. * @dev This value should be greater than or equal to `_lowLatencyValidatorDeadline` of the USDN protocol. */ uint16 internal _lowLatencyDelay = 20 minutes; + /* -------------------------------------------------------------------------- */ + /* CONSTRUCTOR */ + /* -------------------------------------------------------------------------- */ + /** * @param pythContract Address of the Pyth contract. * @param pythFeedId The Pyth price feed ID for the asset. @@ -65,54 +72,12 @@ contract OracleMiddleware is IOracleMiddleware, PythOracle, ChainlinkOracle, Acc } /* -------------------------------------------------------------------------- */ - /* Public view functions */ + /* External functions */ /* -------------------------------------------------------------------------- */ /// @inheritdoc IBaseOracleMiddleware - function parseAndValidatePrice(bytes32, uint128 targetTimestamp, Types.ProtocolAction action, bytes calldata data) - public - payable - virtual - returns (PriceInfo memory price_) - { - if (action == Types.ProtocolAction.None) { - return - _getLowLatencyPrice(data, targetTimestamp, ConfidenceInterval.None, targetTimestamp + _lowLatencyDelay); - } else if (action == Types.ProtocolAction.Initialize) { - return _getInitiateActionPrice(data, ConfidenceInterval.None); - } else if (action == Types.ProtocolAction.ValidateDeposit) { - // use the lowest price in the confidence interval to ensure a minimum benefit for the user in case - // of price inaccuracies until low latency delay is exceeded then use chainlink specified roundId - return _getValidateActionPrice(data, targetTimestamp, ConfidenceInterval.Down); - } else if (action == Types.ProtocolAction.ValidateWithdrawal) { - // use the highest price in the confidence interval to ensure a minimum benefit for the user in case - // of price inaccuracies until low latency delay is exceeded then use chainlink specified roundId - return _getValidateActionPrice(data, targetTimestamp, ConfidenceInterval.Up); - } else if (action == Types.ProtocolAction.ValidateOpenPosition) { - // use the highest price in the confidence interval to ensure a minimum benefit for the user in case - // of price inaccuracies until low latency delay is exceeded then use chainlink specified roundId - return _getValidateActionPrice(data, targetTimestamp, ConfidenceInterval.Up); - } else if (action == Types.ProtocolAction.ValidateClosePosition) { - // use the lowest price in the confidence interval to ensure a minimum benefit for the user in case - // of price inaccuracies until low latency delay is exceeded then use chainlink specified roundId - return _getValidateActionPrice(data, targetTimestamp, ConfidenceInterval.Down); - } else if (action == Types.ProtocolAction.Liquidation) { - // special case, if we pass a timestamp of zero, then we accept all prices newer than - // `_pythRecentPriceDelay` - return _getLowLatencyPrice(data, 0, ConfidenceInterval.None, 0); - } else if (action == Types.ProtocolAction.InitiateDeposit) { - // if the user chooses to initiate with pyth, the neutral price will be used so no confidence is needed - return _getInitiateActionPrice(data, ConfidenceInterval.None); - } else if (action == Types.ProtocolAction.InitiateWithdrawal) { - // if the user chooses to initiate with pyth, the neutral price will be used so no confidence is needed - return _getInitiateActionPrice(data, ConfidenceInterval.None); - } else if (action == Types.ProtocolAction.InitiateOpenPosition) { - // if the user chooses to initiate with pyth, the neutral price will be used so no confidence is needed - return _getInitiateActionPrice(data, ConfidenceInterval.None); - } else if (action == Types.ProtocolAction.InitiateClosePosition) { - // if the user chooses to initiate with pyth, the neutral price will be used so no confidence is needed - return _getInitiateActionPrice(data, ConfidenceInterval.None); - } + function getDecimals() external pure returns (uint8 decimals_) { + return MIDDLEWARE_DECIMALS; } /// @inheritdoc IBaseOracleMiddleware @@ -120,16 +85,6 @@ contract OracleMiddleware is IOracleMiddleware, PythOracle, ChainlinkOracle, Acc return _validationDelay; } - /// @inheritdoc IBaseOracleMiddleware - function getDecimals() external pure returns (uint8 decimals_) { - return MIDDLEWARE_DECIMALS; - } - - /// @inheritdoc IOracleMiddleware - function getConfRatioBps() external view returns (uint16 ratio_) { - return _confRatioBps; - } - /// @inheritdoc IBaseOracleMiddleware function getLowLatencyDelay() external view returns (uint16 delay_) { return _lowLatencyDelay; @@ -142,136 +97,148 @@ contract OracleMiddleware is IOracleMiddleware, PythOracle, ChainlinkOracle, Acc } } + /// @inheritdoc IBaseOracleMiddleware + function parseAndValidatePrice(bytes32, uint128 targetTimestamp, Types.ProtocolAction action, bytes calldata data) + public + payable + virtual + returns (PriceInfo memory price_) + { + if (action == Types.ProtocolAction.InitiateOpenPosition) { + // if the user chooses to initiate with the low latency oracle, the neutral price will be used + return _getInitiateActionPrice(data, PriceAdjustment.None); + } else if (action == Types.ProtocolAction.ValidateOpenPosition) { + // use the highest price to ensure a minimum benefit for the user in case of price + // inaccuracies until low latency delay is exceeded then use chainlink specified roundId + return _getValidateActionPrice(data, targetTimestamp, PriceAdjustment.Up); + } else if (action == Types.ProtocolAction.InitiateDeposit) { + // if the user chooses to initiate with the low latency oracle, the neutral price will be used + return _getInitiateActionPrice(data, PriceAdjustment.None); + } else if (action == Types.ProtocolAction.ValidateDeposit) { + // use the lowest price to ensure a minimum benefit for the user in case of price + // inaccuracies until low latency delay is exceeded then use chainlink specified roundId + return _getValidateActionPrice(data, targetTimestamp, PriceAdjustment.Down); + } else if (action == Types.ProtocolAction.InitiateClosePosition) { + // if the user chooses to initiate with the low latency oracle, the neutral price will be used + return _getInitiateActionPrice(data, PriceAdjustment.None); + } else if (action == Types.ProtocolAction.ValidateClosePosition) { + // use the lowest price to ensure a minimum benefit for the user in case of price + // inaccuracies until low latency delay is exceeded then use chainlink specified roundId + return _getValidateActionPrice(data, targetTimestamp, PriceAdjustment.Down); + } else if (action == Types.ProtocolAction.InitiateWithdrawal) { + // if the user chooses to initiate with the low latency oracle, the neutral price will be used + return _getInitiateActionPrice(data, PriceAdjustment.None); + } else if (action == Types.ProtocolAction.ValidateWithdrawal) { + // use the highest price to ensure a minimum benefit for the user in case of price + // inaccuracies until low latency delay is exceeded then use chainlink specified roundId + return _getValidateActionPrice(data, targetTimestamp, PriceAdjustment.Up); + } else if (action == Types.ProtocolAction.Liquidation) { + // use the neutral price from the low-latency oracle + return _getLiquidationPrice(data); + } else if (action == Types.ProtocolAction.Initialize) { + return _getInitiateActionPrice(data, PriceAdjustment.None); + } else if (action == Types.ProtocolAction.None) { + return _getLowLatencyPrice(data, targetTimestamp, PriceAdjustment.None, targetTimestamp + _lowLatencyDelay); + } + } + /* -------------------------------------------------------------------------- */ - /* Internal functions */ + /* Privileged functions */ /* -------------------------------------------------------------------------- */ - /** - * @notice Gets the price from the low-latency oracle (Pyth). - * @param data The signed price update data. - * @param actionTimestamp The timestamp of the action corresponding to the price. If zero, then we must accept all - * prices younger than {PythOracle._pythRecentPriceDelay}. - * @param dir The direction for the confidence interval adjusted price. - * @param targetLimit The most recent timestamp a price can have (can be zero if `actionTimestamp` is zero). - * @return price_ The price from the low-latency oracle, adjusted according to the confidence interval direction. - */ - function _getLowLatencyPrice( - bytes calldata data, - uint128 actionTimestamp, - ConfidenceInterval dir, - uint128 targetLimit - ) internal virtual returns (PriceInfo memory price_) { - // if actionTimestamp is 0 we're performing a liquidation and we don't add the validation delay - if (actionTimestamp > 0) { - // add the validation delay to the action timestamp to get the timestamp of the price data used to - // validate - actionTimestamp += uint128(_validationDelay); - } + /// @inheritdoc ICommonOracleMiddleware + function setValidationDelay(uint256 newValidationDelay) external onlyRole(ADMIN_ROLE) { + _validationDelay = newValidationDelay; - FormattedPythPrice memory pythPrice = - _getFormattedPythPrice(data, actionTimestamp, MIDDLEWARE_DECIMALS, targetLimit); - price_ = _adjustPythPrice(pythPrice, dir); + emit ValidationDelayUpdated(newValidationDelay); } - /** - * @notice Gets the price for an `initiate` action of the protocol. - * @dev If the data parameter is not empty, validate the price with {PythOracle}. Else, get the on-chain price from - * {ChainlinkOracle} and compare its timestamp with the latest seen Pyth price (cached). If Pyth is more recent, we - * return it. Otherwise, we return the Chainlink price. For the latter, we don't have a confidence interval, so both - * `neutralPrice` and `price` are equal. - * @param data An optional VAA from Pyth. - * @param dir The direction when applying the confidence interval (when using a Pyth price). - * @return price_ The price to use for the user action. - */ - function _getInitiateActionPrice(bytes calldata data, ConfidenceInterval dir) - internal - returns (PriceInfo memory price_) - { - // if data is not empty, use pyth - if (data.length > 0) { - // since we use this function for `initiate` type actions which pass `targetTimestamp = block.timestamp`, - // we should pass `0` to the function below to signal that we accept any recent price - return _getLowLatencyPrice(data, 0, dir, 0); - } + /// @inheritdoc ICommonOracleMiddleware + function setChainlinkTimeElapsedLimit(uint256 newTimeElapsedLimit) external onlyRole(ADMIN_ROLE) { + _timeElapsedLimit = newTimeElapsedLimit; - // chainlink calls do not require a fee - if (msg.value > 0) { - revert OracleMiddlewareIncorrectFee(); + emit TimeElapsedLimitUpdated(newTimeElapsedLimit); + } + + /// @inheritdoc ICommonOracleMiddleware + function setPythRecentPriceDelay(uint64 newDelay) external onlyRole(ADMIN_ROLE) { + if (newDelay < 10 seconds) { + revert OracleMiddlewareInvalidRecentPriceDelay(newDelay); + } + if (newDelay > 10 minutes) { + revert OracleMiddlewareInvalidRecentPriceDelay(newDelay); } + _pythRecentPriceDelay = newDelay; - ChainlinkPriceInfo memory chainlinkOnChainPrice = _getFormattedChainlinkLatestPrice(MIDDLEWARE_DECIMALS); + emit PythRecentPriceDelayUpdated(newDelay); + } - // check if the cached pyth price is more recent and return it instead - FormattedPythPrice memory latestPythPrice = _getLatestStoredPythPrice(MIDDLEWARE_DECIMALS); - if (chainlinkOnChainPrice.timestamp <= latestPythPrice.publishTime) { - // we use the same price age limit as for chainlink here - if (latestPythPrice.publishTime < block.timestamp - _timeElapsedLimit) { - revert OracleMiddlewarePriceTooOld(latestPythPrice.publishTime); - } - return _adjustPythPrice(latestPythPrice, dir); + /// @inheritdoc ICommonOracleMiddleware + function setLowLatencyDelay(uint16 newLowLatencyDelay, IUsdnProtocol usdnProtocol) external onlyRole(ADMIN_ROLE) { + if (newLowLatencyDelay > 90 minutes) { + revert OracleMiddlewareInvalidLowLatencyDelay(); } - - // if the price equals PRICE_TOO_OLD then the tolerated time elapsed for price validity was exceeded, revert - if (chainlinkOnChainPrice.price == PRICE_TOO_OLD) { - revert OracleMiddlewarePriceTooOld(chainlinkOnChainPrice.timestamp); + if (newLowLatencyDelay < usdnProtocol.getLowLatencyValidatorDeadline()) { + revert OracleMiddlewareInvalidLowLatencyDelay(); } + _lowLatencyDelay = newLowLatencyDelay; - // if the price is negative or zero, revert - if (chainlinkOnChainPrice.price <= 0) { - revert OracleMiddlewareWrongPrice(chainlinkOnChainPrice.price); - } + emit LowLatencyDelayUpdated(newLowLatencyDelay); + } - price_ = PriceInfo({ - price: uint256(chainlinkOnChainPrice.price), - neutralPrice: uint256(chainlinkOnChainPrice.price), - timestamp: chainlinkOnChainPrice.timestamp - }); + /// @inheritdoc ICommonOracleMiddleware + function withdrawEther(address to) external onlyRole(ADMIN_ROLE) { + if (to == address(0)) { + revert OracleMiddlewareTransferToZeroAddress(); + } + (bool success,) = payable(to).call{ value: address(this).balance }(""); + if (!success) { + revert OracleMiddlewareTransferFailed(to); + } } + /* -------------------------------------------------------------------------- */ + /* Internal functions */ + /* -------------------------------------------------------------------------- */ + /** - * @notice Applies the confidence interval in the `dir` direction, scaled by the configured {_confRatioBps}. - * @param pythPrice The formatted Pyth price object. - * @param dir The direction of the confidence interval to apply. - * @return price_ The adjusted price according to the confidence interval and confidence ratio. + * @notice Gets the price from the low-latency oracle. + * @param data The signed price update data. + * @param actionTimestamp The timestamp of the action corresponding to the price. If zero, then we must accept all + * prices younger than the recent price delay. + * @param dir The direction for the low latency price adjustment. + * @param targetLimit The most recent timestamp a price can have (can be zero if `actionTimestamp` is zero). + * @return price_ The price from the low-latency oracle, adjusted according to the price adjustment direction. */ - function _adjustPythPrice(FormattedPythPrice memory pythPrice, ConfidenceInterval dir) + function _getLowLatencyPrice(bytes calldata data, uint128 actionTimestamp, PriceAdjustment dir, uint128 targetLimit) internal - view - returns (PriceInfo memory price_) - { - if (dir == ConfidenceInterval.Down) { - uint256 adjust = (pythPrice.conf * _confRatioBps) / BPS_DIVISOR; - if (adjust >= pythPrice.price) { - // avoid underflow or zero price due to confidence interval adjustment - price_.price = 1; - } else { - // strictly positive - unchecked { - price_.price = pythPrice.price - adjust; - } - } - } else if (dir == ConfidenceInterval.Up) { - price_.price = pythPrice.price + ((pythPrice.conf * _confRatioBps) / BPS_DIVISOR); - } else { - price_.price = pythPrice.price; - } + virtual + returns (PriceInfo memory price_); - price_.timestamp = pythPrice.publishTime; - price_.neutralPrice = pythPrice.price; - } + /** + * @notice Gets the price for an `initiate` action of the protocol. + * @param data The low latency data. + * @param dir The direction to adjust the price (when using a low latency price). + * @return price_ The price to use for the user action. + */ + function _getInitiateActionPrice(bytes calldata data, PriceAdjustment dir) + internal + virtual + returns (PriceInfo memory price_); /** * @notice Gets the price for a validate action of the protocol. * @dev If the low latency delay is not exceeded, validate the price with the low-latency oracle(s). * Else, get the specified roundId on-chain price from Chainlink. In case of chainlink price, - * we don't have a confidence interval and so both `neutralPrice` and `price` are equal. - * @param data An optional VAA from Pyth or a chainlink roundId (abi-encoded uint80). + * we don't have a price adjustment, so both `neutralPrice` and `price` are equal. + * @param data An optional low-latency price update or a chainlink roundId (abi-encoded uint80). * @param targetTimestamp The timestamp of the initiate action. - * @param dir The direction for applying the confidence interval (in case we use a Pyth price). + * @param dir The direction to adjust the price (when using a low latency price). * @return price_ The price to use for the user action. */ - function _getValidateActionPrice(bytes calldata data, uint128 targetTimestamp, ConfidenceInterval dir) + function _getValidateActionPrice(bytes calldata data, uint128 targetTimestamp, PriceAdjustment dir) internal + virtual returns (PriceInfo memory price_) { uint128 targetLimit = targetTimestamp + _lowLatencyDelay; @@ -296,6 +263,15 @@ contract OracleMiddleware is IOracleMiddleware, PythOracle, ChainlinkOracle, Acc }); } + /** + * @notice Gets the price from the low-latency oracle. + * @param data The signed price update data. + * @return price_ The low-latency oracle price. + */ + function _getLiquidationPrice(bytes calldata data) internal virtual returns (PriceInfo memory price_) { + return _getLowLatencyPrice(data, 0, PriceAdjustment.None, 0); + } + /** * @notice Checks that the given round ID is valid and returns its corresponding price data. * @dev Round IDs are not necessarily consecutive, so additional computing can be necessary to find @@ -361,71 +337,4 @@ contract OracleMiddleware is IOracleMiddleware, PythOracle, ChainlinkOracle, Acc // Pyth magic stands for PNAU (Pyth Network Accumulator Update) return magic == 0x504e4155; } - - /* -------------------------------------------------------------------------- */ - /* Privileged functions */ - /* -------------------------------------------------------------------------- */ - - /// @inheritdoc IOracleMiddleware - function setValidationDelay(uint256 newValidationDelay) external onlyRole(ADMIN_ROLE) { - _validationDelay = newValidationDelay; - - emit ValidationDelayUpdated(newValidationDelay); - } - - /// @inheritdoc IOracleMiddleware - function setChainlinkTimeElapsedLimit(uint256 newTimeElapsedLimit) external onlyRole(ADMIN_ROLE) { - _timeElapsedLimit = newTimeElapsedLimit; - - emit TimeElapsedLimitUpdated(newTimeElapsedLimit); - } - - /// @inheritdoc IOracleMiddleware - function setPythRecentPriceDelay(uint64 newDelay) external onlyRole(ADMIN_ROLE) { - if (newDelay < 10 seconds) { - revert OracleMiddlewareInvalidRecentPriceDelay(newDelay); - } - if (newDelay > 10 minutes) { - revert OracleMiddlewareInvalidRecentPriceDelay(newDelay); - } - _pythRecentPriceDelay = newDelay; - - emit PythRecentPriceDelayUpdated(newDelay); - } - - /// @inheritdoc IOracleMiddleware - function setConfRatio(uint16 newConfRatio) external onlyRole(ADMIN_ROLE) { - // confidence ratio limit check - if (newConfRatio > MAX_CONF_RATIO) { - revert OracleMiddlewareConfRatioTooHigh(); - } - - _confRatioBps = newConfRatio; - - emit ConfRatioUpdated(newConfRatio); - } - - /// @inheritdoc IOracleMiddleware - function setLowLatencyDelay(uint16 newLowLatencyDelay, IUsdnProtocol usdnProtocol) external onlyRole(ADMIN_ROLE) { - if (newLowLatencyDelay > 90 minutes) { - revert OracleMiddlewareInvalidLowLatencyDelay(); - } - if (newLowLatencyDelay < usdnProtocol.getLowLatencyValidatorDeadline()) { - revert OracleMiddlewareInvalidLowLatencyDelay(); - } - _lowLatencyDelay = newLowLatencyDelay; - - emit LowLatencyDelayUpdated(newLowLatencyDelay); - } - - /// @inheritdoc IOracleMiddleware - function withdrawEther(address to) external onlyRole(ADMIN_ROLE) { - if (to == address(0)) { - revert OracleMiddlewareTransferToZeroAddress(); - } - (bool success,) = payable(to).call{ value: address(this).balance }(""); - if (!success) { - revert OracleMiddlewareTransferFailed(to); - } - } } diff --git a/src/OracleMiddleware/OracleMiddlewareWithDataStreams.sol b/src/OracleMiddleware/OracleMiddlewareWithDataStreams.sol new file mode 100644 index 000000000..070a479be --- /dev/null +++ b/src/OracleMiddleware/OracleMiddlewareWithDataStreams.sol @@ -0,0 +1,215 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.26; + +import { AccessControlDefaultAdminRules } from + "@openzeppelin/contracts/access/extensions/AccessControlDefaultAdminRules.sol"; + +import { IBaseOracleMiddleware } from "../interfaces/OracleMiddleware/IBaseOracleMiddleware.sol"; +import { + ChainlinkPriceInfo, + FormattedDataStreamsPrice, + FormattedPythPrice, + PriceAdjustment, + PriceInfo +} from "../interfaces/OracleMiddleware/IOracleMiddlewareTypes.sol"; +import { IOracleMiddlewareWithDataStreams } from "../interfaces/OracleMiddleware/IOracleMiddlewareWithDataStreams.sol"; +import { IVerifierProxy } from "../interfaces/OracleMiddleware/IVerifierProxy.sol"; +import { IUsdnProtocol } from "../interfaces/UsdnProtocol/IUsdnProtocol.sol"; +import { IUsdnProtocolTypes as Types } from "../interfaces/UsdnProtocol/IUsdnProtocolTypes.sol"; +import { CommonOracleMiddleware } from "./CommonOracleMiddleware.sol"; +import { ChainlinkDataStreamsOracle } from "./oracles/ChainlinkDataStreamsOracle.sol"; + +/** + * @title Middleware Between Oracles And The USDN Protocol + * @notice This contract is used to get the price of an asset from different oracles. + * It is used by the USDN protocol to get the price of the USDN underlying asset. + */ +contract OracleMiddlewareWithDataStreams is + CommonOracleMiddleware, + ChainlinkDataStreamsOracle, + IOracleMiddlewareWithDataStreams +{ + /** + * @param pythContract Address of the Pyth contract. + * @param pythFeedId The Pyth price feed ID for the asset. + * @param chainlinkPriceFeed The address of the Chainlink price feed. + * @param chainlinkTimeElapsedLimit The duration after which a Chainlink price is considered stale. + * @param chainlinkProxyVerifierAddress The address of the Chainlink proxy verifier contract. + * @param chainlinkStreamId The supported Chainlink data stream ID. + */ + constructor( + address pythContract, + bytes32 pythFeedId, + address chainlinkPriceFeed, + uint256 chainlinkTimeElapsedLimit, + address chainlinkProxyVerifierAddress, + bytes32 chainlinkStreamId + ) + CommonOracleMiddleware(pythContract, pythFeedId, chainlinkPriceFeed, chainlinkTimeElapsedLimit) + ChainlinkDataStreamsOracle(chainlinkProxyVerifierAddress, chainlinkStreamId) + { } + + /* -------------------------------------------------------------------------- */ + /* Public view functions */ + /* -------------------------------------------------------------------------- */ + + /// @inheritdoc IBaseOracleMiddleware + function validationCost(bytes calldata data, Types.ProtocolAction) + public + view + override(CommonOracleMiddleware, IBaseOracleMiddleware) + returns (uint256 result_) + { + if (data.length <= 32) { + // If the input data is empty, retrieve the last round data from Chainlink data feeds. + // If the input data is 32 bytes long, decode it to fetch specific round data from Chainlink + // using `uint80 roundId = abi.decode(data, (uint80))`. + return 0; + } else if (_isPythData(data)) { + // If the data is from Pyth, we assume the data length will be greater than 32, + // and use it only for liquidation purposes. + return _getPythUpdateFee(data); + } else { + // For any other data, we assume it is a Chainlink data stream payload, + // which should also have a length greater than 32, and return the associated fee amount. + return _getChainlinkDataStreamFeeData(data).amount; + } + } + + /* -------------------------------------------------------------------------- */ + /* Privileged functions */ + /* -------------------------------------------------------------------------- */ + + /// @inheritdoc IOracleMiddlewareWithDataStreams + function setDataStreamsRecentPriceDelay(uint64 newDelay) external onlyRole(ADMIN_ROLE) { + if (newDelay < 10 seconds) { + revert OracleMiddlewareInvalidRecentPriceDelay(newDelay); + } + if (newDelay > 10 minutes) { + revert OracleMiddlewareInvalidRecentPriceDelay(newDelay); + } + _dataStreamsRecentPriceDelay = newDelay; + + emit DataStreamsRecentPriceDelayUpdated(newDelay); + } + + /* -------------------------------------------------------------------------- */ + /* Internal functions */ + /* -------------------------------------------------------------------------- */ + + /// @inheritdoc CommonOracleMiddleware + function _getLowLatencyPrice( + bytes calldata payload, + uint128 actionTimestamp, + PriceAdjustment dir, + uint128 targetLimit + ) internal virtual override returns (PriceInfo memory price_) { + // if actionTimestamp is 0 we're performing a liquidation or a initiate + // action and we don't add the validation delay + if (actionTimestamp > 0) { + // add the validation delay to the action timestamp to get + // the timestamp of the price data used to validate + actionTimestamp += uint128(_validationDelay); + } + + FormattedDataStreamsPrice memory formattedPrice = + _getChainlinkDataStreamPrice(payload, actionTimestamp, targetLimit); + price_ = _adjustDataStreamPrice(formattedPrice, dir); + } + + /** + * @inheritdoc CommonOracleMiddleware + * @dev If the data parameter is not empty, validate the price with the low latency oracle. Else, get the on-chain + * price from {ChainlinkOracle} and compare its timestamp with the latest seen Pyth price (cached). If Pyth is more + * recent, we return it. Otherwise, we return the Chainlink price. For the latter, we don't have a price adjustment, + * so both `neutralPrice` and `price` are equal. + */ + function _getInitiateActionPrice(bytes calldata data, PriceAdjustment dir) + internal + override + returns (PriceInfo memory price_) + { + // if data is not empty, use Chainlink data streams + if (data.length > 0) { + // since we use this function for `initiate` type actions which pass `targetTimestamp = block.timestamp`, + // we should pass `0` to the function below to signal that we accept any recent price + return _getLowLatencyPrice(data, 0, dir, 0); + } + + // Chainlink calls do not require a fee + if (msg.value > 0) { + revert OracleMiddlewareIncorrectFee(); + } + + ChainlinkPriceInfo memory chainlinkOnChainPrice = _getFormattedChainlinkLatestPrice(MIDDLEWARE_DECIMALS); + + // check if the cached pyth price is more recent and return it instead + FormattedPythPrice memory latestPythPrice = _getLatestStoredPythPrice(MIDDLEWARE_DECIMALS); + if (chainlinkOnChainPrice.timestamp <= latestPythPrice.publishTime) { + // we use the same price age limit as for Chainlink here + if (latestPythPrice.publishTime < block.timestamp - _timeElapsedLimit) { + revert OracleMiddlewarePriceTooOld(latestPythPrice.publishTime); + } + return _convertPythPrice(latestPythPrice); + } + + // if the price equals PRICE_TOO_OLD then the tolerated time elapsed for price validity was exceeded, revert + if (chainlinkOnChainPrice.price == PRICE_TOO_OLD) { + revert OracleMiddlewarePriceTooOld(chainlinkOnChainPrice.timestamp); + } + + // if the price is negative or zero, revert + if (chainlinkOnChainPrice.price <= 0) { + revert OracleMiddlewareWrongPrice(chainlinkOnChainPrice.price); + } + + price_ = PriceInfo({ + price: uint256(chainlinkOnChainPrice.price), + neutralPrice: uint256(chainlinkOnChainPrice.price), + timestamp: chainlinkOnChainPrice.timestamp + }); + } + + /// @inheritdoc CommonOracleMiddleware + function _getLiquidationPrice(bytes calldata data) internal virtual override returns (PriceInfo memory price_) { + if (_isPythData(data)) { + FormattedPythPrice memory pythPrice = _getFormattedPythPrice(data, 0, MIDDLEWARE_DECIMALS, 0); + return _convertPythPrice(pythPrice); + } + + FormattedDataStreamsPrice memory formattedPrice = _getChainlinkDataStreamPrice(data, 0, 0); + price_ = _adjustDataStreamPrice(formattedPrice, PriceAdjustment.None); + } + + /** + * @notice Converts a formatted Pyth price into a PriceInfo. + * @param pythPrice The formatted Pyth price containing the price and publish time. + * @return price_ The PriceInfo with the price, neutral price, and timestamp set from the Pyth price data. + */ + function _convertPythPrice(FormattedPythPrice memory pythPrice) internal pure returns (PriceInfo memory price_) { + price_ = PriceInfo({ price: pythPrice.price, neutralPrice: pythPrice.price, timestamp: pythPrice.publishTime }); + } + + /** + * @notice Applies the ask, bid or price according to the `dir` direction. + * @param formattedPrice The Chainlink data streams formatted price. + * @param dir The direction to adjust the price. + * @return price_ The adjusted price according to the direction. + */ + function _adjustDataStreamPrice(FormattedDataStreamsPrice memory formattedPrice, PriceAdjustment dir) + internal + pure + returns (PriceInfo memory price_) + { + if (dir == PriceAdjustment.Down) { + price_.price = formattedPrice.bid; + } else if (dir == PriceAdjustment.Up) { + price_.price = formattedPrice.ask; + } else { + price_.price = formattedPrice.price; + } + + price_.timestamp = formattedPrice.timestamp; + price_.neutralPrice = formattedPrice.price; + } +} diff --git a/src/OracleMiddleware/OracleMiddlewareWithPyth.sol b/src/OracleMiddleware/OracleMiddlewareWithPyth.sol new file mode 100644 index 000000000..f77a66233 --- /dev/null +++ b/src/OracleMiddleware/OracleMiddlewareWithPyth.sol @@ -0,0 +1,170 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.26; + +import { + ChainlinkPriceInfo, + FormattedPythPrice, + PriceAdjustment, + PriceInfo +} from "../interfaces/OracleMiddleware/IOracleMiddlewareTypes.sol"; +import { IOracleMiddlewareWithPyth } from "../interfaces/OracleMiddleware/IOracleMiddlewareWithPyth.sol"; +import { CommonOracleMiddleware } from "./CommonOracleMiddleware.sol"; + +/** + * @title Middleware Between Oracles And The USDN Protocol + * @notice This contract is used to get the price of an asset from different oracles. + * It is used by the USDN protocol to get the price of the USDN underlying asset. + */ +contract OracleMiddlewareWithPyth is CommonOracleMiddleware, IOracleMiddlewareWithPyth { + /// @inheritdoc IOracleMiddlewareWithPyth + uint16 public constant BPS_DIVISOR = 10_000; + + /// @inheritdoc IOracleMiddlewareWithPyth + uint16 public constant MAX_CONF_RATIO = BPS_DIVISOR * 2; + + /// @notice Ratio to applied to the Pyth confidence interval (in basis points). + uint16 internal _confRatioBps = 4000; // to divide by BPS_DIVISOR + + /** + * @param pythContract Address of the Pyth contract. + * @param pythFeedId The Pyth price feed ID for the asset. + * @param chainlinkPriceFeed Address of the Chainlink price feed. + * @param chainlinkTimeElapsedLimit The duration after which a Chainlink price is considered stale. + */ + constructor(address pythContract, bytes32 pythFeedId, address chainlinkPriceFeed, uint256 chainlinkTimeElapsedLimit) + CommonOracleMiddleware(pythContract, pythFeedId, chainlinkPriceFeed, chainlinkTimeElapsedLimit) + { } + + /* -------------------------------------------------------------------------- */ + /* Public view functions */ + /* -------------------------------------------------------------------------- */ + + /// @inheritdoc IOracleMiddlewareWithPyth + function getConfRatioBps() external view returns (uint16 ratio_) { + return _confRatioBps; + } + + /* -------------------------------------------------------------------------- */ + /* Privileged functions */ + /* -------------------------------------------------------------------------- */ + + /// @inheritdoc IOracleMiddlewareWithPyth + function setConfRatio(uint16 newConfRatio) external onlyRole(ADMIN_ROLE) { + // confidence ratio limit check + if (newConfRatio > MAX_CONF_RATIO) { + revert OracleMiddlewareConfRatioTooHigh(); + } + + _confRatioBps = newConfRatio; + + emit ConfRatioUpdated(newConfRatio); + } + + /* -------------------------------------------------------------------------- */ + /* Internal functions */ + /* -------------------------------------------------------------------------- */ + + /// @inheritdoc CommonOracleMiddleware + function _getLowLatencyPrice(bytes calldata data, uint128 actionTimestamp, PriceAdjustment dir, uint128 targetLimit) + internal + virtual + override + returns (PriceInfo memory price_) + { + // if actionTimestamp is 0 we're performing a liquidation and we don't add the validation delay + if (actionTimestamp > 0) { + // add the validation delay to the action timestamp to get the timestamp of the price data used to + // validate + actionTimestamp += uint128(_validationDelay); + } + + FormattedPythPrice memory pythPrice = + _getFormattedPythPrice(data, actionTimestamp, MIDDLEWARE_DECIMALS, targetLimit); + price_ = _adjustPythPrice(pythPrice, dir); + } + + /** + * @inheritdoc CommonOracleMiddleware + * @dev If the data parameter is not empty, validate the price with the low latency oracle. Else, get the on-chain + * price from {ChainlinkOracle} and compare its timestamp with the latest seen Pyth price (cached). If Pyth is more + * recent, we return it. Otherwise, we return the Chainlink price. For the latter, we don't have a price adjustment, + * so both `neutralPrice` and `price` are equal. + */ + function _getInitiateActionPrice(bytes calldata data, PriceAdjustment dir) + internal + override + returns (PriceInfo memory price_) + { + // if data is not empty, use the low latency oracle + if (data.length > 0) { + // since we use this function for `initiate` type actions which pass `targetTimestamp = block.timestamp`, + // we should pass `0` to the function below to signal that we accept any recent price + return _getLowLatencyPrice(data, 0, dir, 0); + } + + // chainlink calls do not require a fee + if (msg.value > 0) { + revert OracleMiddlewareIncorrectFee(); + } + + ChainlinkPriceInfo memory chainlinkOnChainPrice = _getFormattedChainlinkLatestPrice(MIDDLEWARE_DECIMALS); + + // check if the cached pyth price is more recent and return it instead + FormattedPythPrice memory latestPythPrice = _getLatestStoredPythPrice(MIDDLEWARE_DECIMALS); + if (chainlinkOnChainPrice.timestamp <= latestPythPrice.publishTime) { + // we use the same price age limit as for chainlink here + if (latestPythPrice.publishTime < block.timestamp - _timeElapsedLimit) { + revert OracleMiddlewarePriceTooOld(latestPythPrice.publishTime); + } + return _adjustPythPrice(latestPythPrice, dir); + } + + // if the price equals PRICE_TOO_OLD then the tolerated time elapsed for price validity was exceeded, revert + if (chainlinkOnChainPrice.price == PRICE_TOO_OLD) { + revert OracleMiddlewarePriceTooOld(chainlinkOnChainPrice.timestamp); + } + + // if the price is negative or zero, revert + if (chainlinkOnChainPrice.price <= 0) { + revert OracleMiddlewareWrongPrice(chainlinkOnChainPrice.price); + } + + price_ = PriceInfo({ + price: uint256(chainlinkOnChainPrice.price), + neutralPrice: uint256(chainlinkOnChainPrice.price), + timestamp: chainlinkOnChainPrice.timestamp + }); + } + + /** + * @notice Applies the confidence interval in the `dir` direction, scaled by the configured {_confRatioBps}. + * @param pythPrice The formatted Pyth price object. + * @param dir The direction of the confidence interval to apply. + * @return price_ The adjusted price according to the confidence interval and confidence ratio. + */ + function _adjustPythPrice(FormattedPythPrice memory pythPrice, PriceAdjustment dir) + internal + view + returns (PriceInfo memory price_) + { + uint256 adjust = (pythPrice.conf * _confRatioBps) / BPS_DIVISOR; + + if (adjust >= pythPrice.price) { + revert OracleMiddlewareConfValueTooHigh(); + } + + if (dir == PriceAdjustment.Down) { + unchecked { + // adjust is always less than pythPrice.price so we can safely subtract it + price_.price = pythPrice.price - adjust; + } + } else if (dir == PriceAdjustment.Up) { + price_.price = pythPrice.price + adjust; + } else { + price_.price = pythPrice.price; + } + + price_.timestamp = pythPrice.publishTime; + price_.neutralPrice = pythPrice.price; + } +} diff --git a/src/OracleMiddleware/OracleMiddlewareWithRedstone.sol b/src/OracleMiddleware/OracleMiddlewareWithRedstone.sol index 2d65fc2b2..bb3cce737 100644 --- a/src/OracleMiddleware/OracleMiddlewareWithRedstone.sol +++ b/src/OracleMiddleware/OracleMiddlewareWithRedstone.sol @@ -3,13 +3,13 @@ pragma solidity 0.8.26; import { ChainlinkPriceInfo, - ConfidenceInterval, FormattedPythPrice, + PriceAdjustment, PriceInfo, RedstonePriceInfo } from "../interfaces/OracleMiddleware/IOracleMiddlewareTypes.sol"; import { IOracleMiddlewareWithRedstone } from "../interfaces/OracleMiddleware/IOracleMiddlewareWithRedstone.sol"; -import { OracleMiddleware } from "./OracleMiddleware.sol"; +import { OracleMiddlewareWithPyth } from "./OracleMiddlewareWithPyth.sol"; import { RedstoneOracle } from "./oracles/RedstoneOracle.sol"; /** @@ -19,7 +19,7 @@ import { RedstoneOracle } from "./oracles/RedstoneOracle.sol"; * @dev This contract allows users to use the Redstone oracle and exists in case the Pyth infrastructure fails and we * need a temporary solution. Redstone and Pyth are used concurrently, which could introduce arbitrage opportunities. */ -contract OracleMiddlewareWithRedstone is IOracleMiddlewareWithRedstone, OracleMiddleware, RedstoneOracle { +contract OracleMiddlewareWithRedstone is IOracleMiddlewareWithRedstone, OracleMiddlewareWithPyth, RedstoneOracle { /// @notice The penalty for using a non-Pyth price with low latency oracle (in basis points). uint16 internal _penaltyBps = 25; // 0.25% @@ -37,7 +37,7 @@ contract OracleMiddlewareWithRedstone is IOracleMiddlewareWithRedstone, OracleMi address chainlinkPriceFeed, uint256 chainlinkTimeElapsedLimit ) - OracleMiddleware(pythContract, pythFeedId, chainlinkPriceFeed, chainlinkTimeElapsedLimit) + OracleMiddlewareWithPyth(pythContract, pythFeedId, chainlinkPriceFeed, chainlinkTimeElapsedLimit) RedstoneOracle(redstoneFeedId) { } @@ -55,17 +55,16 @@ contract OracleMiddlewareWithRedstone is IOracleMiddlewareWithRedstone, OracleMi /* -------------------------------------------------------------------------- */ /** - * @inheritdoc OracleMiddleware + * @inheritdoc OracleMiddlewareWithPyth * @notice Gets the price from the low-latency oracle (Pyth or Redstone). * @param actionTimestamp The timestamp of the action corresponding to the price. If zero, then we must accept all * prices younger than {PythOracle._pythRecentPriceDelay} or {RedstoneOracle._redstoneRecentPriceDelay}. */ - function _getLowLatencyPrice( - bytes calldata data, - uint128 actionTimestamp, - ConfidenceInterval dir, - uint128 targetLimit - ) internal override returns (PriceInfo memory price_) { + function _getLowLatencyPrice(bytes calldata data, uint128 actionTimestamp, PriceAdjustment dir, uint128 targetLimit) + internal + override + returns (PriceInfo memory price_) + { // if actionTimestamp is 0 we're performing a liquidation and we don't add the validation delay if (actionTimestamp > 0) { // add the validation delay to the action timestamp to get the timestamp of the price data used to @@ -103,14 +102,14 @@ contract OracleMiddlewareWithRedstone is IOracleMiddlewareWithRedstone, OracleMi * @param dir The direction to apply the penalty. * @return price_ The adjusted price according to the penalty. */ - function _adjustRedstonePrice(RedstonePriceInfo memory redstonePrice, ConfidenceInterval dir) + function _adjustRedstonePrice(RedstonePriceInfo memory redstonePrice, PriceAdjustment dir) internal view returns (PriceInfo memory price_) { - if (dir == ConfidenceInterval.Down) { + if (dir == PriceAdjustment.Down) { price_.price = redstonePrice.price - (redstonePrice.price * _penaltyBps / BPS_DIVISOR); - } else if (dir == ConfidenceInterval.Up) { + } else if (dir == PriceAdjustment.Up) { price_.price = redstonePrice.price + (redstonePrice.price * _penaltyBps / BPS_DIVISOR); } else { price_.price = redstonePrice.price; diff --git a/src/OracleMiddleware/WstEthOracleMiddlewareWithDataStreams.sol b/src/OracleMiddleware/WstEthOracleMiddlewareWithDataStreams.sol new file mode 100644 index 000000000..58880edba --- /dev/null +++ b/src/OracleMiddleware/WstEthOracleMiddlewareWithDataStreams.sol @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.26; + +import { IWstETH } from "../interfaces/IWstETH.sol"; +import { IBaseOracleMiddleware } from "../interfaces/OracleMiddleware/IBaseOracleMiddleware.sol"; +import { PriceInfo } from "../interfaces/OracleMiddleware/IOracleMiddlewareTypes.sol"; +import { IUsdnProtocolTypes as Types } from "../interfaces/UsdnProtocol/IUsdnProtocolTypes.sol"; +import { CommonOracleMiddleware } from "./CommonOracleMiddleware.sol"; +import { OracleMiddlewareWithDataStreams } from "./OracleMiddlewareWithDataStreams.sol"; + +/** + * @title Middleware Implementation For WstETH Price With Chainlink Data Streams + * @notice This contract is used to get the price of wstETH from the ETH price or the wstETH price directly. + */ +contract WstEthOracleMiddlewareWithDataStreams is OracleMiddlewareWithDataStreams { + /// @notice The wstETH contract. + IWstETH internal immutable _wstETH; + + /** + * @param pythContract The address of the Pyth contract. + * @param pythFeedId The ETH/USD Pyth price feed ID for the asset. + * @param chainlinkPriceFeed The address of the Chainlink ETH/USD price feed. + * @param wstETH The address of the wstETH contract. + * @param chainlinkTimeElapsedLimit The duration after which a Chainlink price is considered stale. + * @param chainlinkProxyVerifierAddress The address of the Chainlink proxy verifier contract. + * @param chainlinkStreamId The supported Chainlink wstETH/USD data stream ID. + */ + constructor( + address pythContract, + bytes32 pythFeedId, + address chainlinkPriceFeed, + address wstETH, + uint256 chainlinkTimeElapsedLimit, + address chainlinkProxyVerifierAddress, + bytes32 chainlinkStreamId + ) + OracleMiddlewareWithDataStreams( + pythContract, + pythFeedId, + chainlinkPriceFeed, + chainlinkTimeElapsedLimit, + chainlinkProxyVerifierAddress, + chainlinkStreamId + ) + { + _wstETH = IWstETH(wstETH); + } + + /** + * @inheritdoc IBaseOracleMiddleware + * @notice Parses and validates `data`, returns the corresponding price data, + * applying ETH/wstETH ratio if the price is in ETH. + * @dev The data format is specific to the middleware and is simply forwarded from the user transaction's calldata. + * If needed, the wstETH price is calculated as follows: `ethPrice x stEthPerToken / 1 ether`. + * A fee amounting to exactly {validationCost} (with the same `data` and `action`) must be sent or the transaction + * will revert. + * @param actionId A unique identifier for the current action. This identifier can be used to link an `Initiate` + * call with the corresponding `Validate` call. + * @param targetTimestamp The target timestamp for validating the price data. For validation actions, this is the + * timestamp of the initiation. + * @param action Type of action for which the price is requested. The middleware may use this to alter the + * validation of the price or the returned price. + * @param data The data to be used to communicate with oracles, the format varies from middleware to middleware and + * can be different depending on the action. + * @return result_ The price and timestamp as {IOracleMiddlewareTypes.PriceInfo}. + */ + function parseAndValidatePrice( + bytes32 actionId, + uint128 targetTimestamp, + Types.ProtocolAction action, + bytes calldata data + ) public payable virtual override(IBaseOracleMiddleware, CommonOracleMiddleware) returns (PriceInfo memory) { + PriceInfo memory oraclePrice = super.parseAndValidatePrice(actionId, targetTimestamp, action, data); + + if (data.length > 32 && !_isPythData(data)) { + // the price is already in wstETH/USD from the Chainlink data stream + return oraclePrice; + } + + uint256 stEthPerToken = _wstETH.stEthPerToken(); + + return PriceInfo({ + price: oraclePrice.price * stEthPerToken / 1 ether, + neutralPrice: oraclePrice.neutralPrice * stEthPerToken / 1 ether, + timestamp: oraclePrice.timestamp + }); + } +} diff --git a/src/OracleMiddleware/WstEthOracleMiddleware.sol b/src/OracleMiddleware/WstEthOracleMiddlewareWithPyth.sol similarity index 81% rename from src/OracleMiddleware/WstEthOracleMiddleware.sol rename to src/OracleMiddleware/WstEthOracleMiddlewareWithPyth.sol index 4e7f2976c..ba4acb463 100644 --- a/src/OracleMiddleware/WstEthOracleMiddleware.sol +++ b/src/OracleMiddleware/WstEthOracleMiddlewareWithPyth.sol @@ -2,15 +2,17 @@ pragma solidity 0.8.26; import { IWstETH } from "../interfaces/IWstETH.sol"; +import { IBaseOracleMiddleware } from "../interfaces/OracleMiddleware/IBaseOracleMiddleware.sol"; import { PriceInfo } from "../interfaces/OracleMiddleware/IOracleMiddlewareTypes.sol"; import { IUsdnProtocolTypes as Types } from "../interfaces/UsdnProtocol/IUsdnProtocolTypes.sol"; -import { OracleMiddleware } from "./OracleMiddleware.sol"; +import { CommonOracleMiddleware } from "./CommonOracleMiddleware.sol"; +import { OracleMiddlewareWithPyth } from "./OracleMiddlewareWithPyth.sol"; /** * @title Middleware Implementation For WstETH Price - * @notice This contract is used to get the price of wstETH from the eth price oracle. + * @notice This contract is used to get the price of wstETH from the ETH price oracle. */ -contract WstEthOracleMiddleware is OracleMiddleware { +contract WstEthOracleMiddlewareWithPyth is OracleMiddlewareWithPyth { /// @notice The wstETH contract. IWstETH internal immutable _wstEth; @@ -27,13 +29,13 @@ contract WstEthOracleMiddleware is OracleMiddleware { address chainlinkPriceFeed, address wstETH, uint256 chainlinkTimeElapsedLimit - ) OracleMiddleware(pythContract, pythPriceID, chainlinkPriceFeed, chainlinkTimeElapsedLimit) { + ) OracleMiddlewareWithPyth(pythContract, pythPriceID, chainlinkPriceFeed, chainlinkTimeElapsedLimit) { _wstEth = IWstETH(wstETH); } /** - * @inheritdoc OracleMiddleware - * @notice Parses and validates `data`, returns the corresponding price data by applying eth/wstETH ratio. + * @inheritdoc IBaseOracleMiddleware + * @notice Parses and validates `data`, returns the corresponding price data by applying ETH/wstETH ratio. * @dev The data format is specific to the middleware and is simply forwarded from the user transaction's calldata. * Wsteth price is calculated as follows: `ethPrice x stEthPerToken / 1 ether`. * A fee amounting to exactly {validationCost} (with the same `data` and `action`) must be sent or the transaction @@ -53,7 +55,7 @@ contract WstEthOracleMiddleware is OracleMiddleware { uint128 targetTimestamp, Types.ProtocolAction action, bytes calldata data - ) public payable virtual override returns (PriceInfo memory) { + ) public payable virtual override(IBaseOracleMiddleware, CommonOracleMiddleware) returns (PriceInfo memory) { PriceInfo memory ethPrice = super.parseAndValidatePrice(actionId, targetTimestamp, action, data); uint256 stEthPerToken = _wstEth.stEthPerToken(); diff --git a/src/OracleMiddleware/WstEthOracleMiddlewareWithRedstone.sol b/src/OracleMiddleware/WstEthOracleMiddlewareWithRedstone.sol deleted file mode 100644 index cbbcae886..000000000 --- a/src/OracleMiddleware/WstEthOracleMiddlewareWithRedstone.sol +++ /dev/null @@ -1,71 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity 0.8.26; - -import { IWstETH } from "../interfaces/IWstETH.sol"; -import { IBaseOracleMiddleware } from "../interfaces/OracleMiddleware/IBaseOracleMiddleware.sol"; -import { PriceInfo } from "../interfaces/OracleMiddleware/IOracleMiddlewareTypes.sol"; -import { IUsdnProtocolTypes as Types } from "../interfaces/UsdnProtocol/IUsdnProtocolTypes.sol"; -import { OracleMiddleware } from "./OracleMiddleware.sol"; -import { OracleMiddlewareWithRedstone } from "./OracleMiddlewareWithRedstone.sol"; - -/** - * @title Contract to apply and return wsteth price - * @notice This contract is used to get the price of wsteth from the eth price oracle - */ -contract WstEthOracleMiddlewareWithRedstone is OracleMiddlewareWithRedstone { - /// @notice wsteth instance - IWstETH internal immutable _wstEth; - - /** - * @param pythContract The address of the Pyth contract - * @param pythPriceID The ID of the Pyth price feed - * @param redstoneFeedId The ID of the Redstone price feed - * @param chainlinkPriceFeed The address of the Chainlink price feed - * @param wstETH The address of the wstETH contract - * @param chainlinkTimeElapsedLimit The duration after which a Chainlink price is considered stale - */ - constructor( - address pythContract, - bytes32 pythPriceID, - bytes32 redstoneFeedId, - address chainlinkPriceFeed, - address wstETH, - uint256 chainlinkTimeElapsedLimit - ) - OracleMiddlewareWithRedstone( - pythContract, - pythPriceID, - redstoneFeedId, - chainlinkPriceFeed, - chainlinkTimeElapsedLimit - ) - { - _wstEth = IWstETH(wstETH); - } - - /** - * @inheritdoc IBaseOracleMiddleware - * @notice Parses and validates price data by applying eth/wsteth ratio - * @dev The data format is specific to the middleware and is simply forwarded from the user transaction's calldata - * Wsteth price is calculated as follows: ethPrice x stEthPerToken / 1 ether - */ - function parseAndValidatePrice( - bytes32 actionId, - uint128 targetTimestamp, - Types.ProtocolAction action, - bytes calldata data - ) public payable virtual override(IBaseOracleMiddleware, OracleMiddleware) returns (PriceInfo memory) { - // fetched eth price - PriceInfo memory ethPrice = super.parseAndValidatePrice(actionId, targetTimestamp, action, data); - - // stEth ratio for one wstEth - uint256 stEthPerToken = _wstEth.stEthPerToken(); - - // wsteth price - return PriceInfo({ - price: ethPrice.price * stEthPerToken / 1 ether, - neutralPrice: ethPrice.neutralPrice * stEthPerToken / 1 ether, - timestamp: ethPrice.timestamp - }); - } -} diff --git a/src/OracleMiddleware/WusdnToEthOracleMiddleware.sol b/src/OracleMiddleware/WusdnToEthOracleMiddlewareWithDataStreams.sol similarity index 79% rename from src/OracleMiddleware/WusdnToEthOracleMiddleware.sol rename to src/OracleMiddleware/WusdnToEthOracleMiddlewareWithDataStreams.sol index 3cbe07eea..1ef941e59 100644 --- a/src/OracleMiddleware/WusdnToEthOracleMiddleware.sol +++ b/src/OracleMiddleware/WusdnToEthOracleMiddlewareWithDataStreams.sol @@ -1,24 +1,27 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.26; +import { IBaseOracleMiddleware } from "../interfaces/OracleMiddleware/IBaseOracleMiddleware.sol"; import { PriceInfo } from "../interfaces/OracleMiddleware/IOracleMiddlewareTypes.sol"; import { IUsdn } from "../interfaces/Usdn/IUsdn.sol"; import { IUsdnProtocolTypes as Types } from "../interfaces/UsdnProtocol/IUsdnProtocolTypes.sol"; -import { OracleMiddleware } from "./OracleMiddleware.sol"; +import { CommonOracleMiddleware, OracleMiddlewareWithDataStreams } from "./OracleMiddlewareWithDataStreams.sol"; /** * @title Middleware Implementation For Short ETH Protocol - * @notice This contract is used to get the "inverse" price in ETH/WUSDN denomination, so that it can be used for a + * @notice This contract is used to get the "inverse" price in number of ETH per WUSDN, so that it can be used for a * shorting version of the USDN protocol with WUSDN as the underlying asset. + * @dev This version uses Pyth or Chainlink Data Streams for liquidations, and only Chainlink Data Streams for + * validation actions. */ -contract WusdnToEthOracleMiddleware is OracleMiddleware { +contract WusdnToEthOracleMiddlewareWithDataStreams is OracleMiddlewareWithDataStreams { /// @dev One dollar with the middleware decimals. uint256 internal constant ONE_DOLLAR = 10 ** MIDDLEWARE_DECIMALS; /// @dev Max divisor from the USDN token (as constant for gas optimisation). uint256 internal constant USDN_MAX_DIVISOR = 1e18; - /// @dev Numerator for the price calculation in {WusdnToEthOracleMiddleware.parseAndValidatePrice}. + /// @dev Numerator for the price calculation in {WusdnToEthOracleMiddlewareWithPyth.parseAndValidatePrice}. uint256 internal constant PRICE_NUMERATOR = 10 ** MIDDLEWARE_DECIMALS * ONE_DOLLAR * USDN_MAX_DIVISOR; /// @notice The USDN token address. @@ -30,20 +33,33 @@ contract WusdnToEthOracleMiddleware is OracleMiddleware { * @param chainlinkPriceFeed The address of the ETH Chainlink price feed. * @param usdnToken The address of the USDN token. * @param chainlinkTimeElapsedLimit The duration after which a Chainlink price is considered stale. + * @param chainlinkProxyVerifierAddress The address of the Chainlink proxy verifier contract. + * @param chainlinkStreamId The Chainlink data stream ID for ETH/USD. */ constructor( address pythContract, bytes32 pythPriceID, address chainlinkPriceFeed, address usdnToken, - uint256 chainlinkTimeElapsedLimit - ) OracleMiddleware(pythContract, pythPriceID, chainlinkPriceFeed, chainlinkTimeElapsedLimit) { + uint256 chainlinkTimeElapsedLimit, + address chainlinkProxyVerifierAddress, + bytes32 chainlinkStreamId + ) + OracleMiddlewareWithDataStreams( + pythContract, + pythPriceID, + chainlinkPriceFeed, + chainlinkTimeElapsedLimit, + chainlinkProxyVerifierAddress, + chainlinkStreamId + ) + { USDN = IUsdn(usdnToken); } /** - * @inheritdoc OracleMiddleware - * @dev This function returns an approximation of the price ETH/WUSDN, so how much ETH each WUSDN token is worth. + * @inheritdoc IBaseOracleMiddleware + * @dev This function returns an approximation of the price, so how much ETH each WUSDN token is worth. * The exact formula would be to divide the $/WUSDN price by the $/ETH price, which would look like this (as a * decimal number): * p = pWUSDN / pETH = (pUSDN * MAX_DIVISOR / divisor) / pETH = (pUSDN * MAX_DIVISOR) / (pETH * divisor) @@ -84,7 +100,7 @@ contract WusdnToEthOracleMiddleware is OracleMiddleware { uint128 targetTimestamp, Types.ProtocolAction action, bytes calldata data - ) public payable virtual override returns (PriceInfo memory) { + ) public payable virtual override(CommonOracleMiddleware, IBaseOracleMiddleware) returns (PriceInfo memory) { PriceInfo memory ethPrice = super.parseAndValidatePrice(actionId, targetTimestamp, action, data); uint256 divisor = USDN.divisor(); int256 adjustmentDelta = int256(ethPrice.price) - int256(ethPrice.neutralPrice); diff --git a/src/OracleMiddleware/WusdnToEthOracleMiddlewareWithPyth.sol b/src/OracleMiddleware/WusdnToEthOracleMiddlewareWithPyth.sol new file mode 100644 index 000000000..9fec1d4ea --- /dev/null +++ b/src/OracleMiddleware/WusdnToEthOracleMiddlewareWithPyth.sol @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.26; + +import { FixedPointMathLib } from "solady/src/utils/FixedPointMathLib.sol"; + +import { IBaseOracleMiddleware } from "../interfaces/OracleMiddleware/IBaseOracleMiddleware.sol"; +import { PriceInfo } from "../interfaces/OracleMiddleware/IOracleMiddlewareTypes.sol"; +import { IUsdn } from "../interfaces/Usdn/IUsdn.sol"; +import { IUsdnProtocolTypes as Types } from "../interfaces/UsdnProtocol/IUsdnProtocolTypes.sol"; +import { CommonOracleMiddleware, OracleMiddlewareWithPyth } from "./OracleMiddlewareWithPyth.sol"; + +/** + * @title Middleware Implementation For Short ETH Protocol + * @notice This contract is used to get the "inverse" price in number of ETH per WUSDN, so that it can be used for a + * shorting version of the USDN protocol with WUSDN as the underlying asset. + * @dev This version uses Pyth for low-latency prices used during validation actions and liquidations. + */ +contract WusdnToEthOracleMiddlewareWithPyth is OracleMiddlewareWithPyth { + /// @dev One dollar with the middleware decimals. + uint256 internal constant ONE_DOLLAR = 10 ** MIDDLEWARE_DECIMALS; + + /// @dev Max divisor from the USDN token (as constant for gas optimisation). + uint256 internal constant USDN_MAX_DIVISOR = 1e18; + + /// @dev Numerator for the price calculation in {WusdnToEthOracleMiddlewareWithPyth.parseAndValidatePrice}. + uint256 internal constant PRICE_NUMERATOR = 10 ** MIDDLEWARE_DECIMALS * ONE_DOLLAR * USDN_MAX_DIVISOR; + + /// @notice The USDN token address. + IUsdn internal immutable USDN; + + /** + * @param pythContract The address of the Pyth contract. + * @param pythPriceID The ID of the ETH Pyth price feed. + * @param chainlinkPriceFeed The address of the ETH Chainlink price feed. + * @param usdnToken The address of the USDN token. + * @param chainlinkTimeElapsedLimit The duration after which a Chainlink price is considered stale. + */ + constructor( + address pythContract, + bytes32 pythPriceID, + address chainlinkPriceFeed, + address usdnToken, + uint256 chainlinkTimeElapsedLimit + ) OracleMiddlewareWithPyth(pythContract, pythPriceID, chainlinkPriceFeed, chainlinkTimeElapsedLimit) { + USDN = IUsdn(usdnToken); + } + + /** + * @inheritdoc IBaseOracleMiddleware + * @dev This function returns an approximation of the price, so how much ETH each WUSDN token is worth. + * The exact formula would be to divide the $/WUSDN price by the $/ETH price, which would look like this (as a + * decimal number): + * p = pWUSDN / pETH = (pUSDN * MAX_DIVISOR / divisor) / pETH = (pUSDN * MAX_DIVISOR) / (pETH * divisor) + * = ((usdnVaultBalance * pWstETH / usdnTotalSupply) * MAX_DIVISOR) / (pETH * divisor) + * = (usdnVaultBalance * pETH * stETHRatio * MAX_DIVISOR) / (pETH * divisor * usdnTotalSupply) + * = (usdnVaultBalance * stETHRatio * MAX_DIVISOR) / (usdnTotalSupply * divisor) + * + * Because we don't have historical access to the vault balance, the stETH ratio, the USDN total supply and the + * USDN divisor, we must approximate some parameters. The following approximations are made: + * - The USDN price is $1 + * - The USDN divisor's current value is valid (constant) for the period where we need to provide prices. + * + * This greatly simplifies the formula (with $1 and pETH having 18 decimals): + * p = ($1 * MAX_DIVISOR) / (pETH * divisor) = 1e18 * MAX_DIVISOR / (pETH * divisor) + * + * Since we want to represent this price as an integer with a fixed precision of 18 decimals, the number needs + * to be multiplied by 1e18. + * + * p_wei = 1e54 / (pETH * divisor) + * + * Because we re-use the logic of the {OracleMiddleware}, we need to invert the adjustment direction. So if an + * action in the original protocol requires that we add the confidence interval to the neutral price (e.g. to open + * a new long position), then this oracle middleware needs to subtract the same confidence interval from the + * neutral price to achieve the same effect, i.e. penalizing the user. This is because the ETH price is in the + * denominator of the formula. + * @param actionId A unique identifier for the current action. This identifier can be used to link an `Initiate` + * call with the corresponding `Validate` call. + * @param targetTimestamp The target timestamp for validating the price data. For validation actions, this is the + * timestamp of the initiation. + * @param action Type of action for which the price is requested. The middleware may use this to alter the + * validation of the price or the returned price. + * @param data The data to be used to communicate with oracles, the format varies from middleware to middleware and + * can be different depending on the action. + * @return result_ The price and timestamp as {IOracleMiddlewareTypes.PriceInfo}. + */ + function parseAndValidatePrice( + bytes32 actionId, + uint128 targetTimestamp, + Types.ProtocolAction action, + bytes calldata data + ) public payable virtual override(CommonOracleMiddleware, IBaseOracleMiddleware) returns (PriceInfo memory) { + PriceInfo memory ethPrice = super.parseAndValidatePrice(actionId, targetTimestamp, action, data); + uint256 divisor = USDN.divisor(); + int256 adjustmentDelta = int256(ethPrice.price) - int256(ethPrice.neutralPrice); + // invert the sign of the confidence interval if necessary + if (adjustmentDelta != 0) { + uint256 adjustedPrice; + if (FixedPointMathLib.abs(adjustmentDelta) >= ethPrice.neutralPrice) { + revert OracleMiddlewareConfValueTooHigh(); + } + + if (adjustmentDelta > 0) { + // adjustmentDelta is strictly smaller than neutralPrice so we can safely subtract it + unchecked { + adjustedPrice = ethPrice.neutralPrice - uint256(adjustmentDelta); + } + } else { + adjustedPrice = ethPrice.neutralPrice + uint256(-adjustmentDelta); + } + + return PriceInfo({ + price: PRICE_NUMERATOR / (adjustedPrice * divisor), + neutralPrice: PRICE_NUMERATOR / (ethPrice.neutralPrice * divisor), + timestamp: ethPrice.timestamp + }); + } else { + // gas optimization, only compute the price once because there is no confidence interval to apply + uint256 price = PRICE_NUMERATOR / (ethPrice.price * divisor); + return PriceInfo({ price: price, neutralPrice: price, timestamp: ethPrice.timestamp }); + } + } +} diff --git a/src/OracleMiddleware/mock/MockWstEthOracleMiddleware.sol b/src/OracleMiddleware/mock/MockWstEthOracleMiddlewareWithPyth.sol similarity index 79% rename from src/OracleMiddleware/mock/MockWstEthOracleMiddleware.sol rename to src/OracleMiddleware/mock/MockWstEthOracleMiddlewareWithPyth.sol index 0c05388f8..17958e931 100644 --- a/src/OracleMiddleware/mock/MockWstEthOracleMiddleware.sol +++ b/src/OracleMiddleware/mock/MockWstEthOracleMiddlewareWithPyth.sol @@ -1,23 +1,23 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.26; +import { IBaseOracleMiddleware } from "../../interfaces/OracleMiddleware/IBaseOracleMiddleware.sol"; import { PriceInfo } from "../../interfaces/OracleMiddleware/IOracleMiddlewareTypes.sol"; - import { IUsdnProtocolTypes as Types } from "../../interfaces/UsdnProtocol/IUsdnProtocolTypes.sol"; -import { OracleMiddleware } from "../OracleMiddleware.sol"; -import { WstEthOracleMiddleware } from "../WstEthOracleMiddleware.sol"; +import { CommonOracleMiddleware } from "../CommonOracleMiddleware.sol"; +import { WstEthOracleMiddlewareWithPyth } from "../WstEthOracleMiddlewareWithPyth.sol"; /** * @title Contract to apply and return a mocked wstETH price - * @notice This contract is used to get the price of wsteth by setting up a price or forwarding it to wstethMiddleware + * @notice This contract is used to get the price of wstETH by setting up a price or forwarding it to wstethMiddleware * @dev This aims at simulating price action. Do not use in production */ -contract MockWstEthOracleMiddleware is WstEthOracleMiddleware { +contract MockWstEthOracleMiddlewareWithPyth is WstEthOracleMiddlewareWithPyth { /// @notice Confidence interval percentage numerator uint16 internal _wstethMockedConfBps = 20; // default 0.2% conf /** - * @notice Wsteth mocked price + * @notice wstETH mocked price * @dev This price will be used if greater than zero */ uint256 internal _wstethMockedPrice; @@ -33,16 +33,16 @@ contract MockWstEthOracleMiddleware is WstEthOracleMiddleware { address chainlinkPriceFeed, address wsteth, uint256 chainlinkTimeElapsedLimit - ) WstEthOracleMiddleware(pythContract, pythFeedId, chainlinkPriceFeed, wsteth, chainlinkTimeElapsedLimit) { } + ) WstEthOracleMiddlewareWithPyth(pythContract, pythFeedId, chainlinkPriceFeed, wsteth, chainlinkTimeElapsedLimit) { } - /// @inheritdoc OracleMiddleware + /// @inheritdoc CommonOracleMiddleware function parseAndValidatePrice( bytes32 actionId, uint128 targetTimestamp, Types.ProtocolAction action, bytes calldata data ) public payable override returns (PriceInfo memory price_) { - // parse and validate from parent wsteth middleware + // parse and validate from parent WstEth middleware // this aims to verify pyth price hermes signature in any case if (_verifySignature || _wstethMockedPrice == 0) { price_ = super.parseAndValidatePrice(actionId, targetTimestamp, action, data); @@ -58,14 +58,14 @@ contract MockWstEthOracleMiddleware is WstEthOracleMiddleware { price_.neutralPrice = _wstethMockedPrice; price_.price = price_.neutralPrice; - // `ConfidenceInterval` down cases + // `PriceAdjustment` down cases if ( action == Types.ProtocolAction.ValidateDeposit || action == Types.ProtocolAction.ValidateClosePosition || action == Types.ProtocolAction.InitiateDeposit || action == Types.ProtocolAction.InitiateClosePosition ) { price_.price -= price_.price * _wstethMockedConfBps / BPS_DIVISOR; - // `ConfidenceInterval` up case + // `PriceAdjustment` up case } else if ( action == Types.ProtocolAction.ValidateWithdrawal || action == Types.ProtocolAction.ValidateOpenPosition || action == Types.ProtocolAction.InitiateWithdrawal || action == Types.ProtocolAction.InitiateOpenPosition @@ -75,8 +75,8 @@ contract MockWstEthOracleMiddleware is WstEthOracleMiddleware { } /** - * @notice Set Wsteth mocked price - * @dev If the new mocked wsteth is greater than zero this will validate this mocked price else this will validate + * @notice Set WstEth mocked price + * @dev If the new mocked WstEth is greater than zero this will validate this mocked price else this will validate * the parent middleware price * @param newWstethMockedPrice The mock price to set */ @@ -94,12 +94,12 @@ contract MockWstEthOracleMiddleware is WstEthOracleMiddleware { _wstethMockedConfBps = newWstethMockedConfPct; } - /// @notice Get current wsteth mocked price + /// @notice Get current WstEth mocked price function getWstethMockedPrice() external view returns (uint256) { return _wstethMockedPrice; } - /// @notice Get current wsteth mocked confidence interval + /// @notice Get current WstEth mocked confidence interval function getWstethMockedConfBps() external view returns (uint64) { return _wstethMockedConfBps; } @@ -114,11 +114,11 @@ contract MockWstEthOracleMiddleware is WstEthOracleMiddleware { _verifySignature = verify; } - /// @inheritdoc OracleMiddleware + /// @inheritdoc CommonOracleMiddleware function validationCost(bytes calldata data, Types.ProtocolAction action) public view - override + override(IBaseOracleMiddleware, CommonOracleMiddleware) returns (uint256 result_) { // no signature verification -> no oracle fee diff --git a/src/OracleMiddleware/mock/MockWstEthOracleMiddlewareWithRedstone.sol b/src/OracleMiddleware/mock/MockWstEthOracleMiddlewareWithRedstone.sol deleted file mode 100644 index 47273efe7..000000000 --- a/src/OracleMiddleware/mock/MockWstEthOracleMiddlewareWithRedstone.sol +++ /dev/null @@ -1,140 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity 0.8.26; - -import { PriceInfo } from "../../interfaces/OracleMiddleware/IOracleMiddlewareTypes.sol"; - -import { IBaseOracleMiddleware } from "../../interfaces/OracleMiddleware/IBaseOracleMiddleware.sol"; -import { IUsdnProtocolTypes as Types } from "../../interfaces/UsdnProtocol/IUsdnProtocolTypes.sol"; -import { OracleMiddleware } from "../OracleMiddleware.sol"; -import { WstEthOracleMiddlewareWithRedstone } from "../WstEthOracleMiddlewareWithRedstone.sol"; - -/** - * @title Contract to apply and return a mocked wstETH price - * @notice This contract is used to get the price of wsteth by setting up a price or forwarding it to wstethMiddleware - * @dev This aims at simulating price action. Do not use in production - */ -contract MockWstEthOracleMiddlewareWithRedstone is WstEthOracleMiddlewareWithRedstone { - /// @notice Confidence interval percentage numerator - uint16 internal _wstethMockedConfBps = 20; // default 0.2% conf - - /** - * @notice Wsteth mocked price - * @dev This price will be used if greater than zero - */ - uint256 internal _wstethMockedPrice; - /** - * @notice If we need to verify the provided signature data or not - * @dev If _wstethMockedPrice == 0, this setting is ignored - */ - bool internal _verifySignature = true; - - constructor( - address pythContract, - bytes32 pythFeedId, - bytes32 redstoneFeedId, - address chainlinkPriceFeed, - address wsteth, - uint256 chainlinkTimeElapsedLimit - ) - WstEthOracleMiddlewareWithRedstone( - pythContract, - pythFeedId, - redstoneFeedId, - chainlinkPriceFeed, - wsteth, - chainlinkTimeElapsedLimit - ) - { } - - /// @inheritdoc WstEthOracleMiddlewareWithRedstone - function parseAndValidatePrice( - bytes32 actionId, - uint128 targetTimestamp, - Types.ProtocolAction action, - bytes calldata data - ) public payable override returns (PriceInfo memory price_) { - // parse and validate from parent wsteth middleware - // this aims to verify pyth price hermes signature in any case - if (_verifySignature || _wstethMockedPrice == 0) { - price_ = super.parseAndValidatePrice(actionId, targetTimestamp, action, data); - } else { - price_.timestamp = targetTimestamp == 0 ? block.timestamp : targetTimestamp; - } - - // if the mocked price is not set, return - if (_wstethMockedPrice == 0) { - return price_; - } - - price_.neutralPrice = _wstethMockedPrice; - price_.price = price_.neutralPrice; - - // `ConfidenceInterval` down cases - if ( - action == Types.ProtocolAction.ValidateDeposit || action == Types.ProtocolAction.ValidateClosePosition - || action == Types.ProtocolAction.InitiateDeposit || action == Types.ProtocolAction.InitiateClosePosition - ) { - price_.price -= price_.price * _wstethMockedConfBps / BPS_DIVISOR; - - // `ConfidenceInterval` up case - } else if ( - action == Types.ProtocolAction.ValidateWithdrawal || action == Types.ProtocolAction.ValidateOpenPosition - || action == Types.ProtocolAction.InitiateWithdrawal || action == Types.ProtocolAction.InitiateOpenPosition - ) { - price_.price += price_.price * _wstethMockedConfBps / BPS_DIVISOR; - } - } - - /** - * @notice Set Wsteth mocked price - * @dev If the new mocked wsteth is greater than zero this will validate this mocked price else this will validate - * the parent middleware price - * @param newWstethMockedPrice The mock price to set - */ - function setWstethMockedPrice(uint256 newWstethMockedPrice) external { - _wstethMockedPrice = newWstethMockedPrice; - } - - /** - * @notice Set Wsteth mocked confidence interval percentage - * @dev To calculate a percentage of the neutral price up or down in some protocol actions - * @param newWstethMockedConfPct The mock confidence interval - */ - function setWstethMockedConfBps(uint16 newWstethMockedConfPct) external { - require(newWstethMockedConfPct <= 1500, "15% max"); - _wstethMockedConfBps = newWstethMockedConfPct; - } - - /// @notice Get current wsteth mocked price - function getWstethMockedPrice() external view returns (uint256) { - return _wstethMockedPrice; - } - - /// @notice Get current wsteth mocked confidence interval - function getWstethMockedConfBps() external view returns (uint64) { - return _wstethMockedConfBps; - } - - /// @notice Get the signature verification flag - function getVerifySignature() external view returns (bool) { - return _verifySignature; - } - - /// @notice Set the signature verification flag - function setVerifySignature(bool verify) external { - _verifySignature = verify; - } - - /// @inheritdoc IBaseOracleMiddleware - function validationCost(bytes calldata data, Types.ProtocolAction action) - public - view - override(IBaseOracleMiddleware, OracleMiddleware) - returns (uint256 result_) - { - // no signature verification -> no oracle fee - if (!_verifySignature) return 0; - - return super.validationCost(data, action); - } -} diff --git a/src/OracleMiddleware/oracles/ChainlinkDataStreamsOracle.sol b/src/OracleMiddleware/oracles/ChainlinkDataStreamsOracle.sol new file mode 100644 index 000000000..c957b349a --- /dev/null +++ b/src/OracleMiddleware/oracles/ChainlinkDataStreamsOracle.sol @@ -0,0 +1,162 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import { IChainlinkDataStreamsOracle } from "../../interfaces/OracleMiddleware/IChainlinkDataStreamsOracle.sol"; +import { IFeeManager } from "../../interfaces/OracleMiddleware/IFeeManager.sol"; +import { IOracleMiddlewareErrors } from "../../interfaces/OracleMiddleware/IOracleMiddlewareErrors.sol"; +import { FormattedDataStreamsPrice } from "../../interfaces/OracleMiddleware/IOracleMiddlewareTypes.sol"; +import { IVerifierProxy } from "../../interfaces/OracleMiddleware/IVerifierProxy.sol"; + +/** + * @title Oracle Middleware for Chainlink Data Streams + * @notice This contract is used to get the price of the asset that corresponds to the stored Chainlink data streams ID. + * @dev Is implemented by the {OracleMiddlewareWithDataStreams} contract. + */ +abstract contract ChainlinkDataStreamsOracle is IOracleMiddlewareErrors, IChainlinkDataStreamsOracle { + /// @notice The address of the Chainlink proxy verifier contract. + IVerifierProxy internal immutable PROXY_VERIFIER; + + /** + * @notice The ID of the Chainlink data streams. + * @dev Any data streams are standardized to 18 decimals. + */ + bytes32 internal immutable STREAM_ID; + + /// @notice The report version. + uint256 internal constant REPORT_VERSION = 3; + + /// @notice The maximum age of a recent price to be considered valid for Chainlink data streams. + uint256 internal _dataStreamsRecentPriceDelay = 45 seconds; + + /** + * @param verifierAddress The address of the Chainlink proxy verifier contract. + * @param streamId The ID of the Chainlink data streams. + */ + constructor(address verifierAddress, bytes32 streamId) { + PROXY_VERIFIER = IVerifierProxy(verifierAddress); + STREAM_ID = streamId; + } + + /// @inheritdoc IChainlinkDataStreamsOracle + function getProxyVerifier() external view returns (IVerifierProxy proxyVerifier_) { + return PROXY_VERIFIER; + } + + /// @inheritdoc IChainlinkDataStreamsOracle + function getStreamId() external view returns (bytes32 streamId_) { + return STREAM_ID; + } + + /// @inheritdoc IChainlinkDataStreamsOracle + function getDataStreamRecentPriceDelay() external view returns (uint256 delay_) { + return _dataStreamsRecentPriceDelay; + } + + /// @inheritdoc IChainlinkDataStreamsOracle + function getReportVersion() external pure returns (uint256 version_) { + return REPORT_VERSION; + } + + /** + * @notice Gets the formatted price of the asset with Chainlink data streams. + * @param payload The full report obtained from the Chainlink data streams API. + * @param targetTimestamp The target timestamp of the price. + * If zero, then we accept all recent prices. + * @param targetLimit The most recent timestamp a price can have. + * Can be zero if `targetTimestamp` is zero. + * @return formattedPrice_ The Chainlink formatted price with 18 decimals. + */ + function _getChainlinkDataStreamPrice(bytes calldata payload, uint128 targetTimestamp, uint128 targetLimit) + internal + returns (FormattedDataStreamsPrice memory formattedPrice_) + { + IFeeManager.Asset memory feeData = _getChainlinkDataStreamFeeData(payload); + + // Sanity check on the fee requested by the Chainlink fee manager + if (feeData.amount > 0.01 ether) { + revert OracleMiddlewareDataStreamFeeSafeguard(feeData.amount); + } + if (msg.value != feeData.amount) { + revert OracleMiddlewareIncorrectFee(); + } + + // Verify report + bytes memory verifiedReportData = + PROXY_VERIFIER.verify{ value: feeData.amount }(payload, abi.encode(feeData.assetAddress)); + + // Report version + uint16 reportVersion = (uint16(uint8(verifiedReportData[0])) << 8) | uint16(uint8(verifiedReportData[1])); + if (reportVersion != REPORT_VERSION) { + revert OracleMiddlewareInvalidReportVersion(); + } + + // Decode verified report + IVerifierProxy.ReportV3 memory verifiedReport = abi.decode(verifiedReportData, (IVerifierProxy.ReportV3)); + + // Stream ID + if (verifiedReport.feedId != STREAM_ID) { + revert OracleMiddlewareInvalidStreamId(); + } + + // Report timestamp + if (targetTimestamp == 0) { + // If targetTimestamp is 0, we check if the verified report's validFromTimestamp is older or equal than + // the current block timestamp minus the `_dataStreamsRecentPriceDelay`. This check ensures that the price + // data is considered recent enough to be valid for use, while not strictly requiring it to be the current + // timestamp. + if (verifiedReport.validFromTimestamp < block.timestamp - _dataStreamsRecentPriceDelay) { + revert OracleMiddlewareDataStreamInvalidTimestamp(); + } + + // The report is considered valid if the `targetTimestamp` is within the interval + // `[validFromTimestamp,observationsTimestamp]` and the `observationsTimestamp` + // does not exceed the `targetLimit`. + } else if ( + targetTimestamp < verifiedReport.validFromTimestamp + || verifiedReport.observationsTimestamp < targetTimestamp + || targetLimit < verifiedReport.observationsTimestamp + ) { + revert OracleMiddlewareDataStreamInvalidTimestamp(); + } + + // Report prices + if (verifiedReport.price <= 0) { + revert OracleMiddlewareWrongPrice(verifiedReport.price); + } + if (verifiedReport.ask <= 0) { + revert OracleMiddlewareWrongAskPrice(verifiedReport.ask); + } + if (verifiedReport.bid <= 0) { + revert OracleMiddlewareWrongBidPrice(verifiedReport.bid); + } + + // The following values (price, ask, bid) have been validated to be greater than 0, + // making the casting to uint192 safe. + return FormattedDataStreamsPrice({ + timestamp: verifiedReport.observationsTimestamp, + price: uint192(verifiedReport.price), + ask: uint192(verifiedReport.ask), + bid: uint192(verifiedReport.bid) + }); + } + + /** + * @notice Gets the fee asset data to decode the payload. + * @dev The native token fee option will be used. + * @param payload The data streams payload (full report). + * @return feeData_ The fee asset data including the token and the amount required to verify the report. + */ + function _getChainlinkDataStreamFeeData(bytes calldata payload) + internal + view + returns (IFeeManager.Asset memory feeData_) + { + IFeeManager feeManager = PROXY_VERIFIER.s_feeManager(); + if (address(feeManager) == address(0)) { + return feeData_; + } + (, bytes memory reportData) = abi.decode(payload, (bytes32[3], bytes)); + address quoteAddress = feeManager.i_nativeAddress(); + (feeData_,,) = feeManager.getFeeAndReward(address(this), reportData, quoteAddress); + } +} diff --git a/src/OracleMiddleware/oracles/ChainlinkOracle.sol b/src/OracleMiddleware/oracles/ChainlinkOracle.sol index 93dace2cc..125505383 100644 --- a/src/OracleMiddleware/oracles/ChainlinkOracle.sol +++ b/src/OracleMiddleware/oracles/ChainlinkOracle.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BUSL-1.1 -pragma solidity 0.8.26; +pragma solidity ^0.8.0; import { AggregatorV3Interface } from "@chainlink/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol"; diff --git a/src/OracleMiddleware/oracles/PythOracle.sol b/src/OracleMiddleware/oracles/PythOracle.sol index d87b21b39..0a5a6c1a3 100644 --- a/src/OracleMiddleware/oracles/PythOracle.sol +++ b/src/OracleMiddleware/oracles/PythOracle.sol @@ -1,19 +1,18 @@ // SPDX-License-Identifier: BUSL-1.1 -pragma solidity 0.8.26; +pragma solidity ^0.8.0; import { IPyth } from "@pythnetwork/pyth-sdk-solidity/IPyth.sol"; import { PythStructs } from "@pythnetwork/pyth-sdk-solidity/PythStructs.sol"; -import { IOracleMiddlewareErrors } from "../../interfaces/OracleMiddleware/IOracleMiddlewareErrors.sol"; import { FormattedPythPrice } from "../../interfaces/OracleMiddleware/IOracleMiddlewareTypes.sol"; import { IPythOracle } from "../../interfaces/OracleMiddleware/IPythOracle.sol"; /** * @title Contract To Communicate With The Pyth Oracle * @notice This contract is used to get the price of the asset that corresponds to the stored feed ID. - * @dev Is implemented by the {OracleMiddleware} contract. + * @dev Is implemented by the {CommonOracleMiddleware} contract. */ -abstract contract PythOracle is IPythOracle, IOracleMiddlewareErrors { +abstract contract PythOracle is IPythOracle { /// @notice The ID of the Pyth price feed. bytes32 internal immutable _pythFeedId; diff --git a/src/OracleMiddleware/oracles/RedstoneOracle.sol b/src/OracleMiddleware/oracles/RedstoneOracle.sol index 7844f6d3d..08db512eb 100644 --- a/src/OracleMiddleware/oracles/RedstoneOracle.sol +++ b/src/OracleMiddleware/oracles/RedstoneOracle.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BUSL-1.1 -pragma solidity 0.8.26; +pragma solidity ^0.8.0; // Temporary measure, forked the contracts to remove dependency on safemath diff --git a/src/UsdnProtocol/UsdnProtocolActions.sol b/src/UsdnProtocol/UsdnProtocolActions.sol index 85b840ab7..d943b32f1 100644 --- a/src/UsdnProtocol/UsdnProtocolActions.sol +++ b/src/UsdnProtocol/UsdnProtocolActions.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BUSL-1.1 -pragma solidity 0.8.26; +pragma solidity ^0.8.0; import { PausableUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol"; import { EIP712Upgradeable } from "@openzeppelin/contracts-upgradeable/utils/cryptography/EIP712Upgradeable.sol"; diff --git a/src/UsdnProtocol/UsdnProtocolCore.sol b/src/UsdnProtocol/UsdnProtocolCore.sol index 189355c5c..10bd5b495 100644 --- a/src/UsdnProtocol/UsdnProtocolCore.sol +++ b/src/UsdnProtocol/UsdnProtocolCore.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BUSL-1.1 -pragma solidity 0.8.26; +pragma solidity ^0.8.0; import { AccessControlDefaultAdminRulesUpgradeable } from "@openzeppelin/contracts-upgradeable/access/extensions/AccessControlDefaultAdminRulesUpgradeable.sol"; diff --git a/src/UsdnProtocol/UsdnProtocolFallback.sol b/src/UsdnProtocol/UsdnProtocolFallback.sol index 7f6fd6b11..ff870608a 100644 --- a/src/UsdnProtocol/UsdnProtocolFallback.sol +++ b/src/UsdnProtocol/UsdnProtocolFallback.sol @@ -27,15 +27,30 @@ import { UsdnProtocolUtilsLibrary as Utils } from "./libraries/UsdnProtocolUtils import { UsdnProtocolVaultLibrary as Vault } from "./libraries/UsdnProtocolVaultLibrary.sol"; contract UsdnProtocolFallback is + IUsdnProtocolFallback, IUsdnProtocolErrors, IUsdnProtocolEvents, - IUsdnProtocolFallback, InitializableReentrancyGuard, PausableUpgradeable, AccessControlDefaultAdminRulesUpgradeable { using SafeTransferLib for address; + /// @notice The highest value allowed for the ratio of SDEX to burn per minted USDN on deposit. + uint256 internal immutable MAX_SDEX_BURN_RATIO; + + /// @notice The highest value allowed for the minimum long position setting. + uint256 internal immutable MAX_MIN_LONG_POSITION; + + /** + * @param maxSdexBurnRatio The value of `MAX_SDEX_BURN_RATIO`. + * @param maxMinLongPosition The value of `MAX_MIN_LONG_POSITION`. + */ + constructor(uint256 maxSdexBurnRatio, uint256 maxMinLongPosition) { + MAX_SDEX_BURN_RATIO = maxSdexBurnRatio; + MAX_MIN_LONG_POSITION = maxMinLongPosition; + } + /// @inheritdoc IUsdnProtocolFallback function getActionablePendingActions(address currentUser, uint256 lookAhead, uint256 maxIter) external @@ -299,7 +314,7 @@ contract UsdnProtocolFallback is } /// @inheritdoc IUsdnProtocolFallback - function getSdexBurnOnDepositRatio() external view returns (uint32 ratio_) { + function getSdexBurnOnDepositRatio() external view returns (uint64 ratio_) { return Utils._getMainStorage()._sdexBurnOnDepositRatio; } @@ -575,8 +590,8 @@ contract UsdnProtocolFallback is } /// @inheritdoc IUsdnProtocolFallback - function setSdexBurnOnDepositRatio(uint32 newRatio) external onlyRole(Constants.SET_PROTOCOL_PARAMS_ROLE) { - Setters.setSdexBurnOnDepositRatio(newRatio); + function setSdexBurnOnDepositRatio(uint64 newRatio) external onlyRole(Constants.SET_PROTOCOL_PARAMS_ROLE) { + Setters.setSdexBurnOnDepositRatio(MAX_SDEX_BURN_RATIO, newRatio); } /// @inheritdoc IUsdnProtocolFallback @@ -608,7 +623,7 @@ contract UsdnProtocolFallback is /// @inheritdoc IUsdnProtocolFallback function setMinLongPosition(uint256 newMinLongPosition) external onlyRole(Constants.SET_PROTOCOL_PARAMS_ROLE) { - Setters.setMinLongPosition(newMinLongPosition); + Setters.setMinLongPosition(MAX_MIN_LONG_POSITION, newMinLongPosition); } /* -------------------------------------------------------------------------- */ diff --git a/src/UsdnProtocol/UsdnProtocolImpl.sol b/src/UsdnProtocol/UsdnProtocolImpl.sol index a568fdb47..d89d54e91 100644 --- a/src/UsdnProtocol/UsdnProtocolImpl.sol +++ b/src/UsdnProtocol/UsdnProtocolImpl.sol @@ -1,15 +1,8 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.26; -import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import { UUPSUpgradeable } from "solady/src/utils/UUPSUpgradeable.sol"; -import { IBaseLiquidationRewardsManager } from - "../interfaces/LiquidationRewardsManager/IBaseLiquidationRewardsManager.sol"; -import { IBaseOracleMiddleware } from "../interfaces/OracleMiddleware/IBaseOracleMiddleware.sol"; -import { IUsdn } from "../interfaces/Usdn/IUsdn.sol"; -import { IUsdnProtocolErrors } from "../interfaces/UsdnProtocol/IUsdnProtocolErrors.sol"; -import { IUsdnProtocolFallback } from "../interfaces/UsdnProtocol/IUsdnProtocolFallback.sol"; import { IUsdnProtocolImpl } from "../interfaces/UsdnProtocol/IUsdnProtocolImpl.sol"; import { UsdnProtocolActions } from "./UsdnProtocolActions.sol"; import { UsdnProtocolCore } from "./UsdnProtocolCore.sol"; @@ -20,7 +13,6 @@ import { UsdnProtocolSettersLibrary as Setters } from "./libraries/UsdnProtocolS import { UsdnProtocolUtilsLibrary as Utils } from "./libraries/UsdnProtocolUtilsLibrary.sol"; contract UsdnProtocolImpl is - IUsdnProtocolErrors, IUsdnProtocolImpl, UsdnProtocolActions, UsdnProtocolCore, @@ -34,7 +26,7 @@ contract UsdnProtocolImpl is } /// @inheritdoc IUsdnProtocolImpl - function initializeStorage(InitStorage calldata initStorage) public initializer { + function initializeStorage(InitStorage calldata initStorage) public reinitializer(2) { __AccessControlDefaultAdminRules_init(0, msg.sender); __initializeReentrancyGuard_init(); __Pausable_init(); diff --git a/src/UsdnProtocol/UsdnProtocolLong.sol b/src/UsdnProtocol/UsdnProtocolLong.sol index 2073045c3..5df0d93be 100644 --- a/src/UsdnProtocol/UsdnProtocolLong.sol +++ b/src/UsdnProtocol/UsdnProtocolLong.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BUSL-1.1 -pragma solidity 0.8.26; +pragma solidity ^0.8.0; import { HugeUint } from "@smardex-solidity-libraries-1/HugeUint.sol"; diff --git a/src/UsdnProtocol/UsdnProtocolVault.sol b/src/UsdnProtocol/UsdnProtocolVault.sol index 74e201ac4..f690f59a6 100644 --- a/src/UsdnProtocol/UsdnProtocolVault.sol +++ b/src/UsdnProtocol/UsdnProtocolVault.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BUSL-1.1 -pragma solidity 0.8.26; +pragma solidity ^0.8.0; import { IUsdnProtocolVault } from "../interfaces/UsdnProtocol/IUsdnProtocolVault.sol"; import { UsdnProtocolVaultLibrary as Vault } from "./libraries/UsdnProtocolVaultLibrary.sol"; diff --git a/src/UsdnProtocol/libraries/UsdnProtocolConstantsLibrary.sol b/src/UsdnProtocol/libraries/UsdnProtocolConstantsLibrary.sol index 2e1fe53e0..556318d49 100644 --- a/src/UsdnProtocol/libraries/UsdnProtocolConstantsLibrary.sol +++ b/src/UsdnProtocol/libraries/UsdnProtocolConstantsLibrary.sol @@ -94,18 +94,12 @@ library UsdnProtocolConstantsLibrary { /// @notice Maximum ratio of SDEX rewards allowed in basis points. uint256 internal constant MAX_SDEX_REWARDS_RATIO_BPS = 1000; - /// @notice Maximum ratio of SDEX to burn per minted USDN on deposit (10%). - uint256 internal constant MAX_SDEX_BURN_RATIO = SDEX_BURN_ON_DEPOSIT_DIVISOR / 10; - /// @notice Maximum leverage allowed. uint256 internal constant MAX_LEVERAGE = 100 * 10 ** LEVERAGE_DECIMALS; /// @notice Maximum security deposit allowed. uint256 internal constant MAX_SECURITY_DEPOSIT = 5 ether; - /// @notice The highest value allowed for the minimum long position setting. - uint256 internal constant MAX_MIN_LONG_POSITION = 10 ether; - /// @notice Maximum protocol fee allowed in basis points. uint16 internal constant MAX_PROTOCOL_FEE_BPS = 3000; diff --git a/src/UsdnProtocol/libraries/UsdnProtocolSettersLibrary.sol b/src/UsdnProtocol/libraries/UsdnProtocolSettersLibrary.sol index 286b0929d..b3fbb7c27 100644 --- a/src/UsdnProtocol/libraries/UsdnProtocolSettersLibrary.sol +++ b/src/UsdnProtocol/libraries/UsdnProtocolSettersLibrary.sol @@ -230,10 +230,10 @@ library UsdnProtocolSettersLibrary { } /// @notice See {UsdnProtocolFallback.setSdexBurnOnDepositRatio}. - function setSdexBurnOnDepositRatio(uint32 newRatio) external { + function setSdexBurnOnDepositRatio(uint256 highestPossibleValue, uint64 newRatio) external { Types.Storage storage s = Utils._getMainStorage(); - if (newRatio > Constants.MAX_SDEX_BURN_RATIO) { + if (newRatio > highestPossibleValue) { revert IUsdnProtocolErrors.UsdnProtocolInvalidBurnSdexOnDepositRatio(); } @@ -313,10 +313,10 @@ library UsdnProtocolSettersLibrary { } /// @notice See {UsdnProtocolFallback.setMinLongPosition}. - function setMinLongPosition(uint256 newMinLongPosition) external { + function setMinLongPosition(uint256 highestPossibleValue, uint256 newMinLongPosition) external { Types.Storage storage s = Utils._getMainStorage(); - if (newMinLongPosition > Constants.MAX_MIN_LONG_POSITION) { + if (newMinLongPosition > highestPossibleValue) { revert IUsdnProtocolErrors.UsdnProtocolInvalidMinLongPosition(); } diff --git a/src/UsdnProtocol/libraries/UsdnProtocolUtilsLibrary.sol b/src/UsdnProtocol/libraries/UsdnProtocolUtilsLibrary.sol index 84a901b7e..77876f0fd 100644 --- a/src/UsdnProtocol/libraries/UsdnProtocolUtilsLibrary.sol +++ b/src/UsdnProtocol/libraries/UsdnProtocolUtilsLibrary.sol @@ -543,7 +543,7 @@ library UsdnProtocolUtilsLibrary { * @param sdexBurnRatio The ratio of SDEX burned per minted USDN. * @return sdexToBurn_ The amount of SDEX tokens to burn. */ - function _calcSdexToBurn(uint256 usdnAmount, uint32 sdexBurnRatio) internal pure returns (uint256 sdexToBurn_) { + function _calcSdexToBurn(uint256 usdnAmount, uint64 sdexBurnRatio) internal pure returns (uint256 sdexToBurn_) { sdexToBurn_ = FixedPointMathLib.fullMulDivUp(usdnAmount, sdexBurnRatio, Constants.SDEX_BURN_ON_DEPOSIT_DIVISOR); } diff --git a/src/UsdnProtocol/libraries/UsdnProtocolVaultLibrary.sol b/src/UsdnProtocol/libraries/UsdnProtocolVaultLibrary.sol index 70bd8c6c5..4b96ea959 100644 --- a/src/UsdnProtocol/libraries/UsdnProtocolVaultLibrary.sol +++ b/src/UsdnProtocol/libraries/UsdnProtocolVaultLibrary.sol @@ -604,7 +604,7 @@ library UsdnProtocolVaultLibrary { if (usdnToMintEstimated == 0) { revert IUsdnProtocolErrors.UsdnProtocolDepositTooSmall(); } - uint32 burnRatio = s._sdexBurnOnDepositRatio; + uint64 burnRatio = s._sdexBurnOnDepositRatio; data_.sdexToBurn = Utils._calcSdexToBurn(usdnToMintEstimated, burnRatio); } diff --git a/src/interfaces/IWstETH.sol b/src/interfaces/IWstETH.sol index 1c8310542..cef821c75 100644 --- a/src/interfaces/IWstETH.sol +++ b/src/interfaces/IWstETH.sol @@ -12,7 +12,7 @@ interface IWstETH is IERC20Metadata, IERC20Permit { * - `_stETHAmount` must be non-zero * - msg.sender must approve at least `_stETHAmount` stETH to this contract * - msg.sender must have at least `_stETHAmount` of stETH - * User should first approve `_stETHAmount` to the WstETH contract + * User should first approve `_stETHAmount` to the wstETH contract * @return Amount of wstETH user receives after wrap */ function wrap(uint256 _stETHAmount) external returns (uint256); diff --git a/src/interfaces/OracleMiddleware/IChainlinkDataStreamsOracle.sol b/src/interfaces/OracleMiddleware/IChainlinkDataStreamsOracle.sol new file mode 100644 index 000000000..b2390b420 --- /dev/null +++ b/src/interfaces/OracleMiddleware/IChainlinkDataStreamsOracle.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.0; + +import { IVerifierProxy } from "./IVerifierProxy.sol"; + +interface IChainlinkDataStreamsOracle { + /** + * @notice Gets the Chainlink Proxy verifier contract. + * @return proxyVerifier_ The address of the proxy verifier contract. + */ + function getProxyVerifier() external view returns (IVerifierProxy proxyVerifier_); + + /** + * @notice Gets the supported Chainlink data stream ID. + * @return streamId_ The unique identifier for the Chainlink data streams. + */ + function getStreamId() external view returns (bytes32 streamId_); + + /** + * @notice Gets the maximum age of a recent price to be considered valid. + * @return delay_ The maximum acceptable age of a recent price in seconds. + */ + function getDataStreamRecentPriceDelay() external view returns (uint256 delay_); + + /** + * @notice Gets the supported Chainlink data streams report version. + * @return version_ The version number of the supported Chainlink data streams report. + */ + function getReportVersion() external pure returns (uint256 version_); +} diff --git a/src/interfaces/OracleMiddleware/IOracleMiddleware.sol b/src/interfaces/OracleMiddleware/ICommonOracleMiddleware.sol similarity index 57% rename from src/interfaces/OracleMiddleware/IOracleMiddleware.sol rename to src/interfaces/OracleMiddleware/ICommonOracleMiddleware.sol index 9efa7b699..2b58e7ade 100644 --- a/src/interfaces/OracleMiddleware/IOracleMiddleware.sol +++ b/src/interfaces/OracleMiddleware/ICommonOracleMiddleware.sol @@ -6,19 +6,11 @@ import { IAccessControlDefaultAdminRules } from import { IUsdnProtocol } from "../UsdnProtocol/IUsdnProtocol.sol"; import { IBaseOracleMiddleware } from "./IBaseOracleMiddleware.sol"; -import { IChainlinkOracle } from "./IChainlinkOracle.sol"; import { IOracleMiddlewareErrors } from "./IOracleMiddlewareErrors.sol"; import { IOracleMiddlewareEvents } from "./IOracleMiddlewareEvents.sol"; -import { IPythOracle } from "./IPythOracle.sol"; -/** - * @notice The oracle middleware is a contract that is used by the USDN protocol to validate price data. - * Using a middleware allows the protocol to later upgrade to a new oracle logic without having to modify - * the protocol's contracts. - */ -interface IOracleMiddleware is - IChainlinkOracle, - IPythOracle, +/// @notice This is the common middleware interface for all current middlewares. +interface ICommonOracleMiddleware is IBaseOracleMiddleware, IOracleMiddlewareErrors, IOracleMiddlewareEvents, @@ -34,45 +26,10 @@ interface IOracleMiddleware is */ function ADMIN_ROLE() external pure returns (bytes32 role_); - /* -------------------------------------------------------------------------- */ - /* Constants */ - /* -------------------------------------------------------------------------- */ - - /** - * @notice Gets the denominator for the variables using basis points as a unit. - * @return denominator_ The BPS divisor. - */ - function BPS_DIVISOR() external pure returns (uint16 denominator_); - - /** - * @notice Gets the maximum value for `_confRatioBps`. - * @return ratio_ The max allowed confidence ratio. - */ - function MAX_CONF_RATIO() external pure returns (uint16 ratio_); - - /* -------------------------------------------------------------------------- */ - /* Generic features */ - /* -------------------------------------------------------------------------- */ - - /** - * @notice Gets the confidence ratio. - * @dev This ratio is used to apply a specific portion of the confidence interval provided by an oracle, which is - * used to adjust the precision of predictions or estimations. - * @return ratio_ The confidence ratio (in basis points). - */ - function getConfRatioBps() external view returns (uint16 ratio_); - /* -------------------------------------------------------------------------- */ /* Owner features */ /* -------------------------------------------------------------------------- */ - /** - * @notice Sets the confidence ratio. - * @dev The new value should be lower than {MAX_CONF_RATIO}. - * @param newConfRatio the new confidence ratio. - */ - function setConfRatio(uint16 newConfRatio) external; - /** * @notice Sets the elapsed time tolerated before we consider the price from Chainlink invalid. * @param newTimeElapsedLimit The new time elapsed limit. diff --git a/src/interfaces/OracleMiddleware/IFeeManager.sol b/src/interfaces/OracleMiddleware/IFeeManager.sol new file mode 100644 index 000000000..82e318a2c --- /dev/null +++ b/src/interfaces/OracleMiddleware/IFeeManager.sol @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.0; + +import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; + +interface IFeeManager is IERC165 { + /** + * @notice The Asset struct to hold the address of an asset and its amount. + * @param assetAddress The address of the asset. + * @param amount The amount of the asset. + */ + struct Asset { + address assetAddress; + uint256 amount; + } + + /** + * @notice Gets the subscriber discount for a specific feedId. + * @param subscriber The address of the subscriber to check for a discount. + * @param feedId The feedId related to the discount. + * @param token The address of the quote payment token. + * @return The current subscriber discount. + */ + function s_subscriberDiscounts(address subscriber, bytes32 feedId, address token) external view returns (uint256); + + /** + * @notice Gets any subsidized LINK that is owed to the reward manager for a specific feedId. + * @param feedId The feedId related to the link deficit. + * @return The amount of link deficit. + */ + function s_linkDeficit(bytes32 feedId) external view returns (uint256); + + /** + * @notice Gets the LINK token address. + * @return The address of the LINK token. + */ + function i_linkAddress() external view returns (address); + + /** + * @notice Gets the native token address. + * @return The address of the native token. + */ + function i_nativeAddress() external view returns (address); + + /** + * @notice Gets the proxy contract address. + * @return The address of the proxy contract. + */ + function i_proxyAddress() external view returns (address); + + /** + * @notice Gets the surcharge fee to be paid if paying in native. + * @return The surcharge fee for native payments. + */ + function s_nativeSurcharge() external view returns (uint256); + + /** + * @notice Calculates the applied fee and reward from a report. If the sender is a subscriber, they will receive a + * discount. + * @param subscriber The address of the subscriber trying to verify. + * @param report The report to calculate the fee for. + * @param quoteAddress The address of the quote payment token. + * @return feeData The calculated fee data. + * @return rewardData The calculated reward data. + * @return discount The current subscriber discount applied. + */ + function getFeeAndReward(address subscriber, bytes memory report, address quoteAddress) + external + view + returns (Asset memory feeData, Asset memory rewardData, uint256 discount); +} diff --git a/src/interfaces/OracleMiddleware/IOracleMiddlewareErrors.sol b/src/interfaces/OracleMiddleware/IOracleMiddlewareErrors.sol index a0b352eaa..021baad6a 100644 --- a/src/interfaces/OracleMiddleware/IOracleMiddlewareErrors.sol +++ b/src/interfaces/OracleMiddleware/IOracleMiddlewareErrors.sol @@ -3,7 +3,7 @@ pragma solidity >=0.8.0; /** * @title Errors For The Middleware And Oracle Related Contracts - * @notice Defines all the custom errors thrown by the contracts related to the OracleMiddleware contract. + * @notice Defines all the custom errors thrown by the contracts related to the OracleMiddleware contracts. */ interface IOracleMiddlewareErrors { /** @@ -12,6 +12,18 @@ interface IOracleMiddlewareErrors { */ error OracleMiddlewareWrongPrice(int256 price); + /** + * @notice The ask price returned by Chainlink data streams is negative. + * @param askPrice The ask price returned by Chainlink data streams. + */ + error OracleMiddlewareWrongAskPrice(int256 askPrice); + + /** + * @notice The bid price returned by Chainlink data streams is negative. + * @param bidPrice The bid price returned by Chainlink data streams. + */ + error OracleMiddlewareWrongBidPrice(int256 bidPrice); + /** * @notice The price returned by an oracle is too old. * @param timestamp The timestamp of the price given by the oracle. @@ -30,6 +42,9 @@ interface IOracleMiddlewareErrors { */ error OracleMiddlewarePythPositiveExponent(int32 expo); + /// @notice Indicates that the confidence value is higher than the price. + error OracleMiddlewareConfValueTooHigh(); + /// @notice Indicates that the confidence ratio is too high. error OracleMiddlewareConfRatioTooHigh(); @@ -68,4 +83,19 @@ interface IOracleMiddlewareErrors { /// @notice The new low latency delay is invalid. error OracleMiddlewareInvalidLowLatencyDelay(); + + /// @notice The Chainlink data streams report version is invalid. + error OracleMiddlewareInvalidReportVersion(); + + /// @notice The Chainlink report stream ID is invalid. + error OracleMiddlewareInvalidStreamId(); + + /// @notice The Chainlink data streams report timestamp is invalid. + error OracleMiddlewareDataStreamInvalidTimestamp(); + + /** + * @notice The validation fee returned by the Chainlink fee manager contract exceeded the safeguard value. + * @param fee The required fee returned by the Chainlink fee manager. + */ + error OracleMiddlewareDataStreamFeeSafeguard(uint256 fee); } diff --git a/src/interfaces/OracleMiddleware/IOracleMiddlewareEvents.sol b/src/interfaces/OracleMiddleware/IOracleMiddlewareEvents.sol index b151a0c6e..7ca675d96 100644 --- a/src/interfaces/OracleMiddleware/IOracleMiddlewareEvents.sol +++ b/src/interfaces/OracleMiddleware/IOracleMiddlewareEvents.sol @@ -3,7 +3,7 @@ pragma solidity >=0.8.0; /** * @title Events For The Middleware And Oracle Related Contracts - * @notice Defines all the custom events emitted by the contracts related to the OracleMiddleware contract. + * @notice Defines all the custom events emitted by the contracts related to the OracleMiddleware contracts. */ interface IOracleMiddlewareEvents { /** @@ -24,6 +24,12 @@ interface IOracleMiddlewareEvents { */ event PythRecentPriceDelayUpdated(uint64 newDelay); + /** + * @notice The recent price delay for Chainlink data streams was updated. + * @param newDelay The new recent price delay. + */ + event DataStreamsRecentPriceDelayUpdated(uint64 newDelay); + /** * @notice The recent price delay for Redstone was updated. * @param newDelay The new recent price delay. diff --git a/src/interfaces/OracleMiddleware/IOracleMiddlewareTypes.sol b/src/interfaces/OracleMiddleware/IOracleMiddlewareTypes.sol index e65928eaa..1f0b489c2 100644 --- a/src/interfaces/OracleMiddleware/IOracleMiddlewareTypes.sol +++ b/src/interfaces/OracleMiddleware/IOracleMiddlewareTypes.sol @@ -46,14 +46,28 @@ struct RedstonePriceInfo { } /** - * @notice The different confidence interval of a Pyth price. - * @dev Applied to the neutral price and available as `price`. - * @param Up Adjusted price at the upper bound of the confidence interval. - * @param Down Adjusted price at the lower bound of the confidence interval. - * @param None Neutral price without adjustment. + * @notice Represents the options for the low latency price adjustment. + * @dev Used to determine how the price is adjusted based on protocol action. + * @param Up Price adjusted to the upper bound. + * @param Down Price adjusted to the lower bound. + * @param None Neutral price without any adjustments. */ -enum ConfidenceInterval { +enum PriceAdjustment { Up, Down, None } + +/** + * @notice Representation of data streams asset price with a uint256 price. + * @param timestamp The timestamp of the asset price. + * @param price The price of the asset with 18 decimals. + * @param bid The simulated price impact of a buy order. + * @param ask The simulated price impact of a sell order. + */ +struct FormattedDataStreamsPrice { + uint256 timestamp; + uint256 price; + uint256 ask; + uint256 bid; +} diff --git a/src/interfaces/OracleMiddleware/IOracleMiddlewareWithDataStreams.sol b/src/interfaces/OracleMiddleware/IOracleMiddlewareWithDataStreams.sol new file mode 100644 index 000000000..924961ddf --- /dev/null +++ b/src/interfaces/OracleMiddleware/IOracleMiddlewareWithDataStreams.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.0; + +import { IChainlinkDataStreamsOracle } from "./IChainlinkDataStreamsOracle.sol"; +import { ICommonOracleMiddleware } from "./ICommonOracleMiddleware.sol"; + +/** + * @notice The oracle middleware is a contract that is used by the USDN protocol to validate price data. + * Using a middleware allows the protocol to later upgrade to a new oracle logic without having to modify + * the protocol's contracts. + * @dev This middleware uses Chainlink Data Streams and Pyth as the low-latency oracle, and Chainlink Data Feeds as + * fallback. For liquidations, either Pyth or Data Streams can be used. For validations, only Data Streams is accepted. + */ +interface IOracleMiddlewareWithDataStreams is ICommonOracleMiddleware, IChainlinkDataStreamsOracle { + /* -------------------------------------------------------------------------- */ + /* Owner features */ + /* -------------------------------------------------------------------------- */ + + /** + * @notice Sets the amount of time after which we do not consider a price as recent for Chainlink. + * @param newDelay The maximum age of a price to be considered recent. + */ + function setDataStreamsRecentPriceDelay(uint64 newDelay) external; +} diff --git a/src/interfaces/OracleMiddleware/IOracleMiddlewareWithPyth.sol b/src/interfaces/OracleMiddleware/IOracleMiddlewareWithPyth.sol new file mode 100644 index 000000000..8ba1fddae --- /dev/null +++ b/src/interfaces/OracleMiddleware/IOracleMiddlewareWithPyth.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.0; + +import { ICommonOracleMiddleware } from "./ICommonOracleMiddleware.sol"; + +/** + * @notice The oracle middleware is a contract that is used by the USDN protocol to validate price data. + * Using a middleware allows the protocol to later upgrade to a new oracle logic without having to modify + * the protocol's contracts. + * @dev This middleware uses Pyth as low-latency oracle and Chainlink Data Feeds as fallback. + */ +interface IOracleMiddlewareWithPyth is ICommonOracleMiddleware { + /* -------------------------------------------------------------------------- */ + /* Constants */ + /* -------------------------------------------------------------------------- */ + + /** + * @notice Gets the denominator for the variables using basis points as a unit. + * @return denominator_ The BPS divisor. + */ + function BPS_DIVISOR() external pure returns (uint16 denominator_); + + /** + * @notice Gets the maximum value for `_confRatioBps`. + * @return ratio_ The max allowed confidence ratio. + */ + function MAX_CONF_RATIO() external pure returns (uint16 ratio_); + + /* -------------------------------------------------------------------------- */ + /* Generic features */ + /* -------------------------------------------------------------------------- */ + + /** + * @notice Gets the confidence ratio. + * @dev This ratio is used to apply a specific portion of the confidence interval provided by an oracle, which is + * used to adjust the precision of predictions or estimations. + * @return ratio_ The confidence ratio (in basis points). + */ + function getConfRatioBps() external view returns (uint16 ratio_); + + /* -------------------------------------------------------------------------- */ + /* Owner features */ + /* -------------------------------------------------------------------------- */ + + /** + * @notice Sets the confidence ratio. + * @dev The new value should be lower than {MAX_CONF_RATIO}. + * @param newConfRatio the new confidence ratio. + */ + function setConfRatio(uint16 newConfRatio) external; +} diff --git a/src/interfaces/OracleMiddleware/IOracleMiddlewareWithRedstone.sol b/src/interfaces/OracleMiddleware/IOracleMiddlewareWithRedstone.sol index a4268cafd..0e164d21c 100644 --- a/src/interfaces/OracleMiddleware/IOracleMiddlewareWithRedstone.sol +++ b/src/interfaces/OracleMiddleware/IOracleMiddlewareWithRedstone.sol @@ -1,13 +1,13 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.8.0; -import { IOracleMiddleware } from "./IOracleMiddleware.sol"; +import { IOracleMiddlewareWithPyth } from "./IOracleMiddlewareWithPyth.sol"; /** * @title Oracle Middleware interface * @notice Same as the default oracle middleware, with added support for Redstone */ -interface IOracleMiddlewareWithRedstone is IOracleMiddleware { +interface IOracleMiddlewareWithRedstone is IOracleMiddlewareWithPyth { /* -------------------------------------------------------------------------- */ /* Generic features */ /* -------------------------------------------------------------------------- */ diff --git a/src/interfaces/OracleMiddleware/IPythOracle.sol b/src/interfaces/OracleMiddleware/IPythOracle.sol index 553b580e2..be13fbb4e 100644 --- a/src/interfaces/OracleMiddleware/IPythOracle.sol +++ b/src/interfaces/OracleMiddleware/IPythOracle.sol @@ -3,7 +3,9 @@ pragma solidity >=0.8.0; import { IPyth } from "@pythnetwork/pyth-sdk-solidity/IPyth.sol"; -interface IPythOracle { +import { IOracleMiddlewareErrors } from "../../interfaces/OracleMiddleware/IOracleMiddlewareErrors.sol"; + +interface IPythOracle is IOracleMiddlewareErrors { /** * @notice Gets the Pyth contract address. * @return pyth_ The Pyth contract address. diff --git a/src/interfaces/OracleMiddleware/IVerifierProxy.sol b/src/interfaces/OracleMiddleware/IVerifierProxy.sol new file mode 100644 index 000000000..f090c8be6 --- /dev/null +++ b/src/interfaces/OracleMiddleware/IVerifierProxy.sol @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.0; + +import { IFeeManager } from "./IFeeManager.sol"; + +interface IVerifierProxy { + /** + * @notice Represents a data report from a Data Streams for v3 schema (crypto streams). + * @dev The `price`, `bid`, and `ask` values are carried to either 8 or 18 decimal places, depending on the streams. + * For more information, see https://docs.chain.link/data-streams/crypto-streams and + * https://docs.chain.link/data-streams/reference/report-schema. + * @param feedId The stream ID the report has data for. + * @param validFromTimestamp The earliest timestamp for which price is applicable. + * @param observationsTimestamp The latest timestamp for which price is applicable. + * @param nativeFee The base cost to validate a transaction using the report, + * denominated in the chain’s native token (e.g., WETH/ETH). + * @param linkFee The base cost to validate a transaction using the report, denominated in LINK. + * @param expiresAt The latest timestamp where the report can be verified onchain. + * @param price The DON consensus median price (8 or 18 decimals). + * @param bid The simulated price impact of a buy order up to the X% depth of liquidity usage (8 or 18 decimals). + * @param ask The simulated price impact of a sell order up to the X% depth of liquidity usage (8 or 18 decimals). + */ + struct ReportV3 { + bytes32 feedId; + uint32 validFromTimestamp; + uint32 observationsTimestamp; + uint192 nativeFee; + uint192 linkFee; + uint32 expiresAt; + int192 price; + int192 bid; + int192 ask; + } + + /** + * @notice Gets the fee manager contract. + * @return feeManager_ The fee manager contract. + */ + function s_feeManager() external view returns (IFeeManager feeManager_); + + /** + * @notice Verifies that the data encoded has been signed + * correctly by routing to the correct verifier, and bills the user if applicable. + * @param payload The encoded data to be verified, including the signed + * report. + * @param parameterPayload The fee metadata for billing. + * @return verifierResponse The encoded report from the verifier. + */ + function verify(bytes calldata payload, bytes calldata parameterPayload) + external + payable + returns (bytes memory verifierResponse); + + /** + * @notice Bulk verifies that the data encoded has been signed + * correctly by routing to the correct verifier, and bills the user if applicable. + * @param payloads The encoded payloads to be verified, including the signed + * report. + * @param parameterPayload The fee metadata for billing. + * @return verifiedReports The encoded reports from the verifier. + */ + function verifyBulk(bytes[] calldata payloads, bytes calldata parameterPayload) + external + payable + returns (bytes[] memory verifiedReports); + + /** + * @notice Retrieves the verifier address that verifies reports + * for a config digest. + * @param configDigest The config digest to query for. + * @return verifierAddress The address of the verifier contract that verifies + * reports for a given config digest. + */ + function getVerifier(bytes32 configDigest) external view returns (address verifierAddress); +} diff --git a/src/interfaces/UsdnProtocol/IUsdnProtocol.sol b/src/interfaces/UsdnProtocol/IUsdnProtocol.sol index 7204ece9e..c5719e6f3 100644 --- a/src/interfaces/UsdnProtocol/IUsdnProtocol.sol +++ b/src/interfaces/UsdnProtocol/IUsdnProtocol.sol @@ -1,6 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.8.0; +import { IUsdnProtocolErrors } from "./IUsdnProtocolErrors.sol"; +import { IUsdnProtocolEvents } from "./IUsdnProtocolEvents.sol"; import { IUsdnProtocolFallback } from "./IUsdnProtocolFallback.sol"; import { IUsdnProtocolImpl } from "./IUsdnProtocolImpl.sol"; @@ -8,7 +10,7 @@ import { IUsdnProtocolImpl } from "./IUsdnProtocolImpl.sol"; * @title IUsdnProtocol * @notice Interface for the USDN protocol and fallback. */ -interface IUsdnProtocol is IUsdnProtocolImpl, IUsdnProtocolFallback { +interface IUsdnProtocol is IUsdnProtocolImpl, IUsdnProtocolFallback, IUsdnProtocolErrors, IUsdnProtocolEvents { /** * @notice Upgrades the protocol to a new implementation (check * [UUPSUpgradeable](https://docs.openzeppelin.com/contracts/5.x/api/proxy#UUPSUpgradeable)). diff --git a/src/interfaces/UsdnProtocol/IUsdnProtocolFallback.sol b/src/interfaces/UsdnProtocol/IUsdnProtocolFallback.sol index 6106086cb..27d9f0348 100644 --- a/src/interfaces/UsdnProtocol/IUsdnProtocolFallback.sol +++ b/src/interfaces/UsdnProtocol/IUsdnProtocolFallback.sol @@ -336,7 +336,7 @@ interface IUsdnProtocolFallback is IUsdnProtocolTypes { * @notice Gets the ratio of SDEX tokens to burn per minted USDN. * @return ratio_ The ratio (to be divided by SDEX_BURN_ON_DEPOSIT_DIVISOR). */ - function getSdexBurnOnDepositRatio() external view returns (uint32 ratio_); + function getSdexBurnOnDepositRatio() external view returns (uint64 ratio_); /** * @notice Gets the amount of native tokens used as security deposit when opening a new position. @@ -586,6 +586,9 @@ interface IUsdnProtocolFallback is IUsdnProtocolTypes { /** * @notice Sets the minimum long position size. * @dev This value is used to prevent users from opening positions that are too small and not worth liquidating. + * As this parameter highly depends on the value of the underlying asset, the max value for `newMinLongPosition` is + * given as a constructor argument when the {UsdnProtocolFallback} contract is deployed, and is stored in an + * immutable variable. * @param newMinLongPosition The new minimum long position size (with `_assetDecimals`). */ function setMinLongPosition(uint256 newMinLongPosition) external; @@ -653,9 +656,11 @@ interface IUsdnProtocolFallback is IUsdnProtocolTypes { /** * @notice Sets the ratio of SDEX tokens to burn per minted USDN. + * @dev As this parameter highly depends on the value of the USDN token, the max value for `newRatio` is given as a + * constructor argument when the {UsdnProtocolFallback} contract is deployed and is stored in an immutable variable. * @param newRatio The new ratio. */ - function setSdexBurnOnDepositRatio(uint32 newRatio) external; + function setSdexBurnOnDepositRatio(uint64 newRatio) external; /** * @notice Sets the security deposit value. diff --git a/src/interfaces/UsdnProtocol/IUsdnProtocolImpl.sol b/src/interfaces/UsdnProtocol/IUsdnProtocolImpl.sol index 5bc7f0c09..cec75d1a5 100644 --- a/src/interfaces/UsdnProtocol/IUsdnProtocolImpl.sol +++ b/src/interfaces/UsdnProtocol/IUsdnProtocolImpl.sol @@ -4,11 +4,7 @@ pragma solidity >=0.8.0; import { IAccessControlDefaultAdminRules } from "@openzeppelin/contracts/access/extensions/IAccessControlDefaultAdminRules.sol"; import { IERC5267 } from "@openzeppelin/contracts/interfaces/IERC5267.sol"; -import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; -import { IBaseLiquidationRewardsManager } from "../LiquidationRewardsManager/IBaseLiquidationRewardsManager.sol"; -import { IBaseOracleMiddleware } from "../OracleMiddleware/IBaseOracleMiddleware.sol"; -import { IUsdn } from "../Usdn/IUsdn.sol"; import { IUsdnProtocolActions } from "./IUsdnProtocolActions.sol"; import { IUsdnProtocolCore } from "./IUsdnProtocolCore.sol"; import { IUsdnProtocolFallback } from "./IUsdnProtocolFallback.sol"; diff --git a/src/interfaces/UsdnProtocol/IUsdnProtocolTypes.sol b/src/interfaces/UsdnProtocol/IUsdnProtocolTypes.sol index c6c1179d1..2c26af063 100644 --- a/src/interfaces/UsdnProtocol/IUsdnProtocolTypes.sol +++ b/src/interfaces/UsdnProtocol/IUsdnProtocolTypes.sol @@ -593,7 +593,7 @@ interface IUsdnProtocolTypes { * @param _positionFeeBps The position fee in basis points. * @param _vaultFeeBps The fee for vault deposits and withdrawals, in basis points. * @param _sdexRewardsRatioBps The ratio of SDEX rewards to send to the user (in basis points). - * @param _sdexBurnOnDepositRatio The ratio of USDN to SDEX tokens to burn on deposit. + * @param __unused Old slot for `_sdexBurnOnDepositRatio`. * @param _feeCollector The fee collector's address. * @param _securityDepositValue The deposit required for a new position. * @param _targetUsdnPrice The nominal (target) price of USDN (with _priceFeedDecimals). @@ -624,6 +624,7 @@ interface IUsdnProtocolTypes { * @param _tickBitmap The bitmap used to quickly find populated ticks. * @param _protocolFallbackAddr The address of the fallback contract. * @param _nonce The user EIP712 nonce. + * @param _sdexBurnOnDepositRatio The ratio of USDN to SDEX tokens to burn on deposit. * @param _positionLiquidated Flags when positions have been liquidated. * @param _liquidationPending Tracks if there are positions pending liquidation. * @param _positionWasLiquidatedInTheMeantime Flags when a position was liquidated while pending. @@ -670,7 +671,7 @@ interface IUsdnProtocolTypes { uint16 _positionFeeBps; uint16 _vaultFeeBps; uint16 _sdexRewardsRatioBps; - uint32 _sdexBurnOnDepositRatio; + uint32 __unused; address _feeCollector; uint64 _securityDepositValue; uint128 _targetUsdnPrice; @@ -702,6 +703,7 @@ interface IUsdnProtocolTypes { address _protocolFallbackAddr; // EIP712 mapping(address => uint256) _nonce; + uint64 _sdexBurnOnDepositRatio; /* * * FUZZING SUITE STATE CHECK VARS @@ -788,7 +790,7 @@ interface IUsdnProtocolTypes { uint16 positionFeeBps; uint16 vaultFeeBps; uint16 sdexRewardsRatioBps; - uint32 sdexBurnOnDepositRatio; + uint64 sdexBurnOnDepositRatio; address feeCollector; uint64 securityDepositValue; uint128 targetUsdnPrice; diff --git a/src/interfaces/Utils/IAutoSwapperWstethSdex.sol b/src/interfaces/Utils/IAutoSwapperWstethSdex.sol new file mode 100644 index 000000000..03fdfe065 --- /dev/null +++ b/src/interfaces/Utils/IAutoSwapperWstethSdex.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity >=0.8.0; + +/// @notice Interface for the AutoSwapperWstethSdex contract that provides automated token swapping functionality. +interface IAutoSwapperWstethSdex { + /// @notice Emitted when a swap fails. + event FailedSwap(); + + /** + * @notice Emitted when the swap slippage percentage is updated. + * @param newSwapSlippage The new swap slippage (in basis points). + */ + event SwapSlippageUpdated(uint256 newSwapSlippage); + + /// @notice Thrown when a swap fails. + error AutoSwapperSwapFailed(); + + /// @notice Thrown when slippage configuration is invalid. + error AutoSwapperInvalidSwapSlippage(); + + /// @notice Thrown when the caller is not authorized to perform the operation. + error AutoSwapperInvalidCaller(); + + /** + * @notice Swap wstETH to SDEX. + * @dev This function can only be called by the contract itself. + */ + function swapWstethToSdex() external; + + /** + * @notice Admin function to send the contract token balance to a specified address. + * @param token The address of the token to send. + * @param to The recipient address. + * @param amount The amount of tokens to send. + */ + function sweep(address token, address to, uint256 amount) external; + + /** + * @notice Updates the allowed slippage percentage for swaps. + * @param swapSlippage The new slippage value (in basis points). + */ + function updateSwapSlippage(uint256 swapSlippage) external; +} diff --git a/src/utils/AutoSwapperWstethSdex.sol b/src/utils/AutoSwapperWstethSdex.sol new file mode 100644 index 000000000..29af24111 --- /dev/null +++ b/src/utils/AutoSwapperWstethSdex.sol @@ -0,0 +1,174 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.26; + +import { Ownable, Ownable2Step } from "@openzeppelin/contracts/access/Ownable2Step.sol"; +import { IERC20, SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import { ERC165 } from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; +import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; +import { ISmardexPair } from "@smardex-dex-contracts/contracts/ethereum/core/v2/interfaces/ISmardexPair.sol"; +import { ISmardexSwapCallback } from + "@smardex-dex-contracts/contracts/ethereum/core/v2/interfaces/ISmardexSwapCallback.sol"; +import { SmardexLibrary } from "@smardex-dex-contracts/contracts/ethereum/core/v2/libraries/SmardexLibrary.sol"; +import { IUniswapV3Pool } from "@uniswapV3/contracts/interfaces/IUniswapV3Pool.sol"; +import { IUniswapV3SwapCallback } from "@uniswapV3/contracts/interfaces/callback/IUniswapV3SwapCallback.sol"; + +import { IWstETH } from "./../interfaces/IWstETH.sol"; +import { IFeeCollectorCallback } from "./../interfaces/UsdnProtocol/IFeeCollectorCallback.sol"; +import { IAutoSwapperWstethSdex } from "./../interfaces/Utils/IAutoSwapperWstethSdex.sol"; + +/** + * @title SDEX buy-back and burn Autoswapper + * @notice Automates protocol fee conversion from wstETH to SDEX via Uniswap V3 and Smardex. + */ +contract AutoSwapperWstethSdex is + Ownable2Step, + IAutoSwapperWstethSdex, + IFeeCollectorCallback, + ERC165, + ISmardexSwapCallback, + IUniswapV3SwapCallback +{ + using SafeERC20 for IERC20; + using SafeERC20 for IWstETH; + + /* -------------------------------------------------------------------------- */ + /* Constants */ + /* -------------------------------------------------------------------------- */ + + /// @notice Decimal points for basis points (bps). + uint16 internal constant BPS_DIVISOR = 10_000; + + /// @notice Wrapped staked ETH token address. + IWstETH internal constant WSTETH = IWstETH(0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0); + + /// @notice Wrapped ETH token address. + IERC20 internal constant WETH = IERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); + + /// @notice SmarDex pair address for WETH/SDEX swaps. + ISmardexPair internal constant SMARDEX_WETH_SDEX_PAIR = ISmardexPair(0xf3a4B8eFe3e3049F6BC71B47ccB7Ce6665420179); + + /// @notice Uniswap V3 pair address for WSTETH/WETH swaps. + IUniswapV3Pool internal constant UNI_WSTETH_WETH_PAIR = IUniswapV3Pool(0x109830a1AAaD605BbF02a9dFA7B0B92EC2FB7dAa); + + /// @notice Allowed slippage for swaps (in basis points). + uint256 internal _swapSlippage = 100; // 1% + + constructor() Ownable(msg.sender) { } + + /// @inheritdoc IFeeCollectorCallback + function feeCollectorCallback(uint256) external { + try this.swapWstethToSdex() { } + catch { + emit FailedSwap(); + } + } + + /// @inheritdoc IAutoSwapperWstethSdex + function swapWstethToSdex() external { + _uniWstethToWeth(); + _smarDexWethToSdex(); + } + + /// @inheritdoc IUniswapV3SwapCallback + function uniswapV3SwapCallback(int256 amountWstethIn, int256, bytes calldata) external { + if (msg.sender != address(UNI_WSTETH_WETH_PAIR)) { + revert AutoSwapperInvalidCaller(); + } + + WSTETH.safeTransfer(msg.sender, uint256(amountWstethIn)); + } + + /// @inheritdoc ISmardexSwapCallback + function smardexSwapCallback(int256, int256 amountWethIn, bytes calldata) external { + if (msg.sender != address(SMARDEX_WETH_SDEX_PAIR)) { + revert AutoSwapperInvalidCaller(); + } + + WETH.safeTransfer(msg.sender, uint256(amountWethIn)); + } + + /// @inheritdoc IAutoSwapperWstethSdex + function sweep(address token, address to, uint256 amount) external onlyOwner { + IERC20(token).safeTransfer(to, amount); + } + + /// @inheritdoc IAutoSwapperWstethSdex + function updateSwapSlippage(uint256 newSwapSlippage) external onlyOwner { + if (newSwapSlippage == 0) { + revert AutoSwapperInvalidSwapSlippage(); + } + _swapSlippage = newSwapSlippage; + emit SwapSlippageUpdated(newSwapSlippage); + } + + /// @inheritdoc ERC165 + function supportsInterface(bytes4 interfaceId) public view override(ERC165, IERC165) returns (bool) { + if (interfaceId == type(IFeeCollectorCallback).interfaceId) { + return true; + } + + return super.supportsInterface(interfaceId); + } + + /// @notice Swaps wstETH for WETH on Uniswap V3. + function _uniWstethToWeth() internal { + uint256 wstEthAmount = WSTETH.balanceOf(address(this)); + + (, int256 amountWethOut) = + // 4_295_128_740 is equivalent to getSqrtRatioAtTick(MIN_TICK) -> unlimited slippage. + UNI_WSTETH_WETH_PAIR.swap(address(this), true, int256(wstEthAmount), 4_295_128_740, ""); + + uint256 minWethAmount = WSTETH.getStETHByWstETH(wstEthAmount) * (BPS_DIVISOR - _swapSlippage) / BPS_DIVISOR; + + if (uint256(-amountWethOut) < minWethAmount) { + revert AutoSwapperSwapFailed(); + } + } + + /// @notice Swaps WETH for SDEX token using the SmarDex protocol. + function _smarDexWethToSdex() internal { + uint256 wethAmount = WETH.balanceOf(address(this)); + + uint256 newPriceAvWeth; + uint256 newPriceAvSdex; + (uint256 fictiveReserveSdex, uint256 fictiveReserveWeth) = SMARDEX_WETH_SDEX_PAIR.getFictiveReserves(); + { + (uint256 oldPriceAvSdex, uint256 oldPriceAvWeth, uint256 oldPriceAvTimestamp) = + SMARDEX_WETH_SDEX_PAIR.getPriceAverage(); + + (newPriceAvWeth, newPriceAvSdex) = SmardexLibrary.getUpdatedPriceAverage( + fictiveReserveWeth, + fictiveReserveSdex, + oldPriceAvTimestamp, + oldPriceAvWeth, + oldPriceAvSdex, + block.timestamp + ); + } + + (uint256 reservesSdex, uint256 reservesWeth) = SMARDEX_WETH_SDEX_PAIR.getReserves(); + (uint256 avAmountSdexOut,,,,) = SmardexLibrary.getAmountOut( + SmardexLibrary.GetAmountParameters({ + amount: wethAmount, + reserveIn: reservesWeth, + reserveOut: reservesSdex, + fictiveReserveIn: fictiveReserveWeth, + fictiveReserveOut: fictiveReserveSdex, + priceAverageIn: newPriceAvWeth, + priceAverageOut: newPriceAvSdex, + // SmarDex LP fee for v1 pools + feesLP: 500, + // SmarDex pool fee for v1 pools. + feesPool: 200 + }) + ); + + uint256 minSdexAmount = avAmountSdexOut * (BPS_DIVISOR - _swapSlippage) / BPS_DIVISOR; + + (int256 amountSdexOut,) = SMARDEX_WETH_SDEX_PAIR.swap(address(0xdead), false, int256(wethAmount), ""); + + if (uint256(-amountSdexOut) < minSdexAmount) { + revert AutoSwapperSwapFailed(); + } + } +} diff --git a/src/utils/InitializableReentrancyGuard.sol b/src/utils/InitializableReentrancyGuard.sol index 251c6498b..d6b1ac212 100644 --- a/src/utils/InitializableReentrancyGuard.sol +++ b/src/utils/InitializableReentrancyGuard.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.26; +pragma solidity ^0.8.0; /** * @title Reentrancy Guard with Initializer Check diff --git a/test/fuzzing/FuzzGuided.sol b/test/fuzzing/FuzzGuided.sol index a91372692..c2812d738 100755 --- a/test/fuzzing/FuzzGuided.sol +++ b/test/fuzzing/FuzzGuided.sol @@ -10,7 +10,7 @@ import { FuzzRebalancer } from "./FuzzRebalancer.sol"; import { FuzzUsdnProtocolActions } from "./FuzzUsdnProtocolActions.sol"; import { FuzzUsdnProtocolVault } from "./FuzzUsdnProtocolVault.sol"; -import { IUsdnProtocolTypes as Types } from "../../../src/interfaces/UsdnProtocol/IUsdnProtocolTypes.sol"; +import { IUsdnProtocolTypes as Types } from "../../src/interfaces/UsdnProtocol/IUsdnProtocolTypes.sol"; import { SignedMath } from "../../src/libraries/SignedMath.sol"; import { TickMath } from "../../src/libraries/TickMath.sol"; diff --git a/test/fuzzing/FuzzSetup.sol b/test/fuzzing/FuzzSetup.sol index 9c4b96271..b01b11adb 100755 --- a/test/fuzzing/FuzzSetup.sol +++ b/test/fuzzing/FuzzSetup.sol @@ -8,25 +8,24 @@ import { UsdnHandler } from "../unit/USDN/utils/Handler.sol"; import { DefaultConfig } from "../utils/DefaultConfig.sol"; import { Sdex } from "../utils/Sdex.sol"; import { WstETH } from "../utils/WstEth.sol"; -import { IUsdnProtocolHandler } from "./mocks/interfaces/IUsdnProtocolHandler.sol"; - import { LiquidationRewardsManagerHandler } from "./mocks/LiquidationRewardsManagerHandler.sol"; import { MockPyth } from "./mocks/MockPyth.sol"; import { RebalancerHandler } from "./mocks/RebalancerHandler.sol"; +import { IUsdnProtocolHandler } from "./mocks/interfaces/IUsdnProtocolHandler.sol"; import { FunctionCalls } from "./util/FunctionCalls.sol"; -import { FuzzActors } from "./util/FuzzActors.sol"; -import { WstEthOracleMiddleware } from "../../../src/OracleMiddleware/WstEthOracleMiddleware.sol"; -import { MockWstEthOracleMiddleware } from "../../../src/OracleMiddleware/mock/MockWstEthOracleMiddleware.sol"; -import { Usdn } from "../../../src/Usdn/Usdn.sol"; -import { Wusdn } from "../../../src/Usdn/Wusdn.sol"; -import { UsdnProtocolFallback } from "../../../src/UsdnProtocol/UsdnProtocolFallback.sol"; -import { UsdnProtocolConstantsLibrary as Constants } from - "../../../src/UsdnProtocol/libraries/UsdnProtocolConstantsLibrary.sol"; -import { IWstETH } from "../../../src/interfaces/IWstETH.sol"; -import { FeeCollector } from "../../../src/utils/FeeCollector.sol"; +import { WstEthOracleMiddlewareWithPyth } from "../../src/OracleMiddleware/WstEthOracleMiddlewareWithPyth.sol"; +import { MockWstEthOracleMiddlewareWithPyth } from + "../../src/OracleMiddleware/mock/MockWstEthOracleMiddlewareWithPyth.sol"; +import { Usdn } from "../../src/Usdn/Usdn.sol"; import { Usdn } from "../../src/Usdn/Usdn.sol"; +import { Wusdn } from "../../src/Usdn/Wusdn.sol"; +import { UsdnProtocolFallback } from "../../src/UsdnProtocol/UsdnProtocolFallback.sol"; +import { UsdnProtocolConstantsLibrary as Constants } from + "../../src/UsdnProtocol/libraries/UsdnProtocolConstantsLibrary.sol"; +import { IWstETH } from "../../src/interfaces/IWstETH.sol"; import { SignedMath } from "../../src/libraries/SignedMath.sol"; +import { FeeCollector } from "../../src/utils/FeeCollector.sol"; import { UsdnProtocolHandler } from "./mocks/UsdnProtocolHandler.sol"; /** @@ -67,7 +66,7 @@ contract FuzzSetup is FunctionCalls, DefaultConfig { } function deployOracleMiddleware(address wstETHAddress) internal { - wstEthOracleMiddleware = new MockWstEthOracleMiddleware( + wstEthOracleMiddleware = new MockWstEthOracleMiddlewareWithPyth( address(pyth), PYTH_FEED_ID, address(chainlink), wstETHAddress, CHAINLINK_PRICE_VALIDITY ); @@ -80,13 +79,13 @@ contract FuzzSetup is FunctionCalls, DefaultConfig { function deployProtocol() internal { feeCollector = new FeeCollector(); //NOTE: added fuzzing contract into collector's constructor - usdnProtocolFallback = new UsdnProtocolFallback(); + usdnProtocolFallback = new UsdnProtocolFallback(MAX_SDEX_BURN_RATIO, MAX_MIN_LONG_POSITION); usdnProtocolHandler = new UsdnProtocolHandler(); UsdnProtocolHandler implementation = new UsdnProtocolHandler(); _setPeripheralContracts( - WstEthOracleMiddleware(address(wstEthOracleMiddleware)), + WstEthOracleMiddlewareWithPyth(address(wstEthOracleMiddleware)), liquidationRewardsManager, usdn, wstETH, diff --git a/test/fuzzing/helper/FuzzStorageVariables.sol b/test/fuzzing/helper/FuzzStorageVariables.sol index 12e712439..9ba8c0326 100755 --- a/test/fuzzing/helper/FuzzStorageVariables.sol +++ b/test/fuzzing/helper/FuzzStorageVariables.sol @@ -5,8 +5,8 @@ import { Test } from "forge-std/Test.sol"; import { FuzzBase } from "@perimetersec/fuzzlib/src/FuzzBase.sol"; -import { WstEthOracleMiddleware } from "../../../src/OracleMiddleware/WstEthOracleMiddleware.sol"; -import { MockWstEthOracleMiddleware } from "../../../src/OracleMiddleware/mock/MockWstEthOracleMiddleware.sol"; +import { MockWstEthOracleMiddlewareWithPyth } from + "../../../src/OracleMiddleware/mock/MockWstEthOracleMiddlewareWithPyth.sol"; import { Wusdn } from "../../../src/Usdn/Wusdn.sol"; import { UsdnProtocolFallback } from "../../../src/UsdnProtocol/UsdnProtocolFallback.sol"; import { IFeeCollectorCallback } from "../../../src/interfaces/UsdnProtocol/IFeeCollectorCallback.sol"; @@ -46,7 +46,7 @@ contract FuzzStorageVariables is FuzzConstants, FuzzBase, Test { UsdnProtocolFallback internal usdnProtocolFallback; UsdnProtocolHandler internal usdnProtocolHandler; LiquidationRewardsManagerHandler internal liquidationRewardsManager; - MockWstEthOracleMiddleware internal wstEthOracleMiddleware; + MockWstEthOracleMiddlewareWithPyth internal wstEthOracleMiddleware; Sdex internal sdex; WstETH internal wstETH; diff --git a/test/fuzzing/helper/preconditions/PreconditionsAdmin.sol b/test/fuzzing/helper/preconditions/PreconditionsAdmin.sol index 21d3e9f13..3cc28114e 100755 --- a/test/fuzzing/helper/preconditions/PreconditionsAdmin.sol +++ b/test/fuzzing/helper/preconditions/PreconditionsAdmin.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.26; import { PreconditionsBase } from "./PreconditionsBase.sol"; import { UsdnProtocolConstantsLibrary as Constants } from - "../../../../../src/UsdnProtocol//libraries/UsdnProtocolConstantsLibrary.sol"; + "../../../../src/UsdnProtocol//libraries/UsdnProtocolConstantsLibrary.sol"; import { UsdnProtocolUtilsLibrary as Utils } from "../../../../src/UsdnProtocol/libraries/UsdnProtocolUtilsLibrary.sol"; import { IUsdnProtocolTypes as Types } from "../../../../src/interfaces/UsdnProtocol/IUsdnProtocolTypes.sol"; @@ -142,7 +142,7 @@ abstract contract PreconditionsAdmin is PreconditionsBase { function setSdexBurnOnDepositRatioPreconditions(uint256 seed) internal returns (uint32 ratio) { uint256 minBound = 1e6; - ratio = uint32(bound(seed, minBound, Constants.MAX_SDEX_BURN_RATIO)); + ratio = uint32(bound(seed, minBound, MAX_SDEX_BURN_RATIO)); } function setSecurityDepositValuePreconditions(uint256 seed) internal returns (uint64 securityDeposit) { @@ -291,7 +291,7 @@ abstract contract PreconditionsAdmin is PreconditionsBase { function setMinLongPositionPreconditions(uint256 seed) internal pure returns (uint256 minLongPosition) { uint256 minBound = 2 * 10 ** 18; // 2 tokens - minLongPosition = bound(seed, minBound, Constants.MAX_MIN_LONG_POSITION); + minLongPosition = bound(seed, minBound, MAX_MIN_LONG_POSITION); } function setSafetyMarginBpsPreconditions(uint256 seed) internal pure returns (uint256 safetyMarginBps) { diff --git a/test/fuzzing/mocks/LiquidationRewardsManagerHandler.sol b/test/fuzzing/mocks/LiquidationRewardsManagerHandler.sol index c5be706af..74b482e5c 100644 --- a/test/fuzzing/mocks/LiquidationRewardsManagerHandler.sol +++ b/test/fuzzing/mocks/LiquidationRewardsManagerHandler.sol @@ -3,14 +3,14 @@ pragma solidity 0.8.26; import { Test } from "forge-std/Test.sol"; -import { IWstETH } from "../../../../src/interfaces/IWstETH.sol"; -import { LiquidationRewardsManager } from "../../../src/LiquidationRewardsManager/LiquidationRewardsManager.sol"; -import { UsdnProtocolUtilsLibrary as Utils } from "../../../src/UsdnProtocol/libraries/UsdnProtocolUtilsLibrary.sol"; +import { LiquidationRewardsManagerWstEth } from + "../../../src/LiquidationRewardsManager/LiquidationRewardsManagerWstEth.sol"; +import { IWstETH } from "../../../src/interfaces/IWstETH.sol"; /** * @title LiquidationRewardsManagerHandler * @dev Wrapper to aid in testing the LiquidationRewardsManager */ -contract LiquidationRewardsManagerHandler is LiquidationRewardsManager, Test { - constructor(IWstETH wstETH) LiquidationRewardsManager(wstETH) { } +contract LiquidationRewardsManagerHandler is LiquidationRewardsManagerWstEth, Test { + constructor(IWstETH wstETH) LiquidationRewardsManagerWstEth(wstETH) { } } diff --git a/test/fuzzing/mocks/RebalancerHandler.sol b/test/fuzzing/mocks/RebalancerHandler.sol index ebed49681..96e564771 100644 --- a/test/fuzzing/mocks/RebalancerHandler.sol +++ b/test/fuzzing/mocks/RebalancerHandler.sol @@ -3,12 +3,12 @@ pragma solidity 0.8.26; import { Test, console } from "forge-std/Test.sol"; +import { Rebalancer } from "../../../src/Rebalancer/Rebalancer.sol"; import { UsdnProtocolConstantsLibrary as Constants } from - "../../../../../src/UsdnProtocol//libraries/UsdnProtocolConstantsLibrary.sol"; -import { Rebalancer } from "../../../../src/Rebalancer/Rebalancer.sol"; -import { IUsdnProtocol } from "../../../../src/interfaces/UsdnProtocol/IUsdnProtocol.sol"; -import { IUsdnProtocolTypes as Types } from "../../../../src/interfaces/UsdnProtocol/IUsdnProtocolTypes.sol"; + "../../../src/UsdnProtocol/libraries/UsdnProtocolConstantsLibrary.sol"; import { UsdnProtocolUtilsLibrary as Utils } from "../../../src/UsdnProtocol/libraries/UsdnProtocolUtilsLibrary.sol"; +import { IUsdnProtocol } from "../../../src/interfaces/UsdnProtocol/IUsdnProtocol.sol"; +import { IUsdnProtocolTypes as Types } from "../../../src/interfaces/UsdnProtocol/IUsdnProtocolTypes.sol"; /** * @title RebalancerHandler diff --git a/test/fuzzing/mocks/UsdnProtocolHandler.sol b/test/fuzzing/mocks/UsdnProtocolHandler.sol index fcf7a56c7..c229f3eaa 100755 --- a/test/fuzzing/mocks/UsdnProtocolHandler.sol +++ b/test/fuzzing/mocks/UsdnProtocolHandler.sol @@ -7,13 +7,13 @@ import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; import { HugeUint } from "@smardex-solidity-libraries-1/HugeUint.sol"; import { LibBitmap } from "solady/src/utils/LibBitmap.sol"; -import { UsdnProtocolConstantsLibrary as Constants } from - "../../../../../src/UsdnProtocol//libraries/UsdnProtocolConstantsLibrary.sol"; import { UsdnProtocolImpl } from "../../../src/UsdnProtocol/UsdnProtocolImpl.sol"; import { UsdnProtocolActionsLongLibrary as ActionsLong } from "../../../src/UsdnProtocol/libraries/UsdnProtocolActionsLongLibrary.sol"; import { UsdnProtocolActionsUtilsLibrary as ActionsUtils } from "../../../src/UsdnProtocol/libraries/UsdnProtocolActionsUtilsLibrary.sol"; +import { UsdnProtocolConstantsLibrary as Constants } from + "../../../src/UsdnProtocol/libraries/UsdnProtocolConstantsLibrary.sol"; import { UsdnProtocolCoreLibrary as Core } from "../../../src/UsdnProtocol/libraries/UsdnProtocolCoreLibrary.sol"; import { UsdnProtocolLongLibrary as Long } from "../../../src/UsdnProtocol/libraries/UsdnProtocolLongLibrary.sol"; import { UsdnProtocolSettersLibrary as Setters } from @@ -43,7 +43,7 @@ contract UsdnProtocolHandler is UsdnProtocolImpl, Test { Storage _tempStorage; - function initializeStorageHandler(InitStorage calldata initStorage) external initializer { + function initializeStorageHandler(InitStorage calldata initStorage) external { initializeStorage(initStorage); } diff --git a/test/integration/AutoSwapper/AutoSwapperWstethSdex.t.sol b/test/integration/AutoSwapper/AutoSwapperWstethSdex.t.sol new file mode 100644 index 000000000..b3e86d6d4 --- /dev/null +++ b/test/integration/AutoSwapper/AutoSwapperWstethSdex.t.sol @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.26; + +import { Test } from "forge-std/Test.sol"; + +import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; + +import { SDEX as SDEX_ADDR, WETH as WETH_ADDR, WSTETH as WSTETH_ADDR } from "../../utils/Constants.sol"; + +import { IAutoSwapperWstethSdex } from "../../../src/interfaces/Utils/IAutoSwapperWstethSdex.sol"; +import { AutoSwapperWstethSdex } from "../../../src/utils/AutoSwapperWstethSdex.sol"; + +/** + * @custom:feature The `AutoSwapperWstethSdex` contract + * @custom:background Given a `AutoSwapperWstethSdex` contract and a forked mainnet + */ +contract TestForkAutoSwapperWstethSdex is Test { + AutoSwapperWstethSdex public autoSwapper; + + address constant BURN_ADDRESS = 0x000000000000000000000000000000000000dEaD; + address constant USDN_PROTOCOL = 0x656cB8C6d154Aad29d8771384089be5B5141f01a; + IERC20 constant SDEX = IERC20(SDEX_ADDR); + IERC20 constant WETH = IERC20(WETH_ADDR); + IERC20 constant WSTETH = IERC20(WSTETH_ADDR); + + function setUp() public { + vm.createSelectFork("mainnet"); + + autoSwapper = new AutoSwapperWstethSdex(); + + deal(address(WSTETH), USDN_PROTOCOL, 100 ether); + } + + /** + * @custom:scenario Test the AutoSwapper's full swap execution via the callback function + * @custom:when `feeCollectorCallback` is called + * @custom:then It should perform both swaps + * @custom:and the SDEX balance of the burn address should increase + * @custom:and the wstETH and WETH balances of the contract should be zero + */ + function test_ForkFeeCollectorCallback() public { + uint256 amountToSwap = 1 ether; + uint256 initialBurnAddressBalance = SDEX.balanceOf(BURN_ADDRESS); + + vm.startPrank(USDN_PROTOCOL); + WSTETH.transfer(address(autoSwapper), amountToSwap); + autoSwapper.feeCollectorCallback(1); + vm.stopPrank(); + + assertEq(WSTETH.balanceOf(address(autoSwapper)), 0, "wstETH balance not zero"); + assertEq(WETH.balanceOf(address(autoSwapper)), 0, "WETH balance not zero"); + assertEq(SDEX.balanceOf(address(autoSwapper)), 0, "SDEX balance not zero"); + assertGt( + SDEX.balanceOf(BURN_ADDRESS), initialBurnAddressBalance, "Swap did not increase burn address SDEX balance" + ); + } + + /** + * @custom:scenario Test the `Ownable` access control of the AutoSwapper + * @custom:when the `sweep` and `updateSwapSlippage` functions are called + * @custom:then It should revert with the `OwnableUnauthorizedAccount` error + */ + function test_ForkAdmin() public { + address user = vm.addr(1); + vm.startPrank(user); + + vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, user)); + autoSwapper.sweep(address(0), address(0), 1); + + vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, user)); + autoSwapper.updateSwapSlippage(1); + + vm.stopPrank(); + } + + /** + * @custom:scenario Test the external function calls of the AutoSwapper + * @custom:when the `uniswapV3SwapCallback` and `smardexSwapCallback` functions are called + * @custom:then it should revert with the `AutoSwapperInvalidCaller` error + */ + function test_ForkInvalidCaller() public { + address user = vm.addr(1); + vm.startPrank(user); + + vm.expectRevert(IAutoSwapperWstethSdex.AutoSwapperInvalidCaller.selector); + autoSwapper.uniswapV3SwapCallback(1, 1, ""); + + vm.expectRevert(IAutoSwapperWstethSdex.AutoSwapperInvalidCaller.selector); + autoSwapper.smardexSwapCallback(1, 1, ""); + + vm.stopPrank(); + } +} diff --git a/test/integration/Middlewares/Oracle/ParseAndValidatePrice.t.sol b/test/integration/Middlewares/Oracle/ParseAndValidatePrice.t.sol index ab883117a..618acb235 100644 --- a/test/integration/Middlewares/Oracle/ParseAndValidatePrice.t.sol +++ b/test/integration/Middlewares/Oracle/ParseAndValidatePrice.t.sol @@ -94,7 +94,7 @@ contract TestOracleMiddlewareParseAndValidatePriceRealData is OracleMiddlewareBa /** * @custom:scenario Parse and validate price with chainlink on-chain - * @custom:given The price feed is eth/usd for chainlink + * @custom:given The price feed is ETH/USD for chainlink * @custom:when The protocol action is any targeted action * @custom:then The price retrieved by the oracle middleware is the same as the one from chainlink on-chain data */ @@ -257,7 +257,7 @@ contract TestOracleMiddlewareParseAndValidatePriceRealData is OracleMiddlewareBa /** * @custom:scenario Parse and validate price with chainlink on-chain - * @custom:given The price feed is eth/usd for chainlink + * @custom:given The price feed is ETH/USD for chainlink * @custom:when Protocol action is an `initiateDeposit` * @custom:then The price signature is well-decoded * @custom:and The price retrieved by the oracle middleware is the same as the one from the chainlink on-chain data @@ -304,70 +304,50 @@ contract TestOracleMiddlewareParseAndValidatePriceRealData is OracleMiddlewareBa } /** - * @custom:scenario Use cached Pyth value for `initiate` actions if possible - * @custom:given A pyth signature was provided to the oracle more recently than the latest chainlink on-chain data - * @custom:when A user retrieves a price for a `initiate` action without providing data - * @custom:then The price retrieved by the oracle middleware is the one from pyth + * @custom:scenario Uses the cached Pyth value for `initiate` actions. + * @custom:given A pyth signature was provided to the oracle more recently than the last chainlink price. + * @custom:when A user retrieves a price for a `initiate` action without providing data. + * @custom:then The price retrieved by the oracle middleware must be equal to the unsafe Pyth price. */ function test_ForkFFIUseCachedPythPrice() public ethMainnetFork reSetUp { - // chainlink data + vm.rollFork(PYTH_PRICE_BLOCK_NUMBER); (uint256 chainlinkPrice, uint256 chainlinkTimestamp) = getChainlinkPrice(); - // get pyth price that must be more recent than chainlink data - (,,,, bytes memory data) = getHermesApiSignature(PYTH_ETH_USD, chainlinkTimestamp + 1); - uint256 validationCost = oracleMiddleware.validationCost(data, ProtocolAction.ValidateDeposit); - - // submit to oracle middleware so it gets cached by Pyth - vm.warp(chainlinkTimestamp + 2); - PriceInfo memory middlewarePrice = oracleMiddleware.parseAndValidatePrice{ value: validationCost }( - "", - uint128(chainlinkTimestamp + 1 - oracleMiddleware.getValidationDelay()), - ProtocolAction.ValidateDeposit, - data - ); + PythStructs.Price memory unsafePythPrice = getPythUnsafePrice(); + + uint256 adjustedUnsafePythPrice = + uint64(unsafePythPrice.price) * 10 ** uint32(unsafePythPrice.expo + int8(oracleMiddleware.getDecimals())); // get oracle middleware price without providing data - PriceInfo memory cachedMiddlewarePrice = + PriceInfo memory middlewarePrice = oracleMiddleware.parseAndValidatePrice("", uint128(block.timestamp), ProtocolAction.InitiateDeposit, ""); // timestamp check - assertEq(cachedMiddlewarePrice.timestamp, middlewarePrice.timestamp, "timestamp equal to pyth timestamp"); - assertGt(cachedMiddlewarePrice.timestamp, chainlinkTimestamp, "timestamp greater than chainlink timestamp"); + assertEq(middlewarePrice.timestamp, unsafePythPrice.publishTime, "timestamp equal to pyth unsafe timestamp"); + assertGt(middlewarePrice.timestamp, chainlinkTimestamp, "timestamp greater than chainlink timestamp"); // price check - assertEq(cachedMiddlewarePrice.neutralPrice, middlewarePrice.neutralPrice, "price equal to pyth price"); - assertTrue(cachedMiddlewarePrice.neutralPrice != chainlinkPrice, "price different from chainlink price"); + assertEq(middlewarePrice.neutralPrice, adjustedUnsafePythPrice, "price equal to pyth price"); + assertTrue(middlewarePrice.neutralPrice != chainlinkPrice, "price different from chainlink price"); } /** - * @custom:scenario The cached Pyth price for initiate action is too old - * @custom:given A pyth signature was provided to the oracle more recently than the last chainlink price - * @custom:and The chainlink price is too old (there is a problem with chainlink) - * @custom:when A user retrieves a price for a `initiate` action without providing data + * @custom:scenario The cached Pyth price for initiate action is too old. + * @custom:given A pyth signature was provided to the oracle more recently than the last chainlink price. + * @custom:and The chainlink price is too old (there is a problem with chainlink). + * @custom:when A user retrieves a price for a `initiate` action without providing data. * @custom:then The price is retrieved from Pyth but checked for freshness and the transaction reverts with - * `OracleMiddlewarePriceTooOld` + * `OracleMiddlewarePriceTooOld`. */ function test_RevertWhen_ForkFFIOldCachedPythPrice() public ethMainnetFork reSetUp { - // chainlink data - (, uint256 chainlinkTimestamp) = getChainlinkPrice(); - - // get pyth price that must be more recent than chainlink data - (,,,, bytes memory data) = getHermesApiSignature(PYTH_ETH_USD, chainlinkTimestamp + 1); - uint256 validationCost = oracleMiddleware.validationCost(data, ProtocolAction.ValidateDeposit); - - // submit to oracle middleware so it gets cached by Pyth - vm.warp(chainlinkTimestamp + 2); - oracleMiddleware.parseAndValidatePrice{ value: validationCost }( - "", - uint128(chainlinkTimestamp + 1 - oracleMiddleware.getValidationDelay()), - ProtocolAction.ValidateDeposit, - data - ); + vm.rollFork(PYTH_PRICE_BLOCK_NUMBER); + skip(oracleMiddleware.getChainlinkTimeElapsedLimit()); - // wait for more than _timeElapsedLimit from the middleware - skip(oracleMiddleware.getChainlinkTimeElapsedLimit() + 1); + PythStructs.Price memory unsafePythPrice = getPythUnsafePrice(); // get oracle middleware price without providing data - vm.expectRevert(abi.encodeWithSelector(OracleMiddlewarePriceTooOld.selector, chainlinkTimestamp + 1)); + vm.expectRevert( + abi.encodeWithSelector(OracleMiddlewarePriceTooOld.selector, uint64(unsafePythPrice.publishTime)) + ); oracleMiddleware.parseAndValidatePrice("", uint128(block.timestamp), ProtocolAction.InitiateDeposit, ""); } diff --git a/test/integration/Middlewares/Oracle/ParseAndValidatePriceWithDataStreams.t.sol b/test/integration/Middlewares/Oracle/ParseAndValidatePriceWithDataStreams.t.sol new file mode 100644 index 000000000..1c901a66b --- /dev/null +++ b/test/integration/Middlewares/Oracle/ParseAndValidatePriceWithDataStreams.t.sol @@ -0,0 +1,213 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.26; + +import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; + +import { PriceInfo } from "../../../../src/interfaces/OracleMiddleware/IOracleMiddlewareTypes.sol"; +import { IVerifierProxy } from "../../../../src/interfaces/OracleMiddleware/IVerifierProxy.sol"; + +import { CHAINLINK_DATA_STREAMS_ETH_USD, PYTH_ETH_USD } from "../../../utils/Constants.sol"; +import { ChainlinkDataStreamsFixture } from "../utils/Fixtures.sol"; + +/** + * @custom:feature The `parseAndValidatePrice` function of the `OracleMiddlewareWithDataStreams`. + * @custom:background A deployed OracleMiddlewareWithDataStreams. + */ +contract TestOracleMiddlewareParseAndValidatePriceWithDataStreamsRealData is ChainlinkDataStreamsFixture { + using Strings for uint256; + + string internal constant TIMESTAMP_ERROR = "Wrong timestamp for"; + string internal constant PRICE_ERROR = "Wrong oracle middleware price for "; + string internal constant BALANCE_ERROR = "Wrong balance"; + string internal constant VALIDATION_COST_ERROR = "Wrong validation cost"; + + uint8 internal constant VALIDATE_OPEN_ACTION_INDEX = 7; + string internal _validateOpenTimestampError = + string.concat(TIMESTAMP_ERROR, actionNames[VALIDATE_OPEN_ACTION_INDEX]); + string internal _validateOpenPriceError = string.concat(PRICE_ERROR, actionNames[VALIDATE_OPEN_ACTION_INDEX]); + + /** + * @custom:scenario Parse and validate the price using Chainlink data streams API payload for all actions + * and Pyth Hermes API signature for liquidations. + * @custom:given The price feed is ETH/USD for both Chainlink and Pyth. + * @custom:and The validation delay is respected. + * @custom:when A Protocol action is performed. + * @custom:then The price signature is well-decoded. + * @custom:and The price retrieved by the oracle middleware matches the one + * from the data streams API or the Hermes API. + */ + function test_ForkFFIParseAndValidatePriceWithPythAndDataStreams() public { + _setUp(_params); + + // Loop through all targeted actions + for (uint256 i; i < actions.length; i++) { + // Get the current action type + ProtocolAction action = actions[i]; + + // Construct error messages + string memory timestampErrorMessage = string.concat(TIMESTAMP_ERROR, actionNames[i]); + string memory priceErrorMessage = string.concat(PRICE_ERROR, actionNames[i]); + + // Validate data streams for Chainlink + _parseAndValidateDataStreams(action, timestampErrorMessage, priceErrorMessage); + + // For liquidation actions, validate Pyth + if (action == ProtocolAction.Liquidation) { + _parseAndValidatePyth(action, timestampErrorMessage, priceErrorMessage); + } + } + } + + /** + * @custom:scenario Parse and validate price with Chainlink data streams API payload using a fee manager. + * @custom:given The price feed is ETH/USD for both Chainlink and Pyth. + * @custom:and A mock fee manager is deployed. + * @custom:when The `parseAndValidatePrice` function is called. + * @custom:then The price signature is verified. + * @custom:and The balance of the WETH in the mock fee manager equals the validation cost. + */ + function test_ForkFFIParseAndValidatePriceFeeManager() public { + _params.deployMockFeeManager = true; + _setUp(_params); + uint256 validationCost = _parseAndValidateDataStreams( + actions[VALIDATE_OPEN_ACTION_INDEX], _validateOpenTimestampError, _validateOpenPriceError + ); + assertGt(validationCost, 0, VALIDATION_COST_ERROR); + assertEq(_weth.balanceOf(address(_mockFeeManager)), validationCost, BALANCE_ERROR); + } + + /** + * @custom:scenario Parse and validate price with Chainlink data streams API payload using a fee manager and full + * native surcharge. + * @custom:given The price feed is ETH/USD for both Chainlink and Pyth. + * @custom:and A mock fee manager is deployed. + * @custom:and A full native surcharge is set. + * @custom:when The `parseAndValidatePrice` function is called. + * @custom:then The price signature is verified. + * @custom:and The balance of the WETH in the mock fee manager equals the validation cost. + */ + function test_ForkFFIParseAndValidatePriceFeeManagerWithFullSurcharge() public { + _params.deployMockFeeManager = true; + _params.nativeSurchargeBps = PERCENTAGE_SCALAR; + _setUp(_params); + uint256 validationCost = _parseAndValidateDataStreams( + actions[VALIDATE_OPEN_ACTION_INDEX], _validateOpenTimestampError, _validateOpenPriceError + ); + assertGt(validationCost, 0, VALIDATION_COST_ERROR); + assertEq(_weth.balanceOf(address(_mockFeeManager)), validationCost, BALANCE_ERROR); + } + + /** + * @custom:scenario Parse and validate price with Chainlink data streams API payload using a fee manager and full + * discount. + * @custom:given The price feed is ETH/USD for both Chainlink and Pyth. + * @custom:and A mock fee manager is deployed. + * @custom:when The `parseAndValidatePrice` function is called. + * @custom:then The price signature is verified. + * @custom:and The balance of the WETH in the mock fee manager is zero. + */ + function test_ForkFFIParseAndValidatePriceFeeManagerWithFullDiscount() public { + _params.deployMockFeeManager = true; + _params.discountBps = PERCENTAGE_SCALAR; + _setUp(_params); + uint256 validationCost = _parseAndValidateDataStreams( + actions[VALIDATE_OPEN_ACTION_INDEX], _validateOpenTimestampError, _validateOpenPriceError + ); + assertEq(validationCost, 0, VALIDATION_COST_ERROR); + assertEq(_weth.balanceOf(address(_mockFeeManager)), validationCost, BALANCE_ERROR); + } + + /** + * @notice Parses and validates the Chainlink data streams API payload. + * @param action The USDN protocol action. + * @param timestampErrorMessage The error message for timestamp validation failure. + * @param priceErrorMessage The error message for price validation failure. + */ + function _parseAndValidateDataStreams( + ProtocolAction action, + string memory timestampErrorMessage, + string memory priceErrorMessage + ) internal returns (uint256 validationCost_) { + // Unverified payload from the Chainlink data streams API. + bytes memory payload = _getChainlinkDataStreamsApiSignature(CHAINLINK_DATA_STREAMS_ETH_USD, block.timestamp); + + // Decode report data from the payload. + (, bytes memory reportData) = abi.decode(payload, (bytes32[3], bytes)); + + // Decode the unverified report. + IVerifierProxy.ReportV3 memory unverifiedReport = abi.decode(reportData, (IVerifierProxy.ReportV3)); + + // Calculate the validation cost for the given action and payload. + validationCost_ = oracleMiddleware.validationCost(payload, action); + + // Initialize middleware price info. + PriceInfo memory middlewarePrice; + + // Validate the price based on the action type. + if ( + action == ProtocolAction.Initialize || action == ProtocolAction.Liquidation + || action == ProtocolAction.InitiateDeposit || action == ProtocolAction.InitiateWithdrawal + || action == ProtocolAction.InitiateOpenPosition || action == ProtocolAction.InitiateClosePosition + ) { + middlewarePrice = oracleMiddleware.parseAndValidatePrice{ value: validationCost_ }("", 0, action, payload); + } else { + middlewarePrice = oracleMiddleware.parseAndValidatePrice{ value: validationCost_ }( + "", uint128(block.timestamp - oracleMiddleware.getValidationDelay()), action, payload + ); + } + + // Assert that the timestamp from the middleware price matches the block timestamp. + assertEq(middlewarePrice.timestamp, block.timestamp, timestampErrorMessage); + + // Validate the price based on the action type. + if (action == ProtocolAction.ValidateWithdrawal || action == ProtocolAction.ValidateOpenPosition) { + // Ask price validation for withdrawal or open position actions. + assertEq(middlewarePrice.price, uint192(unverifiedReport.ask), priceErrorMessage); + } else if (action == ProtocolAction.ValidateDeposit || action == ProtocolAction.ValidateClosePosition) { + // Bid price validation for deposit or close position actions. + assertEq(middlewarePrice.price, uint192(unverifiedReport.bid), priceErrorMessage); + } else { + // Neutral price validation for other actions. + assertEq(middlewarePrice.price, uint192(unverifiedReport.price), priceErrorMessage); + } + } + + /** + * @dev Parses and validates the Pyth Hermes API signature. + * @param action The USDN protocol action. + * @param timestampErrorMessage The error message for timestamp validation failure. + * @param priceErrorMessage The error message for price validation failure. + */ + function _parseAndValidatePyth( + ProtocolAction action, + string memory timestampErrorMessage, + string memory priceErrorMessage + ) internal { + // Retrieve Pyth data including price, decimals, timestamp, and signature bytes. + (uint256 pythPrice,, uint256 pythDecimals, uint256 pythTimestamp, bytes memory data) = + getHermesApiSignature(PYTH_ETH_USD, block.timestamp); + + // Calculate the validation cost for the given action and Pyth data. + uint256 validationCost = oracleMiddleware.validationCost(data, action); + + // Since we force the usage of Pyth for initiate actions, Pyth requires + // that the price data timestamp is recent compared to block.timestamp. + vm.warp(pythTimestamp); + + // Parse and validate the price using the middleware. + PriceInfo memory middlewarePrice = + oracleMiddleware.parseAndValidatePrice{ value: validationCost }("", 0, action, data); + + // Format Pyth price to match the expected decimal precision. + uint256 formattedPythPrice = pythPrice * 10 ** (oracleMiddleware.getDecimals() - pythDecimals); + + // Assert that the timestamp from the middleware price matches the Pyth timestamp. + assertEq(middlewarePrice.timestamp, pythTimestamp, timestampErrorMessage); + + // Assert that the formatted Pyth price matches the price from the middleware. + assertEq(middlewarePrice.price, formattedPythPrice, priceErrorMessage); + } + + // Receive ether refunds + receive() external payable { } +} diff --git a/test/integration/Middlewares/WstethOracle/ParseAndValidatePrice.t.sol b/test/integration/Middlewares/WstethOracle/ParseAndValidatePrice.t.sol index 766977f47..0a6cf3ea7 100644 --- a/test/integration/Middlewares/WstethOracle/ParseAndValidatePrice.t.sol +++ b/test/integration/Middlewares/WstethOracle/ParseAndValidatePrice.t.sol @@ -281,7 +281,7 @@ contract TestWstethMiddlewareParseAndValidatePriceRealData is WstethIntegrationF assertApproxEqAbs(middlewarePrice.timestamp, pythWstethTimestamp, 5, "Wrong similar timestamp"); // should obtain a short different price between the pyth wsteth price feed - // and the pyth eth price feed adjusted with ratio. + // and the pyth ETH price feed adjusted with ratio. // We are ok with a delta below the pyth wsteth confidence. assertApproxEqAbs( middlewarePrice.price, diff --git a/test/integration/Middlewares/utils/Fixtures.sol b/test/integration/Middlewares/utils/Fixtures.sol index ad99330c0..37fd5e7b1 100644 --- a/test/integration/Middlewares/utils/Fixtures.sol +++ b/test/integration/Middlewares/utils/Fixtures.sol @@ -2,10 +2,19 @@ pragma solidity 0.8.26; import { AggregatorV3Interface } from "@chainlink/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { IPyth } from "@pythnetwork/pyth-sdk-solidity/IPyth.sol"; import { PythStructs } from "@pythnetwork/pyth-sdk-solidity/PythStructs.sol"; -import { CHAINLINK_ORACLE_ETH, PYTH_ETH_USD, PYTH_ORACLE, WSTETH } from "../../../utils/Constants.sol"; +import { + CHAINLINK_DATA_STREAMS_ETH_USD, + CHAINLINK_ORACLE_ETH, + CHAINLINK_VERIFIER_PROXY, + DEPLOYER, + PYTH_ETH_USD, + PYTH_ORACLE, + WSTETH +} from "../../../utils/Constants.sol"; import { BaseFixture } from "../../../utils/Fixtures.sol"; import { PYTH_DATA_ETH, @@ -14,9 +23,11 @@ import { PYTH_DATA_ETH_PRICE, PYTH_DATA_TIMESTAMP } from "../utils/Constants.sol"; +import { MockFeeManager } from "./MockFeeManager.sol"; -import { OracleMiddleware } from "../../../../src/OracleMiddleware/OracleMiddleware.sol"; -import { WstEthOracleMiddleware } from "../../../../src/OracleMiddleware/WstEthOracleMiddleware.sol"; +import { OracleMiddlewareWithDataStreams } from "../../../../src/OracleMiddleware/OracleMiddlewareWithDataStreams.sol"; +import { OracleMiddlewareWithPyth } from "../../../../src/OracleMiddleware/OracleMiddlewareWithPyth.sol"; +import { WstEthOracleMiddlewareWithPyth } from "../../../../src/OracleMiddleware/WstEthOracleMiddlewareWithPyth.sol"; import { IWstETH } from "../../../../src/interfaces/IWstETH.sol"; import { IOracleMiddlewareErrors } from "../../../../src/interfaces/OracleMiddleware/IOracleMiddlewareErrors.sol"; import { IUsdnProtocolTypes } from "../../../../src/interfaces/UsdnProtocol/IUsdnProtocolTypes.sol"; @@ -72,6 +83,14 @@ contract CommonBaseIntegrationFixture is BaseFixture { return abi.decode(result, (uint256, uint256, uint256, uint256, bytes)); } + function _getChainlinkDataStreamsApiSignature(bytes32 stream, uint256 timestamp) + internal + returns (bytes memory payload_) + { + payload_ = vmFFIRustCommand("chainlink-price", vm.toString(stream), vm.toString(timestamp)); + require(keccak256(payload_) != keccak256(""), "Rust command returned an error"); + } + function getChainlinkPrice() internal view returns (uint256, uint256) { (, int256 price,, uint256 timestamp,) = chainlinkOnChain.latestRoundData(); return (uint256(price), uint256(timestamp)); @@ -95,7 +114,7 @@ contract CommonBaseIntegrationFixture is BaseFixture { * @dev Utils for testing the oracle middleware */ contract OracleMiddlewareBaseIntegrationFixture is CommonBaseIntegrationFixture, ActionsIntegrationFixture { - OracleMiddleware public oracleMiddleware; + OracleMiddlewareWithPyth public oracleMiddleware; modifier reSetUp() { setUp(); @@ -105,7 +124,7 @@ contract OracleMiddlewareBaseIntegrationFixture is CommonBaseIntegrationFixture, function setUp() public virtual { pyth = IPyth(PYTH_ORACLE); chainlinkOnChain = AggregatorV3Interface(CHAINLINK_ORACLE_ETH); - oracleMiddleware = new OracleMiddleware(address(pyth), PYTH_ETH_USD, address(chainlinkOnChain), 1 hours); + oracleMiddleware = new OracleMiddlewareWithPyth(address(pyth), PYTH_ETH_USD, address(chainlinkOnChain), 1 hours); } function getMockedPythSignatureETH() @@ -122,7 +141,7 @@ contract OracleMiddlewareBaseIntegrationFixture is CommonBaseIntegrationFixture, * @dev Utils for testing the oracle middleware */ contract WstethIntegrationFixture is CommonBaseIntegrationFixture, ActionsIntegrationFixture { - WstEthOracleMiddleware public wstethMiddleware; + WstEthOracleMiddlewareWithPyth public wstethMiddleware; IWstETH public constant WST_ETH = IWstETH(WSTETH); modifier reSetUp() virtual { @@ -134,7 +153,7 @@ contract WstethIntegrationFixture is CommonBaseIntegrationFixture, ActionsIntegr pyth = IPyth(PYTH_ORACLE); chainlinkOnChain = AggregatorV3Interface(CHAINLINK_ORACLE_ETH); wstethMiddleware = - new WstEthOracleMiddleware(address(pyth), PYTH_ETH_USD, address(chainlinkOnChain), WSTETH, 1 hours); + new WstEthOracleMiddlewareWithPyth(address(pyth), PYTH_ETH_USD, address(chainlinkOnChain), WSTETH, 1 hours); } function getMockedPythSignatureETH() @@ -149,3 +168,61 @@ contract WstethIntegrationFixture is CommonBaseIntegrationFixture, ActionsIntegr return amount * WST_ETH.stEthPerToken() / 1 ether; } } + +contract ChainlinkDataStreamsFixture is CommonBaseIntegrationFixture, ActionsIntegrationFixture { + OracleMiddlewareWithDataStreams internal oracleMiddleware; + + uint64 internal constant PERCENTAGE_SCALAR = 1e18; + + struct FeeManagerData { + bool deployMockFeeManager; + uint64 discountBps; + uint64 nativeSurchargeBps; + } + + FeeManagerData internal _params; + + MockFeeManager internal _mockFeeManager; + + IERC20 internal _weth; + + function _setUp(FeeManagerData memory params) internal { + string memory url = vm.rpcUrl("mainnet"); + vm.createSelectFork(url); + + pyth = IPyth(PYTH_ORACLE); + chainlinkOnChain = AggregatorV3Interface(CHAINLINK_ORACLE_ETH); + + vm.prank(DEPLOYER); + oracleMiddleware = new OracleMiddlewareWithDataStreams( + address(pyth), + PYTH_ETH_USD, + address(chainlinkOnChain), + 1 hours, + CHAINLINK_VERIFIER_PROXY, + CHAINLINK_DATA_STREAMS_ETH_USD + ); + + if (params.deployMockFeeManager) { + _mockFeeManager = new MockFeeManager(); + _weth = IERC20(_mockFeeManager.i_nativeAddress()); + _mockFeeManager.updateSubscriberDiscount( + address(oracleMiddleware), + CHAINLINK_DATA_STREAMS_ETH_USD, + _mockFeeManager.i_nativeAddress(), + params.discountBps + ); + _mockFeeManager.setNativeSurcharge(params.nativeSurchargeBps); + + (, bytes memory proxyVerifierOwnerData) = + CHAINLINK_VERIFIER_PROXY.staticcall(abi.encodeWithSignature("owner()")); + address proxyVerifierOwner = abi.decode(proxyVerifierOwnerData, (address)); + + vm.prank(proxyVerifierOwner); + (bool success,) = CHAINLINK_VERIFIER_PROXY.call( + abi.encodeWithSignature("setFeeManager(address)", address(_mockFeeManager)) + ); + assertTrue(success, "setFeeManager failed"); + } + } +} diff --git a/test/integration/Middlewares/utils/MockFeeManager.sol b/test/integration/Middlewares/utils/MockFeeManager.sol new file mode 100644 index 000000000..60ad5b966 --- /dev/null +++ b/test/integration/Middlewares/utils/MockFeeManager.sol @@ -0,0 +1,337 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.26; + +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; +import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; + +import { IFeeManager } from "../../../../src/interfaces/OracleMiddleware/IFeeManager.sol"; + +interface IWERC20 { + function deposit() external payable; +} + +contract MockFeeManager is IERC165 { + using SafeERC20 for IERC20; + + /* -------------------------------------------------------------------------- */ + /* STRUCTS */ + /* -------------------------------------------------------------------------- */ + + /** + * @notice The structure to hold a fee and reward to verify a report. + * @param configDigest The digest linked to the fee and reward. + * @param fee The fee paid to verify the report. + * @param reward The reward paid upon verification. + * @param appliedDiscount The discount applied to the reward. + */ + struct FeeAndReward { + bytes32 configDigest; + IFeeManager.Asset fee; + IFeeManager.Asset reward; + uint256 appliedDiscount; + } + + /** + * @notice The structure to hold a fee payment notice. + * @param poolId The poolId receiving the payment. + * @param amount The amount being paid. + */ + struct FeePayment { + bytes32 poolId; + uint192 amount; + } + + /* -------------------------------------------------------------------------- */ + /* PRIVATE */ + /* -------------------------------------------------------------------------- */ + + /// @notice The total discount that can be applied to a fee, 1e18 = 100% discount. + uint256 private constant PERCENTAGE_SCALAR = 1e18; + + /* -------------------------------------------------------------------------- */ + /* PUBLIC */ + /* -------------------------------------------------------------------------- */ + + /// @notice The list of subscribers and their discounts subscriberDiscounts[subscriber][feedId][token]. + mapping(address => mapping(bytes32 => mapping(address => uint256))) public s_subscriberDiscounts; + + /// @notice The native token address. + address public constant i_nativeAddress = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + + /// @notice The surcharge fee to be paid if paying in native. + uint256 public s_nativeSurcharge; + + /* -------------------------------------------------------------------------- */ + /* ERRORS */ + /* -------------------------------------------------------------------------- */ + + /// @notice The error thrown if the discount or surcharge is invalid. + error InvalidSurcharge(); + + /// @notice The error thrown if the discount is invalid. + error InvalidDiscount(); + + /// @notice The error thrown if the address is invalid. + error InvalidAddress(); + + /// @notice Thrown if msg.value is supplied with a bad quote. + error InvalidDeposit(); + + /// @notice Thrown if a report has expired. + error ExpiredReport(); + + /// @notice Thrown if a report has no quote. + error InvalidQuote(); + + /* -------------------------------------------------------------------------- */ + /* EVENTS */ + /* -------------------------------------------------------------------------- */ + + /** + * @notice Emitted whenever a subscriber's discount is updated. + * @param subscriber The address of the subscriber to update discounts for. + * @param feedId Feed ID for the discount. + * @param token Token address for the discount. + * @param discount Discount to apply, in relation to the `PERCENTAGE_SCALAR`. + */ + event SubscriberDiscountUpdated( + address indexed subscriber, bytes32 indexed feedId, address indexed token, uint64 discount + ); + + /** + * @notice Emitted when updating the native surcharge. + * @param newSurcharge The surcharge amount to apply relative to `PERCENTAGE_SCALAR`. + */ + event NativeSurchargeUpdated(uint64 newSurcharge); + + /** + * @notice Emitted when a fee has been processed. + * @param configDigest The config digest of the fee processed. + * @param subscriber The address of the subscriber who paid the fee. + * @param fee The fee paid. + * @param reward The reward paid. + * @param appliedDiscount The discount applied to the fee. + */ + event DiscountApplied( + bytes32 indexed configDigest, + address indexed subscriber, + IFeeManager.Asset fee, + IFeeManager.Asset reward, + uint256 appliedDiscount + ); + + /* -------------------------------------------------------------------------- */ + /* EXTERNAL FUNCTIONS */ + /* -------------------------------------------------------------------------- */ + + /// @inheritdoc IERC165 + function supportsInterface(bytes4 interfaceId) external pure override returns (bool) { + return interfaceId == this.processFee.selector || interfaceId == this.processFeeBulk.selector; + } + + /** + * @notice Processes the fee and reward for a given payload. + * @param payload The encoded data containing the report and other necessary information. + * @param subscriber The address of the subscriber trying to verify. + */ + function processFee(bytes calldata payload, bytes calldata, address subscriber) external payable { + (IFeeManager.Asset memory fee, IFeeManager.Asset memory reward, uint256 appliedDiscount) = + _processFee(payload, subscriber); + + if (fee.amount == 0) { + _tryReturnChange(subscriber, msg.value); + return; + } + + FeeAndReward[] memory feeAndReward = new FeeAndReward[](1); + feeAndReward[0] = FeeAndReward(bytes32(payload), fee, reward, appliedDiscount); + + _handleFeesAndRewards(subscriber, feeAndReward, 0, 1); + } + + /** + * @notice Processes fees and rewards for a batch of given payloads. + * @param payloads An array of encoded data containing reports and other necessary information. + * @param parameterPayload The additional parameter payload (not used in the current implementation). + * @param subscriber The address of the subscriber trying to verify. + */ + function processFeeBulk(bytes[] calldata payloads, bytes calldata parameterPayload, address subscriber) + external + payable + { } + + /** + * @notice Calculate the applied fee and reward from a report. If the sender is a subscriber, they will receive a + * discount. + * @param subscriber The address of the subscriber trying to verify. + * @param report The report for which the fee and reward are calculated. + * @param quoteAddress The address of the quote payment token. + * @return fee_ The calculated fee data. + * @return rewards_ The calculated reward data (currently not implemented, returns default value). + * @return discount_ The current discount applied. + */ + function getFeeAndReward(address subscriber, bytes memory report, address quoteAddress) + public + view + returns (IFeeManager.Asset memory fee_, IFeeManager.Asset memory rewards_, uint256 discount_) + { + // Get the feedId from the report + bytes32 feedId = bytes32(report); + + // Verify the quote payload is a supported token + if (quoteAddress != i_nativeAddress) { + revert InvalidQuote(); + } + + // Decode the report depending on the version + uint256 nativeQuantity; + uint256 expiresAt; + (,,, nativeQuantity,, expiresAt) = abi.decode(report, (bytes32, uint32, uint32, uint192, uint192, uint32)); + + // Read the timestamp bytes from the report data and verify it has not expired + if (expiresAt < block.timestamp) { + revert ExpiredReport(); + } + + // Get the discount being applied + discount_ = s_subscriberDiscounts[subscriber][feedId][quoteAddress]; + + uint256 surchargedFee = + Math.ceilDiv(nativeQuantity * (PERCENTAGE_SCALAR + s_nativeSurcharge), PERCENTAGE_SCALAR); + + fee_.assetAddress = quoteAddress; + fee_.amount = Math.ceilDiv(surchargedFee * (PERCENTAGE_SCALAR - discount_), PERCENTAGE_SCALAR); + + return (fee_, rewards_, discount_); + } + + /** + * @notice Set the surcharge fee to be paid if paying in native. + * @param surcharge The new surcharge fee value. + */ + function setNativeSurcharge(uint64 surcharge) external { + if (surcharge > PERCENTAGE_SCALAR) revert InvalidSurcharge(); + + s_nativeSurcharge = surcharge; + + emit NativeSurchargeUpdated(surcharge); + } + + /** + * @notice Update the subscriber discount for a specific feed and token. + * @param subscriber The address of the subscriber. + * @param feedId The ID of the feed for which the discount is being updated. + * @param token The address of the token (LINK or native). + * @param discount The new discount value to be applied. + */ + function updateSubscriberDiscount(address subscriber, bytes32 feedId, address token, uint64 discount) external { + // Ensure the discount is not greater than the total discount that can be applied + if (discount > PERCENTAGE_SCALAR) revert InvalidDiscount(); + // Ensure the token is either LINK or native + if (token != i_nativeAddress) revert InvalidAddress(); + + s_subscriberDiscounts[subscriber][feedId][token] = discount; + + emit SubscriberDiscountUpdated(subscriber, feedId, token, discount); + } + + /* -------------------------------------------------------------------------- */ + /* INTERNAL FUNCTIONS */ + /* -------------------------------------------------------------------------- */ + + /** + * @notice Process a fee and reward for a given payload. + * @param payload The encoded data containing the report and other necessary information. + * @param subscriber The address of the subscriber trying to verify. + * @return fee The calculated fee data. + * @return reward The calculated reward data. + * @return discount The current discount applied. + */ + function _processFee(bytes calldata payload, address subscriber) + internal + view + returns (IFeeManager.Asset memory, IFeeManager.Asset memory, uint256) + { + if (subscriber == address(this)) revert InvalidAddress(); + + // Decode the report from the payload + (, bytes memory report) = abi.decode(payload, (bytes32[3], bytes)); + + return getFeeAndReward(subscriber, report, i_nativeAddress); + } + + /** + * @notice Handle fees and rewards for a given set of FeeAndReward structures. + * @param subscriber The address of the subscriber trying to verify. + * @param feesAndRewards An array containing FeeAndReward structures. + * @param numberOfLinkFees The number of Link-based fees in the feesAndRewards array. + * @param numberOfNativeFees The number of Native-based fees in the feesAndRewards array. + */ + function _handleFeesAndRewards( + address subscriber, + FeeAndReward[] memory feesAndRewards, + uint256 numberOfLinkFees, + uint256 numberOfNativeFees + ) internal { + FeePayment[] memory nativeFeeLinkRewards = new FeePayment[](numberOfNativeFees); + + uint256 totalNativeFee; + uint256 totalNativeFeeLinkValue; + uint256 nativeFeeLinkRewardsIndex; + + uint256 totalNumberOfFees = numberOfLinkFees + numberOfNativeFees; + for (uint256 i; i < totalNumberOfFees; ++i) { + nativeFeeLinkRewards[nativeFeeLinkRewardsIndex++] = + FeePayment(feesAndRewards[i].configDigest, uint192(feesAndRewards[i].reward.amount)); + totalNativeFee += feesAndRewards[i].fee.amount; + totalNativeFeeLinkValue += feesAndRewards[i].reward.amount; + + if (feesAndRewards[i].appliedDiscount != 0) { + emit DiscountApplied( + feesAndRewards[i].configDigest, + subscriber, + feesAndRewards[i].fee, + feesAndRewards[i].reward, + feesAndRewards[i].appliedDiscount + ); + } + } + + // Keep track of any change in case of over payment. + uint256 change; + + if (msg.value != 0) { + // Ensure there is enough value to cover the fee. + if (totalNativeFee > msg.value) revert InvalidDeposit(); + + // Wrap the amount required to pay the fee and approve as the subscriber paid in wrapped native. + IWERC20(i_nativeAddress).deposit{ value: totalNativeFee }(); + + unchecked { + // msg.value is always >= to totalNativeFee. + change = msg.value - totalNativeFee; + } + } else { + if (totalNativeFee != 0) { + // The subscriber has paid in wrapped native, so transfer the native to this contract. + IERC20(i_nativeAddress).safeTransferFrom(subscriber, address(this), totalNativeFee); + } + } + + // A refund may be needed if the payee has paid in excess of the fee. + _tryReturnChange(subscriber, change); + } + + /** + * @notice Try to return any excess payment to the subscriber. + * @param subscriber The address of the subscriber to receive funds. + * @param quantity The amount of native tokens to be returned. + */ + function _tryReturnChange(address subscriber, uint256 quantity) internal { + if (quantity != 0) { + payable(subscriber).transfer(quantity); + } + } +} diff --git a/test/integration/UsdnProtocol/LiquidationGasUsage.t.sol b/test/integration/UsdnProtocol/LiquidationGasUsage.t.sol index bee9b9461..bd0429c02 100644 --- a/test/integration/UsdnProtocol/LiquidationGasUsage.t.sol +++ b/test/integration/UsdnProtocol/LiquidationGasUsage.t.sol @@ -15,7 +15,8 @@ import { } from "../../utils/Constants.sol"; import { UsdnProtocolBaseIntegrationFixture } from "./utils/Fixtures.sol"; -import { MockWstEthOracleMiddleware } from "../../../src/OracleMiddleware/mock/MockWstEthOracleMiddleware.sol"; +import { MockWstEthOracleMiddlewareWithPyth } from + "../../../src/OracleMiddleware/mock/MockWstEthOracleMiddlewareWithPyth.sol"; import { ILiquidationRewardsManagerErrorsEventsTypes } from "../../../src/interfaces/LiquidationRewardsManager/ILiquidationRewardsManagerErrorsEventsTypes.sol"; import { IBaseRebalancer } from "../../../src/interfaces/Rebalancer/IBaseRebalancer.sol"; @@ -32,7 +33,7 @@ contract TestForkUsdnProtocolLiquidationGasUsage is IUsdnEvents, IRebalancerEvents { - MockWstEthOracleMiddleware mockOracle; + MockWstEthOracleMiddlewareWithPyth mockOracle; uint256 securityDepositValue; uint256[] snapshots; @@ -87,7 +88,7 @@ contract TestForkUsdnProtocolLiquidationGasUsage is /* ------- replace the oracle to setup positions at the desired price ------- */ // use the mock oracle to open positions to avoid hermes calls - mockOracle = new MockWstEthOracleMiddleware( + mockOracle = new MockWstEthOracleMiddlewareWithPyth( address(mockPyth), PYTH_ETH_USD, address(mockChainlinkOnChain), address(wstETH), 1 hours ); diff --git a/test/integration/UsdnProtocol/RebalancerInitiateClosePosition.t.sol b/test/integration/UsdnProtocol/RebalancerInitiateClosePosition.t.sol index 80c555cac..75ed00cc2 100644 --- a/test/integration/UsdnProtocol/RebalancerInitiateClosePosition.t.sol +++ b/test/integration/UsdnProtocol/RebalancerInitiateClosePosition.t.sol @@ -245,7 +245,7 @@ contract TestRebalancerInitiateClosePosition is protocol.setExpoImbalanceLimits(0, 0, 0, 0, 0, 0); skip(1 hours); - // put the eth price a bit higher to avoid liquidating existing position + // put the ETH price a bit higher to avoid liquidating existing position wstEthPrice = _setOraclePrices(wstEthPrice * 15 / 10); vm.startPrank(user); diff --git a/test/integration/UsdnProtocol/utils/Fixtures.sol b/test/integration/UsdnProtocol/utils/Fixtures.sol index df9f61bb0..d380455d2 100644 --- a/test/integration/UsdnProtocol/utils/Fixtures.sol +++ b/test/integration/UsdnProtocol/utils/Fixtures.sol @@ -32,8 +32,9 @@ import { PYTH_DATA_TIMESTAMP } from "../../Middlewares/utils/Constants.sol"; -import { LiquidationRewardsManager } from "../../../../src/LiquidationRewardsManager/LiquidationRewardsManager.sol"; -import { WstEthOracleMiddleware } from "../../../../src/OracleMiddleware/WstEthOracleMiddleware.sol"; +import { LiquidationRewardsManagerWstEth } from + "../../../../src/LiquidationRewardsManager/LiquidationRewardsManagerWstEth.sol"; +import { WstEthOracleMiddlewareWithPyth } from "../../../../src/OracleMiddleware/WstEthOracleMiddlewareWithPyth.sol"; import { Rebalancer } from "../../../../src/Rebalancer/Rebalancer.sol"; import { Usdn } from "../../../../src/Usdn/Usdn.sol"; import { UsdnProtocolFallback } from "../../../../src/UsdnProtocol/UsdnProtocolFallback.sol"; @@ -94,8 +95,8 @@ contract UsdnProtocolBaseIntegrationFixture is WstETH public wstETH; MockPyth public mockPyth; MockChainlinkOnChain public mockChainlinkOnChain; - WstEthOracleMiddleware public oracleMiddleware; - LiquidationRewardsManager public liquidationRewardsManager; + WstEthOracleMiddlewareWithPyth public oracleMiddleware; + LiquidationRewardsManagerWstEth public liquidationRewardsManager; Rebalancer public rebalancer; PreviousActionsData internal EMPTY_PREVIOUS_DATA = @@ -143,13 +144,13 @@ contract UsdnProtocolBaseIntegrationFixture is sdex = Sdex(SDEX); IPyth pyth = IPyth(PYTH_ORACLE); AggregatorV3Interface chainlinkOnChain = AggregatorV3Interface(CHAINLINK_ORACLE_ETH); - oracleMiddleware = new WstEthOracleMiddleware( + oracleMiddleware = new WstEthOracleMiddlewareWithPyth( address(pyth), PYTH_ETH_USD, address(chainlinkOnChain), address(wstETH), 1 hours ); PriceInfo memory currentPrice = oracleMiddleware.parseAndValidatePrice("", uint128(block.timestamp), ProtocolAction.Initialize, ""); testParams.initialLiqPrice = uint128(currentPrice.neutralPrice) / 2; - liquidationRewardsManager = new LiquidationRewardsManager(wstETH); + liquidationRewardsManager = new LiquidationRewardsManagerWstEth(wstETH); } else { wstETH = new WstETH(); sdex = new Sdex(); @@ -160,18 +161,18 @@ contract UsdnProtocolBaseIntegrationFixture is mockChainlinkOnChain.setLastPrice( int256(wstETH.getWstETHByStETH(uint256(testParams.initialPrice / 10 ** (18 - 8)))) ); - oracleMiddleware = new WstEthOracleMiddleware( + oracleMiddleware = new WstEthOracleMiddlewareWithPyth( address(mockPyth), PYTH_ETH_USD, address(mockChainlinkOnChain), address(wstETH), 1 hours ); vm.warp(testParams.initialTimestamp); - liquidationRewardsManager = new LiquidationRewardsManager(wstETH); + liquidationRewardsManager = new LiquidationRewardsManagerWstEth(wstETH); } (bool success,) = address(wstETH).call{ value: DEPLOYER.balance * 9 / 10 }(""); require(success, "DEPLOYER wstETH mint failed"); usdn = new Usdn(address(0), address(0)); - implementation = new UsdnProtocolHandler(); - protocolFallback = new UsdnProtocolFallback(); + implementation = new UsdnProtocolHandler(MAX_SDEX_BURN_RATIO, MAX_MIN_LONG_POSITION); + protocolFallback = new UsdnProtocolFallback(MAX_SDEX_BURN_RATIO, MAX_MIN_LONG_POSITION); _setPeripheralContracts( oracleMiddleware, liquidationRewardsManager, usdn, wstETH, address(protocolFallback), ADMIN, sdex diff --git a/test/invariant/UsdnProtocol/utils/Fixtures.sol b/test/invariant/UsdnProtocol/utils/Fixtures.sol index 2572d2774..f49f71425 100644 --- a/test/invariant/UsdnProtocol/utils/Fixtures.sol +++ b/test/invariant/UsdnProtocol/utils/Fixtures.sol @@ -14,18 +14,16 @@ import { UsdnHandler } from "./handlers/Usdn.sol"; import { UsdnProtocolHandler } from "./handlers/UsdnProtocolHandler.sol"; import { UsdnProtocolSafeHandler } from "./handlers/UsdnProtocolSafeHandler.sol"; -import { LiquidationRewardsManager } from "../../../../src/LiquidationRewardsManager/LiquidationRewardsManager.sol"; -import { WstEthOracleMiddleware } from "../../../../src/OracleMiddleware/WstEthOracleMiddleware.sol"; +import { LiquidationRewardsManagerWstEth } from + "../../../../src/LiquidationRewardsManager/LiquidationRewardsManagerWstEth.sol"; +import { WstEthOracleMiddlewareWithPyth } from "../../../../src/OracleMiddleware/WstEthOracleMiddlewareWithPyth.sol"; import { Rebalancer } from "../../../../src/Rebalancer/Rebalancer.sol"; import { UsdnProtocolFallback } from "../../../../src/UsdnProtocol/UsdnProtocolFallback.sol"; -import { UsdnProtocolImpl } from "../../../../src/UsdnProtocol/UsdnProtocolImpl.sol"; import { IUsdnProtocol } from "../../../../src/interfaces/UsdnProtocol/IUsdnProtocol.sol"; import { IUsdnProtocolErrors } from "../../../../src/interfaces/UsdnProtocol/IUsdnProtocolErrors.sol"; import { IUsdnProtocolEvents } from "../../../../src/interfaces/UsdnProtocol/IUsdnProtocolEvents.sol"; import { FeeCollector } from "../../../../src/utils/FeeCollector.sol"; -import { console } from "forge-std/console.sol"; - /// @dev This fixture does not deploy the protocol, only the dependencies contract UsdnProtocolInvariantBaseFixture is BaseFixture, IUsdnProtocolErrors, IUsdnProtocolEvents, DefaultConfig { int24 public constant TICK_SPACING = 100; @@ -36,7 +34,7 @@ contract UsdnProtocolInvariantBaseFixture is BaseFixture, IUsdnProtocolErrors, I Sdex public sdex; WstETH public wstETH; MockOracleMiddleware public oracleMiddleware; - LiquidationRewardsManager public liquidationRewardsManager; + LiquidationRewardsManagerWstEth public liquidationRewardsManager; UsdnProtocolFallback protocolFallback; // Managers managers; @@ -49,8 +47,8 @@ contract UsdnProtocolInvariantBaseFixture is BaseFixture, IUsdnProtocolErrors, I wstETH = new WstETH(); sdex = new Sdex(); oracleMiddleware = new MockOracleMiddleware(INITIAL_PRICE); - liquidationRewardsManager = new LiquidationRewardsManager(wstETH); - protocolFallback = new UsdnProtocolFallback(); + liquidationRewardsManager = new LiquidationRewardsManagerWstEth(wstETH); + protocolFallback = new UsdnProtocolFallback(MAX_SDEX_BURN_RATIO, MAX_MIN_LONG_POSITION); vm.stopPrank(); managers = Managers({ @@ -78,9 +76,10 @@ contract UsdnProtocolInvariantFixture is UsdnProtocolInvariantBaseFixture { super.setUp(); vm.startPrank(DEPLOYER); - UsdnProtocolHandler implementation = new UsdnProtocolHandler(wstETH, sdex); + UsdnProtocolHandler implementation = + new UsdnProtocolHandler(wstETH, sdex, MAX_SDEX_BURN_RATIO, MAX_MIN_LONG_POSITION); _setPeripheralContracts( - WstEthOracleMiddleware(address(oracleMiddleware)), + WstEthOracleMiddlewareWithPyth(address(oracleMiddleware)), liquidationRewardsManager, usdn, wstETH, @@ -132,12 +131,13 @@ contract UsdnProtocolInvariantSafeFixture is UsdnProtocolInvariantBaseFixture { function setUp() public virtual override { super.setUp(); vm.startPrank(DEPLOYER); - UsdnProtocolSafeHandler implementation = new UsdnProtocolSafeHandler(wstETH, sdex); + UsdnProtocolSafeHandler implementation = + new UsdnProtocolSafeHandler(wstETH, sdex, MAX_SDEX_BURN_RATIO, MAX_MIN_LONG_POSITION); FeeCollector feeCollector = new FeeCollector(); //NOTE: added fuzzing contract into collector's constructor _setPeripheralContracts( - WstEthOracleMiddleware(address(oracleMiddleware)), + WstEthOracleMiddlewareWithPyth(address(oracleMiddleware)), liquidationRewardsManager, usdn, wstETH, @@ -147,7 +147,7 @@ contract UsdnProtocolInvariantSafeFixture is UsdnProtocolInvariantBaseFixture { ); address proxy = UnsafeUpgrades.deployUUPSProxy( - address(implementation), abi.encodeCall(UsdnProtocolHandler.initializeStorageHandler, (initStorage)) + address(implementation), abi.encodeCall(UsdnProtocolHandler.initializeStorageHandler, initStorage) ); protocol = UsdnProtocolSafeHandler(proxy); diff --git a/test/invariant/UsdnProtocol/utils/MockOracleMiddleware.sol b/test/invariant/UsdnProtocol/utils/MockOracleMiddleware.sol index ecdddfa4b..da6fa805c 100644 --- a/test/invariant/UsdnProtocol/utils/MockOracleMiddleware.sol +++ b/test/invariant/UsdnProtocol/utils/MockOracleMiddleware.sol @@ -5,14 +5,16 @@ import { EnumerableMap } from "@openzeppelin/contracts/utils/structs/EnumerableM import { PriceInfo } from "../../../../src/interfaces/OracleMiddleware/IOracleMiddlewareTypes.sol"; -import { OracleMiddleware } from "../../../../src/OracleMiddleware/OracleMiddleware.sol"; +import { CommonOracleMiddleware } from "../../../../src/OracleMiddleware/CommonOracleMiddleware.sol"; +import { OracleMiddlewareWithPyth } from "../../../../src/OracleMiddleware/OracleMiddlewareWithPyth.sol"; +import { IBaseOracleMiddleware } from "../../../../src/interfaces/OracleMiddleware/IBaseOracleMiddleware.sol"; import { IUsdnProtocolTypes as Types } from "../../../../src/interfaces/UsdnProtocol/IUsdnProtocolTypes.sol"; /** * @notice Contract to apply and return a mocked wstETH price * @dev This contract always returns a mocked wstETH price */ -contract MockOracleMiddleware is OracleMiddleware { +contract MockOracleMiddleware is OracleMiddlewareWithPyth { using EnumerableMap for EnumerableMap.UintToUintMap; uint256 internal constant MAX_CONF_BPS = 200; // 2% max @@ -20,7 +22,7 @@ contract MockOracleMiddleware is OracleMiddleware { EnumerableMap.UintToUintMap internal _prices; // append-only map of timestamps to prices - constructor(uint128 initialPrice) OracleMiddleware(address(0), "", address(0), 0) { + constructor(uint128 initialPrice) OracleMiddlewareWithPyth(address(0), "", address(0), 0) { _prices.set(block.timestamp, initialPrice); } @@ -45,11 +47,10 @@ contract MockOracleMiddleware is OracleMiddleware { } } - /// @inheritdoc OracleMiddleware function parseAndValidatePrice(bytes32, uint128 targetTimestamp, Types.ProtocolAction action, bytes calldata) public payable - override + override(CommonOracleMiddleware, IBaseOracleMiddleware) returns (PriceInfo memory price_) { // register a new latest price if needed (new block) @@ -130,11 +131,10 @@ contract MockOracleMiddleware is OracleMiddleware { price_.price = lastPrice; } - /// @inheritdoc OracleMiddleware function validationCost(bytes calldata, Types.ProtocolAction action) public pure - override + override(CommonOracleMiddleware, IBaseOracleMiddleware) returns (uint256 result_) { if ( diff --git a/test/invariant/UsdnProtocol/utils/handlers/UsdnProtocolHandler.sol b/test/invariant/UsdnProtocol/utils/handlers/UsdnProtocolHandler.sol index 3c6ee3ea5..361026b79 100644 --- a/test/invariant/UsdnProtocol/utils/handlers/UsdnProtocolHandler.sol +++ b/test/invariant/UsdnProtocol/utils/handlers/UsdnProtocolHandler.sol @@ -32,12 +32,14 @@ contract UsdnProtocolHandler is UsdnProtocolImpl, UsdnProtocolFallback, Test { // Storage _tempStorage; - constructor(WstETH mockAsset, Sdex mockSdex) { + constructor(WstETH mockAsset, Sdex mockSdex, uint256 maxSdexBurnRatio, uint256 maxMinLongPosition) + UsdnProtocolFallback(maxSdexBurnRatio, maxMinLongPosition) + { _mockAsset = mockAsset; _mockSdex = mockSdex; } - function initializeStorageHandler(InitStorage calldata initStorage) external initializer { + function initializeStorageHandler(InitStorage calldata initStorage) external { initializeStorage(initStorage); } diff --git a/test/invariant/UsdnProtocol/utils/handlers/UsdnProtocolSafeHandler.sol b/test/invariant/UsdnProtocol/utils/handlers/UsdnProtocolSafeHandler.sol index 38b5b4851..29e851cb1 100644 --- a/test/invariant/UsdnProtocol/utils/handlers/UsdnProtocolSafeHandler.sol +++ b/test/invariant/UsdnProtocol/utils/handlers/UsdnProtocolSafeHandler.sol @@ -6,6 +6,7 @@ import { Vm, console } from "forge-std/Test.sol"; import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import { ADMIN } from "../../../../utils/Constants.sol"; +import { UsdnProtocolHandler } from "./UsdnProtocolHandler.sol"; import { UsdnProtocolConstantsLibrary as Constants } from "../../../../../src/UsdnProtocol//libraries/UsdnProtocolConstantsLibrary.sol"; @@ -16,7 +17,6 @@ import { UsdnProtocolUtilsLibrary as Utils } from import { PriceInfo } from "../../../../../src/interfaces/OracleMiddleware/IOracleMiddlewareTypes.sol"; import { Sdex } from "../../../../utils/Sdex.sol"; import { WstETH } from "../../../../utils/WstEth.sol"; -import { UsdnProtocolHandler } from "./UsdnProtocolHandler.sol"; /** * @notice A handler for invariant testing of the USDN protocol which does not revert in normal operation @@ -45,7 +45,9 @@ contract UsdnProtocolSafeHandler is UsdnProtocolHandler { PositionId[] newIds; } - constructor(WstETH mockAsset, Sdex mockSdex) UsdnProtocolHandler(mockAsset, mockSdex) { } + constructor(WstETH mockAsset, Sdex mockSdex, uint256 maxSdexBurnRatio, uint256 maxMinLongPosition) + UsdnProtocolHandler(mockAsset, mockSdex, maxSdexBurnRatio, maxMinLongPosition) + { } /* ------------------------ Protocol actions helpers ------------------------ */ diff --git a/test/unit/LiquidationRewardsManager/GetLiquidationRewardsWusdn.t.sol b/test/unit/LiquidationRewardsManager/GetLiquidationRewardsWusdn.t.sol new file mode 100644 index 000000000..7cd0f73c9 --- /dev/null +++ b/test/unit/LiquidationRewardsManager/GetLiquidationRewardsWusdn.t.sol @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.26; + +import { BaseFixture } from "../../utils/Fixtures.sol"; + +import { LiquidationRewardsManagerWusdn } from + "../../../src/LiquidationRewardsManager/LiquidationRewardsManagerWusdn.sol"; +import { Usdn } from "../../../src/Usdn/Usdn.sol"; +import { Wusdn } from "../../../src/Usdn/Wusdn.sol"; +import { ILiquidationRewardsManagerErrorsEventsTypes } from + "../../../src/interfaces/LiquidationRewardsManager/ILiquidationRewardsManagerErrorsEventsTypes.sol"; +import { IWusdn } from "../../../src/interfaces/Usdn/IWusdn.sol"; +import { IUsdnProtocolTypes as Types } from "../../../src/interfaces/UsdnProtocol/IUsdnProtocolTypes.sol"; + +/// @custom:feature The `getLiquidationRewards` function of `LiquidationRewardsManagerWusdn` +contract TestLiquidationRewardsManagerWusdnGetLiquidationRewards is BaseFixture { + IWusdn internal wusdn; + LiquidationRewardsManagerWusdn internal liquidationRewardsManager; + ILiquidationRewardsManagerErrorsEventsTypes.RewardsParameters rewardsParameters; + + uint256 internal constant CURRENT_PRICE = 1 ether / 1000; // 0.001 ETH/WUSDN + + Types.LiqTickInfo[] internal _singleLiquidatedTick; + Types.LiqTickInfo[] internal _liquidatedTicksEmpty; + + function setUp() public { + wusdn = new Wusdn(new Usdn(address(0), address(0))); + liquidationRewardsManager = new LiquidationRewardsManagerWusdn(wusdn); + + liquidationRewardsManager.setRewardsParameters( + 50_000, 500_000, 0, 300_000, 2 gwei, 10_500, 200, 2 ether, 1000 ether + ); + + rewardsParameters = liquidationRewardsManager.getRewardsParameters(); + + vm.fee(30 gwei); + vm.txGasPrice(40 gwei); + + _singleLiquidatedTick.push( + Types.LiqTickInfo({ + totalPositions: 1, + totalExpo: 10_000 ether, + remainingCollateral: -200 ether, + tickPrice: 1.02 ether / 1000, // 0.00102 ETH/wUsdn + priceWithoutPenalty: 1 ether / 1000 // 0.001 ETH/wUsdn + }) + ); + } + + /** + * @custom:scenario Call `getLiquidationRewards` when 1 tick was liquidated + * @custom:given The tx.gasprice is equal to the base fee + offset + * @custom:and The current price of is 0.001 eth/wUsdn + * @custom:when 1 tick was liquidated + * @custom:then It should return an amount of wUsdn based on the gas used by UsdnProtocolActions.liquidate(bytes) + */ + function test_getLiquidationRewardsFor1Tick() public view { + uint256 posBonusWusdn = ( + _singleLiquidatedTick[0].totalExpo * (_singleLiquidatedTick[0].tickPrice - CURRENT_PRICE) / CURRENT_PRICE + ) * 200 / BPS_DIVISOR; + + uint256 gasPriceAndMultiplier = + (rewardsParameters.baseFeeOffset + block.basefee) * rewardsParameters.gasMultiplierBps / BPS_DIVISOR; + + uint256 gasUsed = rewardsParameters.otherGasUsed + liquidationRewardsManager.BASE_GAS_COST() + + uint256(rewardsParameters.gasUsedPerTick) * _singleLiquidatedTick.length; + uint256 totRewards = + rewardsParameters.fixedReward + posBonusWusdn + gasUsed * gasPriceAndMultiplier * 1e18 / CURRENT_PRICE; + uint256 rewards = liquidationRewardsManager.getLiquidationRewards( + _singleLiquidatedTick, CURRENT_PRICE, false, Types.RebalancerAction.None, Types.ProtocolAction.None, "", "" + ); + assertEq(rewards, totRewards, "without rebalancer trigger"); + + gasUsed += rewardsParameters.rebalancerGasUsed; + totRewards = + rewardsParameters.fixedReward + posBonusWusdn + gasUsed * gasPriceAndMultiplier * 1e18 / CURRENT_PRICE; + rewards = liquidationRewardsManager.getLiquidationRewards( + _singleLiquidatedTick, + CURRENT_PRICE, + true, + Types.RebalancerAction.ClosedOpened, + Types.ProtocolAction.None, + "", + "" + ); + assertEq(rewards, totRewards, "with rebalancer trigger"); + } + + /** + * @custom:scenario Call `getLiquidationRewards` when 0 ticks were liquidated + * @custom:when No ticks were liquidated + * @custom:then It should return 0 as we only give rewards on successful liquidations + */ + function test_getLiquidationRewardsFor0Tick() public view { + uint256 rewards = liquidationRewardsManager.getLiquidationRewards( + _liquidatedTicksEmpty, CURRENT_PRICE, false, Types.RebalancerAction.None, Types.ProtocolAction.None, "", "" + ); + assertEq(rewards, 0, "without rebase"); + rewards = liquidationRewardsManager.getLiquidationRewards( + _liquidatedTicksEmpty, CURRENT_PRICE, true, Types.RebalancerAction.None, Types.ProtocolAction.None, "", "" + ); + assertEq(rewards, 0, "with rebase"); + rewards = liquidationRewardsManager.getLiquidationRewards( + _liquidatedTicksEmpty, + CURRENT_PRICE, + false, + Types.RebalancerAction.ClosedOpened, + Types.ProtocolAction.None, + "", + "" + ); + assertEq(rewards, 0, "with rebalancer trigger"); + } +} diff --git a/test/unit/LiquidationRewardsManager/utils/Fixtures.sol b/test/unit/LiquidationRewardsManager/utils/Fixtures.sol index 9930df2d0..15e93de9f 100644 --- a/test/unit/LiquidationRewardsManager/utils/Fixtures.sol +++ b/test/unit/LiquidationRewardsManager/utils/Fixtures.sol @@ -4,8 +4,8 @@ pragma solidity 0.8.26; import { BaseFixture } from "../../../utils/Fixtures.sol"; import { WstETH } from "../../../utils/WstEth.sol"; -import { LiquidationRewardsManager } from "../../../../src/LiquidationRewardsManager/LiquidationRewardsManager.sol"; -import { IWstETH } from "../../../../src/interfaces/IWstETH.sol"; +import { LiquidationRewardsManagerWstEth } from + "../../../../src/LiquidationRewardsManager/LiquidationRewardsManagerWstEth.sol"; /** * @title LiquidationRewardsManagerBaseFixture @@ -13,12 +13,12 @@ import { IWstETH } from "../../../../src/interfaces/IWstETH.sol"; */ contract LiquidationRewardsManagerBaseFixture is BaseFixture { WstETH internal wsteth; - LiquidationRewardsManager internal liquidationRewardsManager; + LiquidationRewardsManagerWstEth internal liquidationRewardsManager; function setUp() public virtual { vm.warp(1_704_063_600); // 01/01/2024 @ 12:00am (UTC+2) wsteth = new WstETH(); - liquidationRewardsManager = new LiquidationRewardsManager(IWstETH(address(wsteth))); + liquidationRewardsManager = new LiquidationRewardsManagerWstEth(wsteth); } } diff --git a/test/unit/Middlewares/Oracle/DataStreamsOracle/AdjustDataStreamPrice.t.sol b/test/unit/Middlewares/Oracle/DataStreamsOracle/AdjustDataStreamPrice.t.sol new file mode 100644 index 000000000..05d0e88a2 --- /dev/null +++ b/test/unit/Middlewares/Oracle/DataStreamsOracle/AdjustDataStreamPrice.t.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.26; + +import { OracleMiddlewareWithDataStreamsFixture } from "../../utils/Fixtures.sol"; + +import { + FormattedDataStreamsPrice, + PriceAdjustment, + PriceInfo +} from "../../../../../src/interfaces/OracleMiddleware/IOracleMiddlewareTypes.sol"; + +/// @custom:feature The `_adjustDataStreamPrice` function of the `OracleMiddlewareWithDataStreams`. +contract TestOracleMiddlewareWithDataStreamsAdjustDataStream is OracleMiddlewareWithDataStreamsFixture { + FormattedDataStreamsPrice internal formattedPrice; + + function setUp() public override { + super.setUp(); + + formattedPrice = FormattedDataStreamsPrice({ + timestamp: report.observationsTimestamp, + price: uint192(report.price), + ask: uint192(report.ask), + bid: uint192(report.bid) + }); + } + + /** + * @custom:scenario Tests the `_adjustDataStreamPrice` without any adjustments. + * @custom:when The function is called without direction of the price adjustment. + * @custom:then The returned price is the `price` attribute of the report. + */ + function test_adjustDataStreamPriceWithoutAdjustment() public view { + PriceInfo memory price = oracleMiddleware.i_adjustDataStreamPrice(formattedPrice, PriceAdjustment.None); + assertEq(price.price, formattedPrice.price, "Invalid price"); + assertEq(price.neutralPrice, formattedPrice.price, "Invalid neutral price"); + assertEq(price.timestamp, formattedPrice.timestamp, "Invalid timestamp"); + } + + /** + * @custom:scenario Tests the `_adjustDataStreamPrice` with a `Up` adjustment. + * @custom:when The function is called with `Up` for the direction of the price adjustment. + * @custom:then The returned price is the `ask` attribute of the report. + */ + function test_adjustDataStreamPriceWithUpAdjustment() public view { + PriceInfo memory price = oracleMiddleware.i_adjustDataStreamPrice(formattedPrice, PriceAdjustment.Up); + assertEq(price.price, formattedPrice.ask, "Invalid price"); + assertEq(price.neutralPrice, formattedPrice.price, "Invalid neutral price"); + assertEq(price.timestamp, formattedPrice.timestamp, "Invalid timestamp"); + } + + /** + * @custom:scenario Tests the `_adjustDataStreamPrice` with a `Down` adjustment. + * @custom:when The function is called with `Down` for the direction of the price adjustment. + * @custom:then The returned price is the `bid` attribute of the report. + */ + function test_adjustDataStreamPriceWithDownAdjustment() public view { + PriceInfo memory price = oracleMiddleware.i_adjustDataStreamPrice(formattedPrice, PriceAdjustment.Down); + assertEq(price.price, formattedPrice.bid, "Invalid price"); + assertEq(price.neutralPrice, formattedPrice.price, "Invalid neutral price"); + assertEq(price.timestamp, formattedPrice.timestamp, "Invalid timestamp"); + } +} diff --git a/test/unit/Middlewares/Oracle/DataStreamsOracle/GetChainlinkDataStreamFeeData.t.sol b/test/unit/Middlewares/Oracle/DataStreamsOracle/GetChainlinkDataStreamFeeData.t.sol new file mode 100644 index 000000000..f00782a77 --- /dev/null +++ b/test/unit/Middlewares/Oracle/DataStreamsOracle/GetChainlinkDataStreamFeeData.t.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.26; + +import { OracleMiddlewareWithDataStreamsFixture } from "../../utils/Fixtures.sol"; + +import { IFeeManager } from "../../../../../src/interfaces/OracleMiddleware/IFeeManager.sol"; + +import { IMockFeeManager } from "../../utils/MockFeeManager.sol"; + +/// @custom:feature The `_getChainlinkDataStreamFeeData` function of the `ChainlinkDataStreamsOracle`. +contract TestOracleMiddlewareWithDataStreamFeeData is OracleMiddlewareWithDataStreamsFixture { + function setUp() public override { + super.setUp(); + } + + /** + * @custom:scenario Tests the `_getChainlinkDataStreamFeeData` function. + * @custom:when The function is called. + * @custom:then The `feeData.assetAddress` should match the native address of the fee manager. + * @custom:and The `feeData.amount` should be equal to the `report.nativeFee`. + */ + function test_getChainlinkDataStreamFeeData() public view { + IFeeManager.Asset memory feeData = oracleMiddleware.i_getChainlinkDataStreamFeeData(payload); + + assertEq(feeData.assetAddress, mockFeeManager.i_nativeAddress(), "Wrong fee native address"); + assertEq(feeData.amount, report.nativeFee, "Wrong fee amount"); + } + + /** + * @custom:scenario Tests the `_getChainlinkDataStreamFeeData` function with an empty fee manager. + * @custom:when The function is called with an empty fee manager. + * @custom:then The fee data must be empty. + */ + function test_getChainlinkDataStreamFeeDataWithoutFeeManager() public { + IMockFeeManager emptyFeeManager = IMockFeeManager(address(0)); + mockStreamVerifierProxy.setFeeManager(emptyFeeManager); + IFeeManager.Asset memory feeData = oracleMiddleware.i_getChainlinkDataStreamFeeData(payload); + + assertEq(feeData.assetAddress, address(emptyFeeManager), "Wrong fee native address"); + assertEq(feeData.amount, 0, "Wrong fee amount"); + } +} diff --git a/test/unit/Middlewares/Oracle/DataStreamsOracle/GetChainlinkDataStreamPrice.t.sol b/test/unit/Middlewares/Oracle/DataStreamsOracle/GetChainlinkDataStreamPrice.t.sol new file mode 100644 index 000000000..81b374679 --- /dev/null +++ b/test/unit/Middlewares/Oracle/DataStreamsOracle/GetChainlinkDataStreamPrice.t.sol @@ -0,0 +1,169 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.26; + +import { MOCK_STREAM_V3, MOCK_STREAM_V4 } from "../../utils/Constants.sol"; +import { OracleMiddlewareWithDataStreamsFixture } from "../../utils/Fixtures.sol"; + +import { FormattedDataStreamsPrice } from "../../../../../src/interfaces/OracleMiddleware/IOracleMiddlewareTypes.sol"; + +/// @custom:feature The `_getChainlinkDataStreamPrice` function of the `ChainlinkDataStreamsOracle`. +contract TestChainlinkDataStreamsOracleGetPrice is OracleMiddlewareWithDataStreamsFixture { + function setUp() public override { + super.setUp(); + } + + /** + * @custom:scenario Tests the `_getChainlinkDataStreamPrice` function with a large fee. + * @custom:when The function is called with a large fee. + * @custom:then The call should revert with `OracleMiddlewareDataStreamFeeSafeguard`. + */ + function test_RevertWhen_getChainlinkDataStreamPriceFeeSafeguard() public { + report.nativeFee = type(uint192).max; + (, payload) = _encodeReport(report); + vm.expectRevert(abi.encodeWithSelector(OracleMiddlewareDataStreamFeeSafeguard.selector, report.nativeFee)); + oracleMiddleware.i_getChainlinkDataStreamPrice(payload, 0, 0); + } + + /** + * @custom:scenario Tests the `_getChainlinkDataStreamPrice` function with an incorrect fee. + * @custom:when The function is called with an incorrect fee. + * @custom:then The call should revert with `OracleMiddlewareIncorrectFee`. + */ + function test_RevertWhen_getChainlinkDataStreamPriceIncorrectFee() public { + vm.expectRevert(OracleMiddlewareIncorrectFee.selector); + oracleMiddleware.i_getChainlinkDataStreamPrice{ value: report.nativeFee + 1 }(payload, 0, 0); + } + + /** + * @custom:scenario Tests the `_getChainlinkDataStreamPrice` function with an invalid report version. + * @custom:when The function is called with an invalid report version. + * @custom:then The call should revert with `OracleMiddlewareInvalidReportVersion`. + */ + function test_RevertWhen_getChainlinkDataStreamPriceIncorrectReportVersion() public { + report.feedId = MOCK_STREAM_V4; + (, payload) = _encodeReport(report); + vm.expectRevert(OracleMiddlewareInvalidReportVersion.selector); + oracleMiddleware.i_getChainlinkDataStreamPrice{ value: report.nativeFee }(payload, 0, 0); + } + + /** + * @custom:scenario Tests the `_getChainlinkDataStreamPrice` function with an invalid stream id. + * @custom:when The function is called with an invalid stream id. + * @custom:then The call should revert with `OracleMiddlewareInvalidStreamId`. + */ + function test_RevertWhen_getChainlinkDataStreamPriceInvalidStreamId() public { + report.feedId = bytes32(uint256(MOCK_STREAM_V3) | 1); + (, payload) = _encodeReport(report); + vm.expectRevert(OracleMiddlewareInvalidStreamId.selector); + oracleMiddleware.i_getChainlinkDataStreamPrice{ value: report.nativeFee }(payload, 0, 0); + } + + /** + * @custom:scenario Tests the `_getChainlinkDataStreamPrice` function with an invalid timestamp + * payload that lacks a target timestamp. + * @custom:when The function is called. + * @custom:then The call should revert with `OracleMiddlewareDataStreamInvalidTimestamp`. + */ + function test_RevertWhen_getChainlinkDataStreamPriceInvalidTimestampWithoutTargetTimestamp() public { + report.validFromTimestamp = 0; + (, payload) = _encodeReport(report); + vm.expectRevert(OracleMiddlewareDataStreamInvalidTimestamp.selector); + oracleMiddleware.i_getChainlinkDataStreamPrice{ value: report.nativeFee }(payload, 0, 0); + } + + /** + * @custom:scenario Tests the `_getChainlinkDataStreamPrice` function with an invalid timestamp + * payload and a target timestamp that is lower than the report's `validFromTimestamp`. + * @custom:when The function is called. + * @custom:then The call should revert with `OracleMiddlewareDataStreamInvalidTimestamp`. + */ + function test_RevertWhen_getChainlinkDataStreamPriceInvalidTimestampWithTargetTimestampLtValidFromTimestamp() + public + { + vm.expectRevert(OracleMiddlewareDataStreamInvalidTimestamp.selector); + oracleMiddleware.i_getChainlinkDataStreamPrice{ value: report.nativeFee }( + payload, report.validFromTimestamp - 1, 0 + ); + } + + /** + * @custom:scenario Tests the `_getChainlinkDataStreamPrice` function with an invalid timestamp + * payload and a target timestamp that is greater than the report's `observationsTimestamp`. + * @custom:when The function is called. + * @custom:then The call should revert with `OracleMiddlewareDataStreamInvalidTimestamp`. + */ + function test_RevertWhen_getChainlinkDataStreamPriceInvalidTimestampWithTargetTimestampGtObservationsTimestamp() + public + { + vm.expectRevert(OracleMiddlewareDataStreamInvalidTimestamp.selector); + oracleMiddleware.i_getChainlinkDataStreamPrice{ value: report.nativeFee }( + payload, report.observationsTimestamp + 1, 0 + ); + } + + /** + * @custom:scenario Tests the `_getChainlinkDataStreamPrice` function with an invalid timestamp + * payload and a target limit that is lower than the report's `observationsTimestamp`. + * @custom:when The function is called. + * @custom:then The call should revert with `OracleMiddlewareDataStreamInvalidTimestamp`. + */ + function test_RevertWhen_getChainlinkDataStreamPriceInvalidTimestampWithTargetLimitLtObservationsTimestamp() + public + { + vm.expectRevert(OracleMiddlewareDataStreamInvalidTimestamp.selector); + oracleMiddleware.i_getChainlinkDataStreamPrice{ value: report.nativeFee }( + payload, report.validFromTimestamp, report.observationsTimestamp - 1 + ); + } + + /** + * @custom:scenario Tests the `_getChainlinkDataStreamPrice` function with a report price equal to zero. + * @custom:when The function is called. + * @custom:then The call should revert with `OracleMiddlewareWrongPrice`. + */ + function test_RevertWhen_getChainlinkDataStreamPriceInvalidTimestampWithInvalidPrice() public { + report.price = 0; + (, payload) = _encodeReport(report); + vm.expectRevert(abi.encodeWithSelector(OracleMiddlewareWrongPrice.selector, report.price)); + oracleMiddleware.i_getChainlinkDataStreamPrice{ value: report.nativeFee }(payload, 0, 0); + } + + /** + * @custom:scenario Tests the `_getChainlinkDataStreamPrice` function with a report ask price equal to zero. + * @custom:when The function is called. + * @custom:then The call should revert with `OracleMiddlewareWrongAskPrice`. + */ + function test_RevertWhen_getChainlinkDataStreamPriceInvalidTimestampWithInvalidAskPrice() public { + report.ask = 0; + (, payload) = _encodeReport(report); + vm.expectRevert(abi.encodeWithSelector(OracleMiddlewareWrongAskPrice.selector, report.ask)); + oracleMiddleware.i_getChainlinkDataStreamPrice{ value: report.nativeFee }(payload, 0, 0); + } + + /** + * @custom:scenario Tests the `_getChainlinkDataStreamPrice` function with a report bid price equal to zero. + * @custom:when The function is called. + * @custom:then The call should revert with `OracleMiddlewareWrongBidPrice`. + */ + function test_RevertWhen_getChainlinkDataStreamPriceInvalidTimestampWithInvalidBidPrice() public { + report.bid = 0; + (, payload) = _encodeReport(report); + vm.expectRevert(abi.encodeWithSelector(OracleMiddlewareWrongBidPrice.selector, report.bid)); + oracleMiddleware.i_getChainlinkDataStreamPrice{ value: report.nativeFee }(payload, 0, 0); + } + + /** + * @custom:scenario Tests the `_getChainlinkDataStreamPrice` function. + * @custom:when The function is called. + * @custom:then The verified report must match the Chainlink data streams report. + */ + function test_getChainlinkDataStreamPrice() public { + FormattedDataStreamsPrice memory formattedReport = + oracleMiddleware.i_getChainlinkDataStreamPrice{ value: report.nativeFee }(payload, 0, 0); + + assertEq(formattedReport.timestamp, report.observationsTimestamp, "Invalid observationsTimestamp"); + assertEq(int192(int256(formattedReport.price)), report.price, "Invalid price"); + assertEq(int192(int256(formattedReport.bid)), report.bid, "Invalid bid"); + assertEq(int192(int256(formattedReport.ask)), report.ask, "Invalid ask"); + } +} diff --git a/test/unit/Middlewares/Oracle/DataStreamsOracle/GetInitiateActionPrice.t.sol b/test/unit/Middlewares/Oracle/DataStreamsOracle/GetInitiateActionPrice.t.sol new file mode 100644 index 000000000..15ab64385 --- /dev/null +++ b/test/unit/Middlewares/Oracle/DataStreamsOracle/GetInitiateActionPrice.t.sol @@ -0,0 +1,124 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.26; + +import { PythStructs } from "@pythnetwork/pyth-sdk-solidity/PythStructs.sol"; +import { FixedPointMathLib } from "solady/src/utils/FixedPointMathLib.sol"; + +import { OracleMiddlewareWithDataStreamsFixture } from "../../utils/Fixtures.sol"; + +import { PriceAdjustment, PriceInfo } from "../../../../../src/interfaces/OracleMiddleware/IOracleMiddlewareTypes.sol"; + +/// @custom:feature The `_getInitiateActionPrice` function of the `OracleMiddlewareWithDataStreams`. +contract TestOracleMiddlewareWithDataStreamsGetInitiateActionPrice is OracleMiddlewareWithDataStreamsFixture { + function setUp() public override { + super.setUp(); + } + + /** + * @custom:scenario Tests the `_getInitiateActionPrice` with chainlink data stream. + * @custom:when The function is called. + * @custom:then The price info must be equal to the Chainlink data streams report. + */ + function test_getInitiateActionPriceWithDataStream() public { + PriceInfo memory price = + oracleMiddleware.i_getInitiateActionPrice{ value: report.nativeFee }(payload, PriceAdjustment.None); + + assertEq(int192(int256(price.price)), report.price, "Invalid price"); + assertEq(int192(int256(price.neutralPrice)), report.price, "Invalid neutral price"); + assertEq(uint32(price.timestamp), report.observationsTimestamp, "Invalid timestamp"); + } + + /** + * @custom:scenario Tests the `_getInitiateActionPrice` function with a Chainlink on-chain price and an ETH value. + * @custom:when The function is called under incorrect fee conditions. + * @custom:then The function should revert with the `OracleMiddlewareIncorrectFee`. + */ + function test_RevertWhen_getInitiateActionPriceWithValue() public { + vm.expectRevert(OracleMiddlewareIncorrectFee.selector); + oracleMiddleware.i_getInitiateActionPrice{ value: 1 }("", PriceAdjustment.None); + } + + /** + * @custom:scenario Tests the `_getInitiateActionPrice` function with an unsafe Pyth price + * and an outdated timestamp. + * @custom:when The function is called. + * @custom:then The call should revert with the `OracleMiddlewarePriceTooOld`. + */ + function test_RevertWhen_getInitiateActionUnsafePythPriceTooOld() public { + mockChainlinkOnChain.setLastPublishTime(0); + mockPyth.setUnsafePrice(1); + uint256 invalidPythTimestamp = block.timestamp - oracleMiddleware.getChainlinkTimeElapsedLimit() - 1; + mockPyth.setLastPublishTime(invalidPythTimestamp); + + vm.expectRevert(abi.encodeWithSelector(OracleMiddlewarePriceTooOld.selector, invalidPythTimestamp)); + oracleMiddleware.i_getInitiateActionPrice("", PriceAdjustment.None); + } + + /** + * @custom:scenario Tests the `_getInitiateActionPrice` function with an unsafe Pyth price. + * @custom:when The function is called. + * @custom:then The returned price must be equal to the provided unsafe Pyth price. + */ + function test_getInitiateActionPriceUnsafePyth() public { + mockChainlinkOnChain.setLastPublishTime(0); + int64 pythPrice = mockPyth.price(); + mockPyth.setUnsafePrice(pythPrice); + uint256 validPythTimestamp = block.timestamp - oracleMiddleware.getChainlinkTimeElapsedLimit(); + mockPyth.setLastPublishTime(validPythTimestamp); + + PythStructs.Price memory unsafePythPrice = mockPyth.getPriceUnsafe(""); + PriceInfo memory price = oracleMiddleware.i_getInitiateActionPrice("", PriceAdjustment.None); + + uint256 scaleFactor = 10 ** (oracleMiddleware.getDecimals() - FixedPointMathLib.abs(unsafePythPrice.expo)); + uint256 scaledUnsafePythPrice = uint64(unsafePythPrice.price) * scaleFactor; + + assertEq(price.price, scaledUnsafePythPrice, "Invalid price"); + assertEq(price.neutralPrice, scaledUnsafePythPrice, "Invalid neutral price"); + assertEq(price.timestamp, unsafePythPrice.publishTime, "Invalid timestamp"); + } + + /** + * @custom:scenario Tests the `_getInitiateActionPrice` function with an outdated Chainlink on-chain price. + * @custom:when The function is called. + * @custom:then The function should revert with the `OracleMiddlewarePriceTooOld`. + */ + function test_RevertWhen_getInitiateActionChainlinkPriceTooOld() public { + uint256 invalidChainlinkTimestamp = block.timestamp - oracleMiddleware.getChainlinkTimeElapsedLimit() - 1; + mockChainlinkOnChain.setLastPublishTime(invalidChainlinkTimestamp); + + vm.expectRevert(abi.encodeWithSelector(OracleMiddlewarePriceTooOld.selector, invalidChainlinkTimestamp)); + oracleMiddleware.i_getInitiateActionPrice("", PriceAdjustment.None); + } + + /** + * @custom:scenario Tests the `_getInitiateActionPrice` function with an incorrect Chainlink on-chain price. + * @custom:when The function is called. + * @custom:then The function should revert with the `OracleMiddlewareWrongPrice`. + */ + function test_RevertWhen_getInitiateActionWrongPrice() public { + int256 wrongChainlinkPrice = -1; + mockChainlinkOnChain.setLastPrice(wrongChainlinkPrice); + + int256 scaleFactor = int256(10 ** (oracleMiddleware.getDecimals() - mockChainlinkOnChain.decimals())); + + vm.expectRevert(abi.encodeWithSelector(OracleMiddlewareWrongPrice.selector, wrongChainlinkPrice * scaleFactor)); + oracleMiddleware.i_getInitiateActionPrice("", PriceAdjustment.None); + } + + /** + * @custom:scenario Tests the `_getInitiateActionPrice` function with a valid Chainlink on-chain price. + * @custom:when The function is called. + * @custom:then The returned price must be equal to the Chainlink on-chain price. + */ + function test_getInitiateActionPriceWithChainlinkOnchain() public { + (, int256 chainlinkOnChainPrice,, uint256 timestamp,) = mockChainlinkOnChain.latestRoundData(); + + uint256 adjustedPrice = uint256(chainlinkOnChainPrice) + * 10 ** uint256(oracleMiddleware.getDecimals() - mockChainlinkOnChain.decimals()); + PriceInfo memory price = oracleMiddleware.i_getInitiateActionPrice("", PriceAdjustment.None); + + assertEq(price.price, adjustedPrice, "Invalid price"); + assertEq(price.neutralPrice, adjustedPrice, "Invalid neutral price"); + assertEq(price.timestamp, timestamp, "Invalid timestamp"); + } +} diff --git a/test/unit/Middlewares/Oracle/DataStreamsOracle/GetLiquidationPrice.t.sol b/test/unit/Middlewares/Oracle/DataStreamsOracle/GetLiquidationPrice.t.sol new file mode 100644 index 000000000..79973507f --- /dev/null +++ b/test/unit/Middlewares/Oracle/DataStreamsOracle/GetLiquidationPrice.t.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.26; + +import { FixedPointMathLib } from "solady/src/utils/FixedPointMathLib.sol"; + +import { MOCK_PYTH_DATA } from "../../utils/Constants.sol"; +import { OracleMiddlewareWithDataStreamsFixture } from "../../utils/Fixtures.sol"; + +import { PriceInfo } from "../../../../../src/interfaces/OracleMiddleware/IOracleMiddlewareTypes.sol"; + +/// @custom:feature The `_getLiquidationPrice` function of the `OracleMiddlewareWithDataStreams`. +contract TestOracleMiddlewareWithDataStreamsGetLiquidationPrice is OracleMiddlewareWithDataStreamsFixture { + function setUp() public override { + super.setUp(); + } + + /** + * @custom:scenario Tests the `_getLiquidationPrice` function using a Pyth price. + * @custom:when The function is called. + * @custom:then The returned price should match the price from Pyth. + */ + function test_getLiquidationPriceWithPyth() public { + bytes[] memory pythUpdateFees = new bytes[](1); + pythUpdateFees[0] = MOCK_PYTH_DATA; + uint256 pythUpdateFee = mockPyth.getUpdateFee(pythUpdateFees); + + uint256 scaleFactor = 10 ** (oracleMiddleware.getDecimals() - FixedPointMathLib.abs(mockPyth.expo())); + uint256 pythPrice = uint256(uint64(mockPyth.price())) * scaleFactor; + PriceInfo memory price = oracleMiddleware.i_getLiquidationPrice{ value: pythUpdateFee }(MOCK_PYTH_DATA); + + assertEq(price.price, pythPrice, "Invalid price"); + assertEq(price.neutralPrice, pythPrice, "Invalid neutral price"); + assertEq(price.timestamp, mockPyth.lastPublishTime(), "Invalid timestamp"); + } + + /** + * @custom:scenario Tests the `_getLiquidationPrice` function using a Chainlink data stream price. + * @custom:when The function is called. + * @custom:then The returned price should match the report from the Chainlink data stream. + */ + function test_getLiquidationPriceWithDatastream() public { + PriceInfo memory price = oracleMiddleware.i_getLiquidationPrice{ value: report.nativeFee }(payload); + assertEq(int192(int256(price.price)), report.price, "Invalid price"); + assertEq(int192(int256(price.neutralPrice)), report.price, "Invalid neutral price"); + assertEq(uint32(price.timestamp), report.observationsTimestamp, "Invalid timestamp"); + } +} diff --git a/test/unit/Middlewares/Oracle/DataStreamsOracle/GetLowLatencyPrice.t.sol b/test/unit/Middlewares/Oracle/DataStreamsOracle/GetLowLatencyPrice.t.sol new file mode 100644 index 000000000..c6689e517 --- /dev/null +++ b/test/unit/Middlewares/Oracle/DataStreamsOracle/GetLowLatencyPrice.t.sol @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.26; + +import { OracleMiddlewareWithDataStreamsFixture } from "../../utils/Fixtures.sol"; + +import { PriceAdjustment, PriceInfo } from "../../../../../src/interfaces/OracleMiddleware/IOracleMiddlewareTypes.sol"; + +/// @custom:feature The `_getLowLatencyPrice` function of the `OracleMiddlewareWithDataStreams`. +contract TestOracleMiddlewareWithDataStreamsGetLowLatencyPrice is OracleMiddlewareWithDataStreamsFixture { + function setUp() public override { + super.setUp(); + } + + /** + * @custom:scenario Tests the `_getLowLatencyPrice` function without any adjustments. + * @custom:when The function is called. + * @custom:then The returned price should match the report from the Chainlink data stream. + */ + function test_getLowLatencyPrice() public { + PriceInfo memory price = + oracleMiddleware.i_getLowLatencyPrice{ value: report.nativeFee }(payload, 0, PriceAdjustment.None, 0); + assertEq(int192(int256(price.price)), report.price, "Invalid price"); + assertEq(int192(int256(price.neutralPrice)), report.price, "Invalid neutral price"); + assertEq(uint32(price.timestamp), report.observationsTimestamp, "Invalid timestamp"); + } + + /** + * @custom:scenario Tests the `_getLowLatencyPrice` function with an `Up` adjustment. + * @custom:when The function is called. + * @custom:then The returned price should match the report from the Chainlink data stream. + */ + function test_getLowLatencyPriceUp() public { + PriceInfo memory price = + oracleMiddleware.i_getLowLatencyPrice{ value: report.nativeFee }(payload, 0, PriceAdjustment.Up, 0); + assertEq(int192(int256(price.price)), report.ask, "Invalid price"); + assertEq(int192(int256(price.neutralPrice)), report.price, "Invalid neutral price"); + assertEq(uint32(price.timestamp), report.observationsTimestamp, "Invalid timestamp"); + } + + /** + * @custom:scenario Tests the `_getLowLatencyPrice` function with a `Down` adjustment. + * @custom:when The function is called. + * @custom:then The returned price should match the report from the Chainlink data stream. + */ + function test_getLowLatencyPriceDown() public { + PriceInfo memory price = + oracleMiddleware.i_getLowLatencyPrice{ value: report.nativeFee }(payload, 0, PriceAdjustment.Down, 0); + assertEq(int192(int256(price.price)), report.bid, "Invalid price"); + assertEq(int192(int256(price.neutralPrice)), report.price, "Invalid neutral price"); + assertEq(uint32(price.timestamp), report.observationsTimestamp, "Invalid timestamp"); + } + + /** + * @custom:scenario Tests the `_getLowLatencyPrice` function with timestamp values. + * @custom:when The function is called. + * @custom:then The returned price must be valid for the given timestamps. + */ + function test_getLowLatencyPriceWithTimestamp() public { + PriceInfo memory price = oracleMiddleware.i_getLowLatencyPrice{ value: report.nativeFee }( + payload, + report.validFromTimestamp - uint128(oracleMiddleware.getValidationDelay()), + PriceAdjustment.None, + report.observationsTimestamp + ); + assertEq(int192(int256(price.price)), report.price, "Invalid price"); + assertEq(int192(int256(price.neutralPrice)), report.price, "Invalid neutral price"); + assertEq(uint32(price.timestamp), report.observationsTimestamp, "Invalid timestamp"); + } +} diff --git a/test/unit/Middlewares/Oracle/DataStreamsOracle/SetDataStreamsRecentPriceDelay.t.sol b/test/unit/Middlewares/Oracle/DataStreamsOracle/SetDataStreamsRecentPriceDelay.t.sol new file mode 100644 index 000000000..a92aa65a0 --- /dev/null +++ b/test/unit/Middlewares/Oracle/DataStreamsOracle/SetDataStreamsRecentPriceDelay.t.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.26; + +import { OracleMiddlewareWithDataStreamsFixture } from "../../utils/Fixtures.sol"; + +/// @custom:feature The `setDataStreamsRecentPriceDelay` function of the `OracleMiddlewareWithDataStreams`. +contract TestOracleMiddlewareWithDataStreamsSetRecentPriceDelay is OracleMiddlewareWithDataStreamsFixture { + function setUp() public override { + super.setUp(); + } + + /** + * @custom:scenario Tests the `setDataStreamsRecentPriceDelay` function with a low delay. + * @custom:when The function is called with a delay value considered too low. + * @custom:then The call should revert with `OracleMiddlewareInvalidRecentPriceDelay`. + */ + function test_RevertWhen_setRecentPriceDelayLowDelay() public { + uint64 delay; // = 0 + vm.expectRevert(abi.encodeWithSelector(OracleMiddlewareInvalidRecentPriceDelay.selector, delay)); + oracleMiddleware.setDataStreamsRecentPriceDelay(delay); + } + + /** + * @custom:scenario Tests the `setDataStreamsRecentPriceDelay` function with a high delay. + * @custom:when The function is called with a delay value that exceeds the allowed limit. + * @custom:then The call should revert with `OracleMiddlewareInvalidRecentPriceDelay`. + */ + function test_RevertWhen_setRecentPriceDelayHighDelay() public { + uint64 delay = type(uint64).max; + vm.expectRevert(abi.encodeWithSelector(OracleMiddlewareInvalidRecentPriceDelay.selector, delay)); + oracleMiddleware.setDataStreamsRecentPriceDelay(delay); + } + + /** + * @custom:scenario Tests the `setDataStreamsRecentPriceDelay` function with a valid delay. + * @custom:when The function is called with a delay that meets all specified criteria. + * @custom:then The `_dataStreamsRecentPriceDelay` value must be updated to the new valid delay. + */ + function test_setRecentPriceDelayValidDelay() public { + uint64 delay = 1 minutes; + + vm.expectEmit(); + emit DataStreamsRecentPriceDelayUpdated(delay); + oracleMiddleware.setDataStreamsRecentPriceDelay(delay); + + assertEq( + oracleMiddleware.getDataStreamRecentPriceDelay(), delay, "Data stream recent price delay must be updated" + ); + } +} diff --git a/test/unit/Middlewares/Oracle/DataStreamsOracle/ValidationCost.t.sol b/test/unit/Middlewares/Oracle/DataStreamsOracle/ValidationCost.t.sol new file mode 100644 index 000000000..5b17d39b4 --- /dev/null +++ b/test/unit/Middlewares/Oracle/DataStreamsOracle/ValidationCost.t.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.26; + +import { MOCK_PYTH_DATA } from "../../utils/Constants.sol"; +import { OracleMiddlewareWithDataStreamsFixture } from "../../utils/Fixtures.sol"; + +/// @custom:feature The `validationCost` function of the `OracleMiddlewareWithDataStreams`. +contract TestOracleMiddlewareWithDataStreamsValidationCost is OracleMiddlewareWithDataStreamsFixture { + function setUp() public override { + super.setUp(); + } + + /** + * @custom:scenario Tests the `validationCost` function when using Chainlink on-chain. + * @custom:when The function is called without fee data. + * @custom:then The fee returned by the function must be equal to 0. + */ + function test_validationCostWithChainlinkOnchain() public view { + bytes[] memory pythUpdateFees = new bytes[](1); + pythUpdateFees[0] = MOCK_PYTH_DATA; + + uint256 fee = oracleMiddleware.validationCost("", actions[0]); + assertEq(fee, 0, "Invalid Chainlink onchain fee"); + } + + /** + * @custom:scenario Tests the `validationCost` function with Pyth data. + * @custom:when The function is called with a valid Pyth data. + * @custom:then The fee returned by the function must be equal to the Pyth fee. + */ + function test_validationCostWithPyth() public view { + bytes[] memory pythUpdateFees = new bytes[](1); + pythUpdateFees[0] = MOCK_PYTH_DATA; + + uint256 fee = oracleMiddleware.validationCost(MOCK_PYTH_DATA, actions[0]); + assertEq(fee, mockPyth.getUpdateFee(pythUpdateFees), "Invalid Pyth fee"); + } + + /** + * @custom:scenario Tests the `validationCost` function with a Chainlink data stream payload. + * @custom:when The function is called with a valid Chainlink data stream payload. + * @custom:then The fee returned by the function must be equal to the report's native fee. + */ + function test_validationCostWithDataStream() public view { + uint256 fee = oracleMiddleware.validationCost(payload, actions[0]); + assertEq(fee, report.nativeFee, "Invalid Chainlink data stream fee"); + } +} diff --git a/test/unit/Middlewares/Oracle/PythOracle.t.sol b/test/unit/Middlewares/Oracle/PythOracle.t.sol index 721e64a79..bd31dbd4c 100644 --- a/test/unit/Middlewares/Oracle/PythOracle.t.sol +++ b/test/unit/Middlewares/Oracle/PythOracle.t.sol @@ -4,7 +4,6 @@ pragma solidity 0.8.26; import { MOCK_PYTH_DATA } from "../utils/Constants.sol"; import { OracleMiddlewareBaseFixture } from "../utils/Fixtures.sol"; -import { PriceInfo } from "../../../../src/interfaces/OracleMiddleware/IOracleMiddlewareTypes.sol"; import { IUsdnProtocolTypes as Types } from "../../../../src/interfaces/UsdnProtocol/IUsdnProtocolTypes.sol"; /// @custom:feature The `PythOracle` specific functions @@ -18,17 +17,18 @@ contract TestOracleMiddlewarePythOracle is OracleMiddlewareBaseFixture { * @custom:given The price of the asset is $10 and the confidence interval is $30. * @custom:when The `parseAndValidatePrice` function is called with an action that uses the lower bound of the conf * interval. - * @custom:then The price is adjusted to 1 wei to avoid being negative or zero. + * @custom:then The function reverts with the error {OracleMiddlewareConfValueTooHigh}. */ function test_pythConfGreaterThanPrice() public { + uint256 validationCost = oracleMiddleware.validationCost(MOCK_PYTH_DATA, Types.ProtocolAction.ValidateDeposit); + mockPyth.setPrice(10e8); mockPyth.setConf(30e8); - // ValidateDeposit adjusts down with conf - PriceInfo memory price = oracleMiddleware.parseAndValidatePrice{ - value: oracleMiddleware.validationCost(MOCK_PYTH_DATA, Types.ProtocolAction.ValidateDeposit) - }("", uint128(block.timestamp), Types.ProtocolAction.ValidateDeposit, MOCK_PYTH_DATA); - assertEq(price.price, 1, "price should be 1"); + vm.expectRevert(OracleMiddlewareConfValueTooHigh.selector); + oracleMiddleware.parseAndValidatePrice{ value: validationCost }( + "", uint128(block.timestamp), Types.ProtocolAction.ValidateDeposit, MOCK_PYTH_DATA + ); } /** diff --git a/test/unit/Middlewares/WstethOracle/WstethOracleWithDataStreams/ParseAndValidate.t.sol b/test/unit/Middlewares/WstethOracle/WstethOracleWithDataStreams/ParseAndValidate.t.sol new file mode 100644 index 000000000..306c1e2ba --- /dev/null +++ b/test/unit/Middlewares/WstethOracle/WstethOracleWithDataStreams/ParseAndValidate.t.sol @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.26; + +import { FixedPointMathLib } from "solady/src/utils/FixedPointMathLib.sol"; + +import { MOCK_PYTH_DATA } from "../../utils/Constants.sol"; +import { WstethOracleWithDataStreamsBaseFixture } from "../../utils/Fixtures.sol"; + +import { PriceInfo } from "../../../../../src/interfaces/OracleMiddleware/IOracleMiddlewareTypes.sol"; +import { IUsdnProtocolTypes as Types } from "../../../../../src/interfaces/UsdnProtocol/IUsdnProtocolTypes.sol"; + +/** + * @custom:feature The `parseAndValidatePrice` function of the `WstethOracleWithDataStreams` + * @custom:background A deployed `WstethOracleWithDataStreams` contract. + */ +contract TestWstethOracleParseAndValidatePrice is WstethOracleWithDataStreamsBaseFixture { + uint256 internal oracleFee; + + function setUp() public override { + super.setUp(); + } + + /** + * @custom:scenario Tests the `parseAndValidatePrice` with a payload from a Chainlink data stream. + * @custom:when The function is called. + * @custom:then The returned price data must be equal to the data from the Chainlink data stream report. + */ + function test_parseAndValidatePriceWithDataStreams() public { + oracleFee = oracleMiddleware.validationCost(payload, Types.ProtocolAction.InitiateOpenPosition); + + PriceInfo memory price = oracleMiddleware.parseAndValidatePrice{ value: oracleFee }( + "", 0, Types.ProtocolAction.InitiateOpenPosition, payload + ); + + assertEq(price.price, uint192(report.price), "The returned price must be equal to the report price"); + assertEq( + price.neutralPrice, uint192(report.price), "The returned neutral price must be equal to the report price" + ); + assertEq( + price.timestamp, + uint128(report.observationsTimestamp), + "The returned timestamp must be equal to the report observationsTimestamp" + ); + } + + /** + * @custom:scenario Tests the `parseAndValidatePrice` with a price data from a Pyth price feed. + * @custom:when The function is called. + * @custom:then The returned price data must be equal to the adjusted data from the Pyth price feed. + */ + function test_parseAndValidatePriceWithPyth() public { + oracleFee = oracleMiddleware.validationCost(MOCK_PYTH_DATA, Types.ProtocolAction.Liquidation); + + PriceInfo memory price = oracleMiddleware.parseAndValidatePrice{ value: oracleFee }( + "", 0, Types.ProtocolAction.Liquidation, MOCK_PYTH_DATA + ); + uint256 adjustedWstethPythPrice = uint256(uint64(mockPyth.price())) + * 10 ** (oracleMiddleware.getDecimals() - FixedPointMathLib.abs(mockPyth.expo())) * wsteth.stEthPerToken() + / 1 ether; + + assertEq(price.price, adjustedWstethPythPrice, "The returned price must be equal to the adjusted Pyth price"); + assertEq( + price.neutralPrice, + adjustedWstethPythPrice, + "The returned neutral price must be equal to the adjusted Pyth price" + ); + assertEq( + price.timestamp, mockPyth.lastPublishTime(), "The returned timestamp must be equal to the Pyth timestamp" + ); + } + + /** + * @custom:scenario Tests the `parseAndValidatePrice` without data. + * @custom:when The function is called. + * @custom:then The returned price data must be equal to the adjusted data from the latest roundId of the Chainlink + * data feeds. + */ + function test_parseAndValidatePriceWithDataFeedsEmptyData() public { + oracleFee = oracleMiddleware.validationCost("", Types.ProtocolAction.InitiateOpenPosition); + + (, int256 answer,, uint256 updatedAt,) = mockChainlinkOnChain.latestRoundData(); + PriceInfo memory price = oracleMiddleware.parseAndValidatePrice{ value: oracleFee }( + "", 0, Types.ProtocolAction.InitiateOpenPosition, "" + ); + uint256 adjustedWstethDataFeedsPrice = uint256(answer) + * 10 ** (oracleMiddleware.getDecimals() - mockChainlinkOnChain.decimals()) * wsteth.stEthPerToken() / 1 ether; + assertEq( + price.price, + adjustedWstethDataFeedsPrice, + "The returned price must be equal to the adjusted latest roundId adjusted price" + ); + assertEq( + price.neutralPrice, + adjustedWstethDataFeedsPrice, + "The returned neutral price must be equal to the adjusted latest roundId adjusted price" + ); + assertEq(price.timestamp, updatedAt, "The returned timestamp must be equal to the latest roundId timestamp"); + } + + /** + * @custom:scenario Tests the `parseAndValidatePrice` with a specified roundId data from a Chainlink data feed. + * @custom:when The function is called. + * @custom:then The returned price data must be equal to the adjusted data from the specified + * roundId of the Chainlink data feeds. + */ + function test_parseAndValidatePriceWithDataFeedsRoundIdData() public { + uint256 lowLatencyDelay = oracleMiddleware.getLowLatencyDelay(); + skip(lowLatencyDelay + 1); + + uint80 roundId = 1; + (,,, uint256 previousRoundIdTimestamp,) = mockChainlinkOnChain.getRoundData(roundId - 1); + + mockChainlinkOnChain.setRoundTimestamp( + roundId, previousRoundIdTimestamp + oracleMiddleware.getLowLatencyDelay() + 1 + ); + oracleFee = oracleMiddleware.validationCost(abi.encode(roundId), Types.ProtocolAction.ValidateOpenPosition); + + (, int256 answer,, uint256 updatedAt,) = mockChainlinkOnChain.getRoundData(roundId); + PriceInfo memory price = oracleMiddleware.parseAndValidatePrice{ value: oracleFee }( + "", uint128(previousRoundIdTimestamp), Types.ProtocolAction.ValidateOpenPosition, abi.encode(roundId) + ); + uint256 adjustedWstethDataFeedsPrice = uint256(answer) + * 10 ** (oracleMiddleware.getDecimals() - mockChainlinkOnChain.decimals()) * wsteth.stEthPerToken() / 1 ether; + + assertEq( + price.price, + adjustedWstethDataFeedsPrice, + "The returned price must be equal to the specified roundId adjusted price" + ); + assertEq( + price.neutralPrice, + adjustedWstethDataFeedsPrice, + "The returned neutral price must be equal to the specified roundId adjusted price" + ); + assertEq(price.timestamp, updatedAt, "The returned timestamp must be equal to specified roundId timestamp"); + } +} diff --git a/test/unit/Middlewares/WusdnToEthOracle/ParseAndValidate.t.sol b/test/unit/Middlewares/WusdnToEthOracle/ParseAndValidate.t.sol index af9356f67..a5f860f2a 100644 --- a/test/unit/Middlewares/WusdnToEthOracle/ParseAndValidate.t.sol +++ b/test/unit/Middlewares/WusdnToEthOracle/ParseAndValidate.t.sol @@ -10,7 +10,7 @@ import { PriceInfo } from "../../../../src/interfaces/OracleMiddleware/IOracleMi import { IUsdnProtocolTypes as Types } from "../../../../src/interfaces/UsdnProtocol/IUsdnProtocolTypes.sol"; /** - * @custom:feature The `parseAndValidatePrice` function of `WusdnToEthOracleMiddleware` + * @custom:feature The `parseAndValidatePrice` function of `WusdnToEthOracleMiddlewareWithPyth` * @custom:background Given the price ETH is 2000 USD * @custom:and The confidence interval is 20 USD * @custom:and The USDN divisor is 9e17 diff --git a/test/unit/Middlewares/utils/Constants.sol b/test/unit/Middlewares/utils/Constants.sol index 5f00af95e..7a86735fd 100644 --- a/test/unit/Middlewares/utils/Constants.sol +++ b/test/unit/Middlewares/utils/Constants.sol @@ -17,3 +17,7 @@ uint48 constant REDSTONE_ETH_TIMESTAMP = 1_717_684_100; // since redstone parses from the end of the calldata bytes constant REDSTONE_ETH_DATA = hex"00000000000000004554480000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000595cd5e4d5018fedf35ba00000002000000196c710f6ee933d08f86e99def2c6c64684808705691289aef63503f54bf8b9202d21cde463aeef0830cea7795a03cbebd92d51a7eb13134129fc82e3cac26e2e1b4554480000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000595cd4a962018fedf35ba0000000200000015bd816c1b59f67af5990b834c572b1e403a6a7ffcbc78e13510a0b7de750e02a4d0c641de3797cc2225fd6064f7445d0d77808ec620bd96f84e298bea049ac2f1c4554480000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000595cd5e4d5018fedf35ba000000020000001ab05ff923cef4ea56a1b77af4ea5f2700cab64628b44aaf460b7d9950dff4c1e74da926233820b52b78644fce28a97dc7b88c25bbe1dbd173ff69e8e3c1dcef01b0003000000000002ed57011e0000"; + +uint256 constant STREAM_WSTETH_PRICE = 2300 ether; +bytes32 constant MOCK_STREAM_V3 = 0x0003000000000000000000000000000000000000000000000000000000000000; +bytes32 constant MOCK_STREAM_V4 = 0x0004000000000000000000000000000000000000000000000000000000000000; diff --git a/test/unit/Middlewares/utils/Fixtures.sol b/test/unit/Middlewares/utils/Fixtures.sol index 66ad9d704..3b8c82fa1 100644 --- a/test/unit/Middlewares/utils/Fixtures.sol +++ b/test/unit/Middlewares/utils/Fixtures.sol @@ -10,17 +10,25 @@ import { OracleMiddlewareHandler } from "../utils/Handler.sol"; import { OracleMiddlewareWithRedstoneHandler } from "../utils/HandlerWithRedstone.sol"; import { MockChainlinkOnChain } from "../utils/MockChainlinkOnChain.sol"; import { MockPyth } from "../utils/MockPyth.sol"; - -import { WstEthOracleMiddleware } from "../../../../src/OracleMiddleware/WstEthOracleMiddleware.sol"; -import { WusdnToEthOracleMiddleware } from "../../../../src/OracleMiddleware/WusdnToEthOracleMiddleware.sol"; +import { MOCK_STREAM_V3, STREAM_WSTETH_PRICE } from "./Constants.sol"; +import { OracleMiddlewareWithDataStreamsHandler } from "./Handler.sol"; +import { MockFeeManager } from "./MockFeeManager.sol"; +import { MockStreamVerifierProxy } from "./MockStreamVerifierProxy.sol"; + +import { WstEthOracleMiddlewareWithDataStreams } from + "../../../../src/OracleMiddleware/WstEthOracleMiddlewareWithDataStreams.sol"; +import { WstEthOracleMiddlewareWithPyth } from "../../../../src/OracleMiddleware/WstEthOracleMiddlewareWithPyth.sol"; +import { WusdnToEthOracleMiddlewareWithPyth } from + "../../../../src/OracleMiddleware/WusdnToEthOracleMiddlewareWithPyth.sol"; import { Usdn } from "../../../../src/Usdn/Usdn.sol"; import { IOracleMiddlewareErrors } from "../../../../src/interfaces/OracleMiddleware/IOracleMiddlewareErrors.sol"; import { IOracleMiddlewareEvents } from "../../../../src/interfaces/OracleMiddleware/IOracleMiddlewareEvents.sol"; +import { IVerifierProxy } from "../../../../src/interfaces/OracleMiddleware/IVerifierProxy.sol"; import { IUsdnProtocolTypes as Types } from "../../../../src/interfaces/UsdnProtocol/IUsdnProtocolTypes.sol"; /** * @title ActionsFixture - * @dev All protocol actions + * @dev All protocol actions. */ contract ActionsFixture is IOracleMiddlewareErrors, IOracleMiddlewareEvents { // all action types @@ -41,7 +49,7 @@ contract ActionsFixture is IOracleMiddlewareErrors, IOracleMiddlewareEvents { /** * @title OracleMiddlewareBaseFixture - * @dev Utils for testing the oracle middleware + * @dev Utils for testing the oracle middleware. */ contract OracleMiddlewareBaseFixture is BaseFixture, ActionsFixture { MockPyth internal mockPyth; @@ -88,7 +96,7 @@ contract OracleMiddlewareBaseFixture is BaseFixture, ActionsFixture { /** * @title OracleMiddlewareWithRedstoneFixture - * @dev Utils for testing the oracle middleware with redstone support + * @dev Utils for testing the oracle middleware with redstone support. */ contract OracleMiddlewareWithRedstoneFixture is BaseFixture, ActionsFixture { MockPyth internal mockPyth; @@ -133,14 +141,106 @@ contract OracleMiddlewareWithRedstoneFixture is BaseFixture, ActionsFixture { } } +/** + * @title OracleMiddlewareWithDataStreamsFixture + * @dev Utils for testing the oracle middleware with chainlink data streams support. + */ +contract OracleMiddlewareWithDataStreamsFixture is BaseFixture, ActionsFixture { + MockPyth internal mockPyth; + MockChainlinkOnChain internal mockChainlinkOnChain; + MockStreamVerifierProxy internal mockStreamVerifierProxy; + MockFeeManager internal mockFeeManager; + OracleMiddlewareWithDataStreamsHandler internal oracleMiddleware; + IVerifierProxy.ReportV3 internal report; + + uint256 internal chainlinkTimeElapsedLimit = 1 hours; + bytes internal reportData; + bytes internal payload; + + bytes32[3] internal emptySignature; + + function setUp() public virtual { + vm.warp(1_704_063_600); + + mockPyth = new MockPyth(); + mockChainlinkOnChain = new MockChainlinkOnChain(); + mockFeeManager = new MockFeeManager(); + mockStreamVerifierProxy = new MockStreamVerifierProxy(address(mockFeeManager)); + + oracleMiddleware = new OracleMiddlewareWithDataStreamsHandler( + address(mockPyth), + PYTH_ETH_USD, + address(mockChainlinkOnChain), + chainlinkTimeElapsedLimit, + address(mockStreamVerifierProxy), + MOCK_STREAM_V3 + ); + + report = IVerifierProxy.ReportV3({ + feedId: MOCK_STREAM_V3, + validFromTimestamp: uint32(block.timestamp), + observationsTimestamp: uint32(block.timestamp), + nativeFee: 0.001 ether, + linkFee: 0, + expiresAt: uint32(block.timestamp) + 100, + price: int192(int256(STREAM_WSTETH_PRICE)), + bid: int192(int256(STREAM_WSTETH_PRICE)) - 1, + ask: int192(int256(STREAM_WSTETH_PRICE)) + 1 + }); + + (reportData, payload) = _encodeReport(report); + } + + function test_setUp() public { + assertEq(address(oracleMiddleware.getPyth()), address(mockPyth)); + assertEq(address(oracleMiddleware.getPriceFeed()), address(mockChainlinkOnChain)); + + assertEq(mockPyth.lastPublishTime(), block.timestamp); + assertEq(mockChainlinkOnChain.latestTimestamp(), block.timestamp); + + /* ----------------------------- Test pyth mock ----------------------------- */ + bytes[] memory updateData = new bytes[](1); + bytes32[] memory priceIds = new bytes32[](1); + PythStructs.PriceFeed[] memory priceFeeds = mockPyth.parsePriceFeedUpdatesUnique{ + value: mockPyth.getUpdateFee(updateData) + }(updateData, priceIds, 1000, 0); + + assertEq(priceFeeds.length, 1); + assertEq(priceFeeds[0].price.price, 2000e8); + assertEq(priceFeeds[0].price.conf, 20e8); + assertEq(priceFeeds[0].price.expo, -8); + assertEq(priceFeeds[0].price.publishTime, 1000); + + /* ---------------------- Test Chainlink on chain mock ---------------------- */ + (, int256 price,, uint256 updatedAt,) = mockChainlinkOnChain.latestRoundData(); + assertEq(price, 2000e8); + assertEq(updatedAt, block.timestamp); + + /* ------------------- Test Chainlink stream verifier mock ------------------ */ + assertEq(address(mockStreamVerifierProxy.s_feeManager()), address(mockFeeManager)); + + /* --------------------- Test Chainlink fee manager mock -------------------- */ + assertEq(mockFeeManager.i_nativeAddress(), address(1)); + } + + function _encodeReport(IVerifierProxy.ReportV3 memory reportV3) + internal + view + returns (bytes memory reportData_, bytes memory payload_) + { + reportData_ = abi.encode(reportV3); + payload_ = abi.encode(emptySignature, reportData_); + } +} + /** * @title WstethBaseFixture - * @dev Utils for testing the wsteth oracle + * @dev Utils for testing the wsteth oracle. */ contract WstethBaseFixture is BaseFixture, ActionsFixture { MockPyth internal mockPyth; MockChainlinkOnChain internal mockChainlinkOnChain; - WstEthOracleMiddleware public wstethOracle; + WstEthOracleMiddlewareWithPyth public wstethOracle; WstETH public wsteth; function setUp() public virtual { @@ -149,8 +249,9 @@ contract WstethBaseFixture is BaseFixture, ActionsFixture { mockPyth = new MockPyth(); mockChainlinkOnChain = new MockChainlinkOnChain(); wsteth = new WstETH(); - wstethOracle = - new WstEthOracleMiddleware(address(mockPyth), 0, address(mockChainlinkOnChain), address(wsteth), 1 hours); + wstethOracle = new WstEthOracleMiddlewareWithPyth( + address(mockPyth), 0, address(mockChainlinkOnChain), address(wsteth), 1 hours + ); } function test_setUp() public { @@ -184,11 +285,11 @@ contract WstethBaseFixture is BaseFixture, ActionsFixture { } } -/// @dev Utils for testing the short oracle middleware +/// @dev Utils for testing the short oracle middleware. contract WusdnToEthBaseFixture is BaseFixture, ActionsFixture { MockPyth internal mockPyth; MockChainlinkOnChain internal mockChainlinkOnChain; - WusdnToEthOracleMiddleware public middleware; + WusdnToEthOracleMiddlewareWithPyth public middleware; Usdn public usdn; function setUp() public virtual { @@ -198,8 +299,9 @@ contract WusdnToEthBaseFixture is BaseFixture, ActionsFixture { mockChainlinkOnChain = new MockChainlinkOnChain(); usdn = new Usdn(address(this), address(this)); usdn.rebase(9e17); - middleware = - new WusdnToEthOracleMiddleware(address(mockPyth), 0, address(mockChainlinkOnChain), address(usdn), 1 hours); + middleware = new WusdnToEthOracleMiddlewareWithPyth( + address(mockPyth), 0, address(mockChainlinkOnChain), address(usdn), 1 hours + ); } function test_setUp() public { @@ -231,3 +333,63 @@ contract WusdnToEthBaseFixture is BaseFixture, ActionsFixture { assertEq(usdn.divisor(), 9e17, "USDN divisor"); } } + +/// @dev Utils for testing the wsteth oracle middleware with Chainlink data streams. +contract WstethOracleWithDataStreamsBaseFixture is BaseFixture, ActionsFixture { + MockPyth internal mockPyth; + MockChainlinkOnChain internal mockChainlinkOnChain; + MockStreamVerifierProxy internal mockStreamVerifierProxy; + MockFeeManager internal mockFeeManager; + WstEthOracleMiddlewareWithDataStreams internal oracleMiddleware; + IVerifierProxy.ReportV3 internal report; + WstETH public wsteth; + + uint256 internal chainlinkTimeElapsedLimit = 1 hours; + bytes internal reportData; + bytes internal payload; + + bytes32[3] internal emptySignature; + + function setUp() public virtual { + vm.warp(1_704_063_600); + + mockPyth = new MockPyth(); + mockChainlinkOnChain = new MockChainlinkOnChain(); + mockFeeManager = new MockFeeManager(); + mockStreamVerifierProxy = new MockStreamVerifierProxy(address(mockFeeManager)); + wsteth = new WstETH(); + + oracleMiddleware = new WstEthOracleMiddlewareWithDataStreams( + address(mockPyth), + PYTH_ETH_USD, + address(mockChainlinkOnChain), + address(wsteth), + chainlinkTimeElapsedLimit, + address(mockStreamVerifierProxy), + MOCK_STREAM_V3 + ); + + report = IVerifierProxy.ReportV3({ + feedId: MOCK_STREAM_V3, + validFromTimestamp: uint32(block.timestamp), + observationsTimestamp: uint32(block.timestamp), + nativeFee: 0.001 ether, + linkFee: 0, + expiresAt: uint32(block.timestamp) + 100, + price: int192(int256(STREAM_WSTETH_PRICE)), + bid: int192(int256(STREAM_WSTETH_PRICE)) - 1, + ask: int192(int256(STREAM_WSTETH_PRICE)) + 1 + }); + + (reportData, payload) = _encodeReport(report); + } + + function _encodeReport(IVerifierProxy.ReportV3 memory reportV3) + internal + view + returns (bytes memory reportData_, bytes memory payload_) + { + reportData_ = abi.encode(reportV3); + payload_ = abi.encode(emptySignature, reportData_); + } +} diff --git a/test/unit/Middlewares/utils/Handler.sol b/test/unit/Middlewares/utils/Handler.sol index e8d4c71d6..757d92cdf 100644 --- a/test/unit/Middlewares/utils/Handler.sol +++ b/test/unit/Middlewares/utils/Handler.sol @@ -3,13 +3,21 @@ pragma solidity 0.8.26; import { Test } from "forge-std/Test.sol"; -import { OracleMiddleware } from "../../../../src/OracleMiddleware/OracleMiddleware.sol"; +import { OracleMiddlewareWithDataStreams } from "../../../../src/OracleMiddleware/OracleMiddlewareWithDataStreams.sol"; +import { OracleMiddlewareWithPyth } from "../../../../src/OracleMiddleware/OracleMiddlewareWithPyth.sol"; +import { IFeeManager } from "../../../../src/interfaces/OracleMiddleware/IFeeManager.sol"; +import { + FormattedDataStreamsPrice, + FormattedPythPrice, + PriceAdjustment, + PriceInfo +} from "../../../../src/interfaces/OracleMiddleware/IOracleMiddlewareTypes.sol"; -contract OracleMiddlewareHandler is OracleMiddleware, Test { +contract OracleMiddlewareHandler is OracleMiddlewareWithPyth, Test { bool internal _mockRedstonePriceZero; constructor(address pythContract, bytes32 pythFeedId, address chainlinkPriceFeed, uint256 chainlinkTimeElapsedLimit) - OracleMiddleware(pythContract, pythFeedId, chainlinkPriceFeed, chainlinkTimeElapsedLimit) + OracleMiddlewareWithPyth(pythContract, pythFeedId, chainlinkPriceFeed, chainlinkTimeElapsedLimit) { } function setMockRedstonePriceZero(bool mock) external { @@ -20,3 +28,76 @@ contract OracleMiddlewareHandler is OracleMiddleware, Test { return _isPythData(data); } } + +contract OracleMiddlewareWithDataStreamsHandler is OracleMiddlewareWithDataStreams, Test { + constructor( + address pythContract, + bytes32 pythFeedId, + address chainlinkPriceFeed, + uint256 chainlinkTimeElapsedLimit, + address chainlinkProxyVerifierAddress, + bytes32 chainlinkStreamId + ) + OracleMiddlewareWithDataStreams( + pythContract, + pythFeedId, + chainlinkPriceFeed, + chainlinkTimeElapsedLimit, + chainlinkProxyVerifierAddress, + chainlinkStreamId + ) + { } + + /* -------------------------------------------------------------------------- */ + /* ChainlinkDataStreamsOracle */ + /* -------------------------------------------------------------------------- */ + + function i_getChainlinkDataStreamPrice(bytes calldata payload, uint128 targetTimestamp, uint128 targetLimit) + external + payable + returns (FormattedDataStreamsPrice memory formattedPrice_) + { + return _getChainlinkDataStreamPrice(payload, targetTimestamp, targetLimit); + } + + function i_getChainlinkDataStreamFeeData(bytes calldata payload) + external + view + returns (IFeeManager.Asset memory feeData_) + { + return _getChainlinkDataStreamFeeData(payload); + } + + /* -------------------------------------------------------------------------- */ + /* OracleMiddlewareWithDataStreams */ + /* -------------------------------------------------------------------------- */ + + function i_getLowLatencyPrice( + bytes calldata payload, + uint128 actionTimestamp, + PriceAdjustment dir, + uint128 targetLimit + ) external payable returns (PriceInfo memory price_) { + return _getLowLatencyPrice(payload, actionTimestamp, dir, targetLimit); + } + + function i_getInitiateActionPrice(bytes calldata data, PriceAdjustment dir) + external + payable + returns (PriceInfo memory price_) + { + return _getInitiateActionPrice(data, dir); + } + + function i_getLiquidationPrice(bytes calldata data) external payable returns (PriceInfo memory price_) { + return _getLiquidationPrice(data); + } + + function i_adjustDataStreamPrice(FormattedDataStreamsPrice memory formattedReport, PriceAdjustment dir) + external + pure + returns (PriceInfo memory price_) + { + return _adjustDataStreamPrice(formattedReport, dir); + } +} diff --git a/test/unit/Middlewares/utils/MockFeeManager.sol b/test/unit/Middlewares/utils/MockFeeManager.sol new file mode 100644 index 000000000..ae6f27105 --- /dev/null +++ b/test/unit/Middlewares/utils/MockFeeManager.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.26; + +import { IFeeManager } from "../../../../src/interfaces/OracleMiddleware/IFeeManager.sol"; +import { IVerifierProxy } from "../../../../src/interfaces/OracleMiddleware/IVerifierProxy.sol"; + +interface IMockFeeManager { + function i_nativeAddress() external pure returns (address); + + function getFeeAndReward(address, bytes calldata reportData, address) + external + pure + returns (IFeeManager.Asset memory feeData_, IFeeManager.Asset memory rewardData_, uint256 discount_); +} + +contract MockFeeManager is IMockFeeManager { + address internal constant NATIVE_ADDRESS = address(1); + + function i_nativeAddress() external pure returns (address) { + return NATIVE_ADDRESS; + } + + function getFeeAndReward(address, bytes calldata reportData, address) + external + pure + returns (IFeeManager.Asset memory feeData_, IFeeManager.Asset memory rewardData_, uint256 discount_) + { + IVerifierProxy.ReportV3 memory report = abi.decode(reportData, (IVerifierProxy.ReportV3)); + feeData_ = IFeeManager.Asset({ amount: report.nativeFee, assetAddress: NATIVE_ADDRESS }); + return (feeData_, rewardData_, discount_); + } +} diff --git a/test/unit/Middlewares/utils/MockStreamVerifierProxy.sol b/test/unit/Middlewares/utils/MockStreamVerifierProxy.sol new file mode 100644 index 000000000..9b63c18a3 --- /dev/null +++ b/test/unit/Middlewares/utils/MockStreamVerifierProxy.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.26; + +import { IVerifierProxy } from "../../../../src/interfaces/OracleMiddleware/IVerifierProxy.sol"; + +import { IMockFeeManager } from "./MockFeeManager.sol"; + +contract MockStreamVerifierProxy { + IMockFeeManager public s_feeManager; + + constructor(address feeManagerAddress) { + s_feeManager = IMockFeeManager(feeManagerAddress); + } + + function setFeeManager(IMockFeeManager newFeeManager) external { + s_feeManager = newFeeManager; + } + + function verify(bytes calldata payload, bytes calldata) external payable returns (bytes memory reportData_) { + IMockFeeManager feeManager = s_feeManager; + + (, reportData_) = abi.decode(payload, (bytes32[3], bytes)); + + if (address(feeManager) != address(0)) { + IVerifierProxy.ReportV3 memory report = abi.decode(reportData_, (IVerifierProxy.ReportV3)); + require(msg.value == report.nativeFee, "Wrong native fee"); + } + } + + receive() external payable { } +} diff --git a/test/unit/Rebalancer/utils/Fixtures.sol b/test/unit/Rebalancer/utils/Fixtures.sol index 138ba31a6..b19a243b0 100644 --- a/test/unit/Rebalancer/utils/Fixtures.sol +++ b/test/unit/Rebalancer/utils/Fixtures.sol @@ -12,8 +12,9 @@ import { WstETH } from "../../../utils/WstEth.sol"; import { MockOracleMiddleware } from "../../UsdnProtocol/utils/MockOracleMiddleware.sol"; import { RebalancerHandler } from "../utils/Handler.sol"; -import { LiquidationRewardsManager } from "../../../../src/LiquidationRewardsManager/LiquidationRewardsManager.sol"; -import { WstEthOracleMiddleware } from "../../../../src/OracleMiddleware/WstEthOracleMiddleware.sol"; +import { LiquidationRewardsManagerWstEth } from + "../../../../src/LiquidationRewardsManager/LiquidationRewardsManagerWstEth.sol"; +import { WstEthOracleMiddlewareWithPyth } from "../../../../src/OracleMiddleware/WstEthOracleMiddlewareWithPyth.sol"; import { Usdn } from "../../../../src/Usdn/Usdn.sol"; import { UsdnProtocolFallback } from "../../../../src/UsdnProtocol/UsdnProtocolFallback.sol"; import { UsdnProtocolImpl } from "../../../../src/UsdnProtocol/UsdnProtocolImpl.sol"; @@ -39,7 +40,7 @@ contract RebalancerFixture is Sdex public sdex; WstETH public wstETH; MockOracleMiddleware public oracleMiddleware; - LiquidationRewardsManager public liquidationRewardsManager; + LiquidationRewardsManagerWstEth public liquidationRewardsManager; RebalancerHandler public rebalancer; IUsdnProtocol public usdnProtocol; Types.PreviousActionsData internal EMPTY_PREVIOUS_DATA = @@ -51,13 +52,14 @@ contract RebalancerFixture is wstETH = new WstETH(); sdex = new Sdex(); oracleMiddleware = new MockOracleMiddleware(); - liquidationRewardsManager = new LiquidationRewardsManager(wstETH); + liquidationRewardsManager = new LiquidationRewardsManagerWstEth(wstETH); - UsdnProtocolFallback protocolFallback = new UsdnProtocolFallback(); + UsdnProtocolFallback protocolFallback = + new UsdnProtocolFallback(DefaultConfig.MAX_SDEX_BURN_RATIO, DefaultConfig.MAX_MIN_LONG_POSITION); UsdnProtocolImpl implementation = new UsdnProtocolImpl(); _setPeripheralContracts( - WstEthOracleMiddleware(address(oracleMiddleware)), + WstEthOracleMiddlewareWithPyth(address(oracleMiddleware)), liquidationRewardsManager, usdn, wstETH, diff --git a/test/unit/UsdnProtocol/Admin.t.sol b/test/unit/UsdnProtocol/Admin.t.sol index ce1a4d2ea..e5bc88239 100644 --- a/test/unit/UsdnProtocol/Admin.t.sol +++ b/test/unit/UsdnProtocol/Admin.t.sol @@ -15,7 +15,7 @@ import { UsdnProtocolConstantsLibrary as Constants } from "../../../src/UsdnProtocol/libraries/UsdnProtocolConstantsLibrary.sol"; import { ILiquidationRewardsManager } from "../../../src/interfaces/LiquidationRewardsManager/ILiquidationRewardsManager.sol"; -import { IOracleMiddleware } from "../../../src/interfaces/OracleMiddleware/IOracleMiddleware.sol"; +import { IOracleMiddlewareWithPyth } from "../../../src/interfaces/OracleMiddleware/IOracleMiddlewareWithPyth.sol"; import { IRebalancer } from "../../../src/interfaces/Rebalancer/IRebalancer.sol"; import { IRebalancerEvents } from "../../../src/interfaces/Rebalancer/IRebalancerEvents.sol"; @@ -38,7 +38,7 @@ contract TestUsdnProtocolAdmin is UsdnProtocolBaseFixture, IRebalancerEvents { */ function test_RevertWhen_nonAdminWalletCallAdminFunctions() public { vm.expectRevert(customError("SET_EXTERNAL_ROLE")); - protocol.setOracleMiddleware(IOracleMiddleware(address(1))); + protocol.setOracleMiddleware(IOracleMiddlewareWithPyth(address(1))); vm.expectRevert(customError("SET_PROTOCOL_PARAMS_ROLE")); protocol.setMinLeverage(0); @@ -120,7 +120,7 @@ contract TestUsdnProtocolAdmin is UsdnProtocolBaseFixture, IRebalancerEvents { // zero address disallowed vm.expectRevert(UsdnProtocolInvalidMiddlewareAddress.selector); // set middleware - protocol.setOracleMiddleware(IOracleMiddleware(address(0))); + protocol.setOracleMiddleware(IOracleMiddlewareWithPyth(address(0))); } /** @@ -151,7 +151,7 @@ contract TestUsdnProtocolAdmin is UsdnProtocolBaseFixture, IRebalancerEvents { uint16 invalidValue = uint16(protocol.getLowLatencyValidatorDeadline() - 1); mockInvalidOracleMiddleware.setLowLatencyDelay(invalidValue); vm.expectRevert(UsdnProtocolInvalidMiddlewareLowLatencyDelay.selector); - protocol.setOracleMiddleware(IOracleMiddleware(address(mockInvalidOracleMiddleware))); + protocol.setOracleMiddleware(IOracleMiddlewareWithPyth(address(mockInvalidOracleMiddleware))); } /** @@ -484,7 +484,7 @@ contract TestUsdnProtocolAdmin is UsdnProtocolBaseFixture, IRebalancerEvents { * @custom:then The call reverts */ function test_RevertWhen_setSdexBurnOnDepositRatioWithMax() public adminPrank { - uint32 aboveMax = uint32(Constants.MAX_SDEX_BURN_RATIO + 1); + uint32 aboveMax = uint32(MAX_SDEX_BURN_RATIO + 1); vm.expectRevert(UsdnProtocolInvalidBurnSdexOnDepositRatio.selector); protocol.setSdexBurnOnDepositRatio(aboveMax); @@ -955,7 +955,7 @@ contract TestUsdnProtocolAdmin is UsdnProtocolBaseFixture, IRebalancerEvents { */ function test_RevertWhen_invalidSetMinLongPosition() public adminPrank { vm.expectRevert(UsdnProtocolInvalidMinLongPosition.selector); - protocol.setMinLongPosition(10 ether + 1); + protocol.setMinLongPosition(MAX_MIN_LONG_POSITION + 1); } /** diff --git a/test/unit/UsdnProtocol/Core/_Funding.t.sol b/test/unit/UsdnProtocol/Core/_Funding.t.sol index 5443ae558..b57530732 100644 --- a/test/unit/UsdnProtocol/Core/_Funding.t.sol +++ b/test/unit/UsdnProtocol/Core/_Funding.t.sol @@ -271,7 +271,7 @@ contract TestUsdnProtocolCoreFunding is UsdnProtocolBaseFixture { int256 ema ) public { s.fundingSF = bound(sf, 0, 10 ** Constants.FUNDING_SF_DECIMALS); - // as a safe upper bound, we use the total supply of eth with a leverage max of 10x + // as a safe upper bound, we use the total supply of ETH with a leverage max of 10x s.totalExpo = bound(totalExpo, 1 ether, 1.2e9 ether); s.balanceLong = bound(balanceLong, 0, s.totalExpo); s.balanceVault = bound(balanceVault, 0, 120e6 ether); diff --git a/test/unit/UsdnProtocol/Initialize.t.sol b/test/unit/UsdnProtocol/Initialize.t.sol index 172731745..2ea295141 100644 --- a/test/unit/UsdnProtocol/Initialize.t.sol +++ b/test/unit/UsdnProtocol/Initialize.t.sol @@ -9,9 +9,8 @@ import { ADMIN, DEPLOYER } from "../../utils/Constants.sol"; import { UsdnProtocolBaseFixture } from "./utils/Fixtures.sol"; import { UsdnProtocolHandler } from "./utils/Handler.sol"; -import { WstEthOracleMiddleware } from "../../../src/OracleMiddleware/WstEthOracleMiddleware.sol"; +import { WstEthOracleMiddlewareWithPyth } from "../../../src/OracleMiddleware/WstEthOracleMiddlewareWithPyth.sol"; import { Usdn } from "../../../src/Usdn/Usdn.sol"; -import { UsdnProtocolFallback } from "../../../src/UsdnProtocol/UsdnProtocolFallback.sol"; import { UsdnProtocolConstantsLibrary as Constants } from "../../../src/UsdnProtocol/libraries/UsdnProtocolConstantsLibrary.sol"; import { IUsdnProtocol } from "../../../src/interfaces/UsdnProtocol/IUsdnProtocol.sol"; @@ -30,15 +29,14 @@ contract TestUsdnProtocolInitialize is UsdnProtocolBaseFixture { vm.startPrank(ADMIN); usdn = new Usdn(address(0), address(0)); - UsdnProtocolFallback protocolFallback = new UsdnProtocolFallback(); - UsdnProtocolHandler test = new UsdnProtocolHandler(); + UsdnProtocolHandler test = new UsdnProtocolHandler(MAX_SDEX_BURN_RATIO, MAX_MIN_LONG_POSITION); _setPeripheralContracts( - WstEthOracleMiddleware(address(oracleMiddleware)), + WstEthOracleMiddlewareWithPyth(address(oracleMiddleware)), liquidationRewardsManager, usdn, wstETH, - address(protocolFallback), + address(0), ADMIN, sdex ); diff --git a/test/unit/UsdnProtocol/Proxy/Proxy.t.sol b/test/unit/UsdnProtocol/Proxy/Proxy.t.sol index fe2425874..efa42d23e 100644 --- a/test/unit/UsdnProtocol/Proxy/Proxy.t.sol +++ b/test/unit/UsdnProtocol/Proxy/Proxy.t.sol @@ -105,10 +105,13 @@ contract TestUsdnProtocolProxy is UsdnProtocolBaseFixture { * @custom:and The new implementation function should return true */ function test_upgrade() public { + // todo: restore test after the ShortDN deployment + vm.skip(true); + _storageSnapshot(); vm.startPrank(ADMIN); - UsdnProtocolFallback newProtocolFallback = new UsdnProtocolFallback(); + UsdnProtocolFallback newProtocolFallback = new UsdnProtocolFallback(MAX_SDEX_BURN_RATIO, MAX_MIN_LONG_POSITION); UsdnProtocolImplV2 newImplementation = new UsdnProtocolImplV2(); vm.expectEmit(); diff --git a/test/unit/UsdnProtocol/Storage/Constructor.t.sol b/test/unit/UsdnProtocol/Storage/Constructor.t.sol index 21687c7a2..8541b0b08 100644 --- a/test/unit/UsdnProtocol/Storage/Constructor.t.sol +++ b/test/unit/UsdnProtocol/Storage/Constructor.t.sol @@ -27,7 +27,7 @@ contract TestUsdnProtocolStorageConstructor is UsdnProtocolBaseFixture { function setUp() public { _setUp(DEFAULT_PARAMS); implementation = new UsdnProtocolImpl(); - protocolFallback = new UsdnProtocolFallback(); + protocolFallback = new UsdnProtocolFallback(MAX_SDEX_BURN_RATIO, MAX_MIN_LONG_POSITION); } /** diff --git a/test/unit/UsdnProtocol/Vault/_CalcSdexToBurn.t.sol b/test/unit/UsdnProtocol/Vault/_CalcSdexToBurn.t.sol index 15e4e1245..e122dad46 100644 --- a/test/unit/UsdnProtocol/Vault/_CalcSdexToBurn.t.sol +++ b/test/unit/UsdnProtocol/Vault/_CalcSdexToBurn.t.sol @@ -22,7 +22,7 @@ contract TestUsdnProtocolCalcUsdnPrice is UsdnProtocolBaseFixture { * @custom:then The correct amount of SDEX to burn is returned */ function test_calcSdexToBurn() public view { - uint32 burnRatio = protocol.getSdexBurnOnDepositRatio(); + uint64 burnRatio = protocol.getSdexBurnOnDepositRatio(); uint256 burnRatioDivisor = Constants.SDEX_BURN_ON_DEPOSIT_DIVISOR; uint8 usdnDecimals = Constants.TOKENS_DECIMALS; diff --git a/test/unit/UsdnProtocol/utils/Fixtures.sol b/test/unit/UsdnProtocol/utils/Fixtures.sol index 9edb8f943..9b9cd28df 100644 --- a/test/unit/UsdnProtocol/utils/Fixtures.sol +++ b/test/unit/UsdnProtocol/utils/Fixtures.sol @@ -15,10 +15,10 @@ import { RebalancerHandler } from "../../Rebalancer/utils/Handler.sol"; import { UsdnProtocolHandler } from "./Handler.sol"; import { MockOracleMiddleware } from "./MockOracleMiddleware.sol"; -import { LiquidationRewardsManager } from "../../../../src/LiquidationRewardsManager/LiquidationRewardsManager.sol"; -import { WstEthOracleMiddleware } from "../../../../src/OracleMiddleware/WstEthOracleMiddleware.sol"; +import { LiquidationRewardsManagerWstEth } from + "../../../../src/LiquidationRewardsManager/LiquidationRewardsManagerWstEth.sol"; +import { WstEthOracleMiddlewareWithPyth } from "../../../../src/OracleMiddleware/WstEthOracleMiddlewareWithPyth.sol"; import { Usdn } from "../../../../src/Usdn/Usdn.sol"; -import { UsdnProtocolFallback } from "../../../../src/UsdnProtocol/UsdnProtocolFallback.sol"; import { UsdnProtocolActionsUtilsLibrary as ActionUtils } from "../../../../src/UsdnProtocol/libraries/UsdnProtocolActionsUtilsLibrary.sol"; import { UsdnProtocolConstantsLibrary as Constants } from @@ -99,7 +99,7 @@ contract UsdnProtocolBaseFixture is Sdex public sdex; WstETH public wstETH; MockOracleMiddleware public oracleMiddleware; - LiquidationRewardsManager public liquidationRewardsManager; + LiquidationRewardsManagerWstEth public liquidationRewardsManager; RebalancerHandler public rebalancer; UsdnProtocolHandler public protocol; FeeCollector public feeCollector; @@ -124,7 +124,7 @@ contract UsdnProtocolBaseFixture is wstETH = new WstETH(); sdex = new Sdex(); oracleMiddleware = new MockOracleMiddleware(); - liquidationRewardsManager = new LiquidationRewardsManager(wstETH); + liquidationRewardsManager = new LiquidationRewardsManagerWstEth(wstETH); feeCollector = new FeeCollector(); if (!testParams.flags.enableLiquidationRewards) { @@ -144,21 +144,20 @@ contract UsdnProtocolBaseFixture is }); } - UsdnProtocolHandler test = new UsdnProtocolHandler(); - UsdnProtocolFallback protocolFallback = new UsdnProtocolFallback(); + UsdnProtocolHandler test = new UsdnProtocolHandler(MAX_SDEX_BURN_RATIO, MAX_MIN_LONG_POSITION); _setPeripheralContracts( - WstEthOracleMiddleware(address(oracleMiddleware)), + WstEthOracleMiddlewareWithPyth(address(oracleMiddleware)), liquidationRewardsManager, usdn, wstETH, - address(protocolFallback), + address(0), address(feeCollector), sdex ); address proxy = UnsafeUpgrades.deployUUPSProxy( - address(test), abi.encodeCall(UsdnProtocolHandler.initializeStorageHandler, (initStorage)) + address(test), abi.encodeCall(UsdnProtocolHandler.initializeStorageHandler, initStorage) ); protocol = UsdnProtocolHandler(proxy); diff --git a/test/unit/UsdnProtocol/utils/Handler.sol b/test/unit/UsdnProtocol/utils/Handler.sol index 0173339fe..663f0b7a6 100644 --- a/test/unit/UsdnProtocol/utils/Handler.sol +++ b/test/unit/UsdnProtocol/utils/Handler.sol @@ -39,7 +39,11 @@ contract UsdnProtocolHandler is UsdnProtocolImpl, UsdnProtocolFallback, Test { using SafeCast for uint256; using SignedMath for int256; - function initializeStorageHandler(InitStorage calldata initStorage) external initializer { + constructor(uint256 maxSdexBurnRatio, uint256 maxMinLongPosition) + UsdnProtocolFallback(maxSdexBurnRatio, maxMinLongPosition) + { } + + function initializeStorageHandler(InitStorage calldata initStorage) external { initializeStorage(initStorage); } @@ -373,7 +377,7 @@ contract UsdnProtocolHandler is UsdnProtocolImpl, UsdnProtocolFallback, Test { return Utils._getOraclePrice(action, timestamp, actionId, priceData); } - function i_calcSdexToBurn(uint256 usdnAmount, uint32 sdexBurnRatio) external pure returns (uint256) { + function i_calcSdexToBurn(uint256 usdnAmount, uint64 sdexBurnRatio) external pure returns (uint256) { return Utils._calcSdexToBurn(usdnAmount, sdexBurnRatio); } diff --git a/test/unit/UsdnProtocol/utils/MockOracleMiddleware.sol b/test/unit/UsdnProtocol/utils/MockOracleMiddleware.sol index a5635c049..22b115b5d 100644 --- a/test/unit/UsdnProtocol/utils/MockOracleMiddleware.sol +++ b/test/unit/UsdnProtocol/utils/MockOracleMiddleware.sol @@ -5,10 +5,7 @@ import { AccessControlDefaultAdminRules } from "@openzeppelin/contracts/access/extensions/AccessControlDefaultAdminRules.sol"; import { IBaseOracleMiddleware } from "../../../../src/interfaces/OracleMiddleware/IBaseOracleMiddleware.sol"; -import { - IOracleMiddleware, - IOracleMiddlewareErrors -} from "../../../../src/interfaces/OracleMiddleware/IOracleMiddleware.sol"; +import { IOracleMiddlewareErrors } from "../../../../src/interfaces/OracleMiddleware/IOracleMiddlewareErrors.sol"; import { PriceInfo } from "../../../../src/interfaces/OracleMiddleware/IOracleMiddlewareTypes.sol"; import { IUsdnProtocolTypes as Types } from "../../../../src/interfaces/UsdnProtocol/IUsdnProtocolTypes.sol"; diff --git a/test/unit/UsdnProtocol/utils/TransferCallback.sol b/test/unit/UsdnProtocol/utils/TransferCallback.sol index 6448cf5ce..a49dee0bd 100644 --- a/test/unit/UsdnProtocol/utils/TransferCallback.sol +++ b/test/unit/UsdnProtocol/utils/TransferCallback.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.0; +pragma solidity ^0.8.0; import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import { ERC165, IERC165 } from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; diff --git a/test/utils/Constants.sol b/test/utils/Constants.sol index 3a74b4b34..befaad77b 100644 --- a/test/utils/Constants.sol +++ b/test/utils/Constants.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity 0.8.26; +pragma solidity >=0.8.0; /* -------------------------------------------------------------------------- */ /* General Constants */ @@ -50,9 +50,14 @@ address constant WSTETH = address(0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0); address constant PYTH_ORACLE = address(0x4305FB66699C3B2702D4d05CF36551390A4c69C6); address constant CHAINLINK_ORACLE_ETH = address(0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419); // ETH-USD +address constant CHAINLINK_VERIFIER_PROXY = address(0x5A1634A86e9b7BfEf33F0f3f3EA3b1aBBc4CC85F); bytes32 constant PYTH_ETH_USD = bytes32(0xff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace); bytes32 constant PYTH_WSTETH_USD = bytes32(0x6df640f3b8963d8f8358f791f352b8364513f6ab1cca5ed3f1f7b5448980e784); bytes32 constant REDSTONE_ETH_USD = bytes32("ETH"); +bytes32 constant CHAINLINK_DATA_STREAMS_WSTETH_USD = + bytes32(0x0003e8aed7fa7b939cc969a6db3105e05b4e77a7e9bb546fe21516f22aa1b0f4); +bytes32 constant CHAINLINK_DATA_STREAMS_ETH_USD = + bytes32(0x000362205e10b3a147d02792eccee483dca6c7b44ecce7012cb8c6e0b68b3ae9); /* -------------------------------------------------------------------------- */ /* Polygon mainnet */ diff --git a/test/utils/DefaultConfig.sol b/test/utils/DefaultConfig.sol index fb73a826f..378b96c20 100644 --- a/test/utils/DefaultConfig.sol +++ b/test/utils/DefaultConfig.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.26; import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import { LiquidationRewardsManager } from "../../src/LiquidationRewardsManager/LiquidationRewardsManager.sol"; -import { WstEthOracleMiddleware } from "../../src/OracleMiddleware/WstEthOracleMiddleware.sol"; +import { WstEthOracleMiddlewareWithPyth } from "../../src/OracleMiddleware/WstEthOracleMiddlewareWithPyth.sol"; import { Usdn } from "../../src/Usdn/Usdn.sol"; import { UsdnProtocolConstantsLibrary as Constants } from "../../src/UsdnProtocol/libraries/UsdnProtocolConstantsLibrary.sol"; @@ -12,6 +12,9 @@ import { IWstETH } from "../../src/interfaces/IWstETH.sol"; import { IUsdnProtocolTypes as Types } from "../../src/interfaces/UsdnProtocol/IUsdnProtocolTypes.sol"; contract DefaultConfig { + uint256 constant MAX_SDEX_BURN_RATIO = Constants.SDEX_BURN_ON_DEPOSIT_DIVISOR / 10; // 10% + uint256 constant MAX_MIN_LONG_POSITION = 10 ether; + Types.InitStorage internal initStorage; constructor() { @@ -43,7 +46,7 @@ contract DefaultConfig { } function _setPeripheralContracts( - WstEthOracleMiddleware oracleMiddleware, + WstEthOracleMiddlewareWithPyth oracleMiddleware, LiquidationRewardsManager liquidationRewardsManager, Usdn usdn, IWstETH wstETH, diff --git a/test_utils/Cargo.toml b/test_utils/Cargo.toml index 43a49e4d0..c4303d709 100644 --- a/test_utils/Cargo.toml +++ b/test_utils/Cargo.toml @@ -9,6 +9,11 @@ alloy-sol-types = "0.7" anyhow = "1" clap = { version = "4", features = ["derive"] } const-hex = "1" +data-streams-report = { git = "https://github.com/smartcontractkit/data-streams-sdk.git" } +data-streams-sdk = { git = "https://github.com/smartcontractkit/data-streams-sdk.git" } +hex = "0.4" +hmac = "0.12" rug = "1" serde = { version = "1", features = ["derive"] } +sha2 = "0.10" ureq = { version = "2", features = ["json"] } diff --git a/test_utils/src/main.rs b/test_utils/src/main.rs index 8a0e3a124..16513f97e 100644 --- a/test_utils/src/main.rs +++ b/test_utils/src/main.rs @@ -1,15 +1,21 @@ -use std::ops::DivAssign; +use std::{ + ops::DivAssign, + time::{SystemTime, UNIX_EPOCH}, +}; use alloy_primitives::{Bytes, FixedBytes, I256, U256}; use alloy_sol_types::SolValue; use anyhow::{anyhow, Context, Result}; use clap::{Parser, Subcommand}; +use data_streams_report::report::Report; +use hmac::{Hmac, Mac}; use rug::{ float::Round, ops::{DivRounding, MulAssignRound, Pow}, Float, Integer, }; use serde::Deserialize; +use sha2::{Digest, Sha256}; #[derive(Deserialize, Debug)] struct HermesResponse { @@ -34,6 +40,12 @@ struct PythPrice { expo: i64, publish_time: u64, } + +#[derive(Debug, Deserialize)] +pub struct ReportResponse { + pub report: Report, +} + #[derive(Parser)] struct Cli { #[command(subcommand)] @@ -95,6 +107,13 @@ enum Commands { decimals: u32, usdn_divisor: Integer, }, + /// Get price from chainlink data streams api + ChainlinkPrice { + /// The chainlink datastream id + feed_id: String, + /// The price timestamp + timestamp: u128, + }, } fn main() -> Result<()> { @@ -178,6 +197,42 @@ fn main() -> Result<()> { let total_mint_shares = total_mint * usdn_divisor; print_int_u256_hex(total_mint_shares)?; } + Commands::ChainlinkPrice { feed_id, timestamp } => { + let chainlink_low_latency_api_key = std::env::var("CHAINLINK_DATA_STREAMS_API_KEY") + .context("getting CHAINLINK_DATA_STREAMS_API_KEY env variable")?; + + let chainlink_user_secret = std::env::var("CHAINLINK_DATA_STREAMS_API_SECRET") + .context("getting CHAINLINK_DATA_STREAMS_API_SECRET env variable")?; + + let chainlink_rest_url = std::env::var("CHAINLINK_DATA_STREAMS_API_URL") + .context("getting CHAINLINK_DATA_STREAMS_API_URL env variable")?; + + let path = format!("/api/v1/reports?feedID={feed_id}×tamp={timestamp}"); + let request_timestamp = SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("Error: Timestamp in the past") + .as_millis(); + + let hmac_string = generate_hmac( + "GET", + &path, + b"", + &chainlink_low_latency_api_key, + request_timestamp, + &chainlink_user_secret, + )?; + + let response = ureq::get(&format!("{chainlink_rest_url}{path}")) + .set("Authorization", &chainlink_low_latency_api_key) + .set("X-Authorization-Timestamp", &request_timestamp.to_string()) + .set("X-Authorization-Signature-SHA256", &hmac_string) + .call()?; + + let report_response: ReportResponse = response.into_json()?; + let report: Report = report_response.report; + + print!("{}", report.full_report); + } } Ok(()) } @@ -237,3 +292,44 @@ fn parse_float(s: &str) -> Result { Float::parse(s).map_err(|e| e.to_string())?, )) } + +/// Generates an HMAC-SHA256 signature based on the provided parameters. +/// +/// # Arguments +/// +/// * `method` - The HTTP method (e.g., "GET", "POST", etc.). +/// * `path` - The API endpoint path (e.g., "/api/v1/feeds", "/api/v1/reports/bulk", etc.). +/// * `body` - The request body as a byte slice. +/// * `client_id` - The client's API key. +/// * `timestamp` - The current timestamp as an `u128`. +/// * `user_secret` - The client's API secret. +/// +/// # Returns +/// +/// A `Result` containing the hex-encoded HMAC string if successful, or an error. +fn generate_hmac( + method: &str, + path: &str, + body: &[u8], + client_id: &str, + timestamp: u128, + user_secret: &str, +) -> Result { + let mut hasher = Sha256::new(); + hasher.update(body); + let server_body_hash = hasher.finalize(); + let server_body_hash_hex = hex::encode(server_body_hash); + + // Create the server body hash string + let server_body_hash_string = + format!("{method} {path} {server_body_hash_hex} {client_id} {timestamp}"); + + // Compute HMAC-SHA256 of the server body hash string + let mut mac = Hmac::::new_from_slice(user_secret.as_bytes())?; + mac.update(server_body_hash_string.as_bytes()); + let signed_message = mac.finalize(); + let signed_message_bytes = signed_message.into_bytes(); + let user_hmac = hex::encode(signed_message_bytes); + + Ok(user_hmac) +}