48 Commits

Author SHA1 Message Date
hdbg
2b27da224e housekeeping: linter
Some checks failed
ci/woodpecker/pr/server-audit Pipeline was successful
ci/woodpecker/pr/server-vet Pipeline failed
ci/woodpecker/pr/server-lint Pipeline failed
ci/woodpecker/pr/server-test Pipeline was successful
2026-03-11 14:51:54 +01:00
hdbg
9e92b168ba tests(evm::engine): basic policies tests 2026-03-11 14:50:32 +01:00
hdbg
bd159c35e8 docs: add EVM Policy Engine section
Some checks failed
ci/woodpecker/pr/server-audit Pipeline was successful
ci/woodpecker/pr/server-vet Pipeline failed
ci/woodpecker/pr/server-lint Pipeline failed
ci/woodpecker/pr/server-test Pipeline failed
2026-03-11 14:08:33 +01:00
hdbg
b3e378b5fc fix(evm::engine): added shared settings check in vet_transaction 2026-03-11 14:08:33 +01:00
hdbg
b7c4f2e735 feat(evm): add find_all_grants to Policy trait with shared auto_type queries 2026-03-11 14:08:33 +01:00
hdbg
4a5dd3eea7 feat(protobuf): EVM grants and signing definitions 2026-03-11 14:08:33 +01:00
hdbg
5af6d8dd9c housekeeping: linter 2026-03-11 14:08:33 +01:00
hdbg
5dfe390ac3 feat(evm): add grant management and transaction signing 2026-03-11 14:08:33 +01:00
hdbg
43c7b211c3 feat(server::evm::engine): return meaning on error path 2026-03-11 14:08:33 +01:00
hdbg
c5f9cfcaa0 feat(server::evm::engine): initial wiring of all components -- we now can evaluate transactions 2026-03-11 14:08:33 +01:00
hdbg
67fce6f06a feat(server::evm): more criterion types 2026-03-11 14:08:33 +01:00
hdbg
191b126462 feat(server): initial EVM functionality impl 2026-03-11 14:08:33 +01:00
hdbg
cb05407bb6 feat(server): broker agent for inter-actor coordination
Some checks failed
ci/woodpecker/push/server-vet Pipeline failed
ci/woodpecker/push/server-audit Pipeline was successful
ci/woodpecker/push/server-lint Pipeline failed
ci/woodpecker/push/server-test Pipeline was successful
2026-03-11 14:08:15 +01:00
4beb34764d Merge pull request 'refactor(server::{user_agent, client}): move auth part to separate function to not to pollute actor session with one-time concerns' (#24) from push-upvpzwvlwyvs into main
Reviewed-on: #24
2026-03-11 14:08:15 +01:00
hdbg
4b4a8f4489 refactor: consolidate auth messages into client and user_agent packages 2026-03-11 14:08:15 +01:00
hdbg
54d0fe0505 refactor(server::{user_agent, client}): move auth part to separate function to not to pollute actor session with one-time concerns 2026-03-11 14:08:15 +01:00
hdbg
06f4d628db chore(server): update Cargo.lock dependencies 2026-03-11 14:08:15 +01:00
hdbg
657f47e32f refactor(transport): convert Bi trait to use async_trait 2026-03-11 14:08:15 +01:00
hdbg
86f8feb291 tests(user-agent): basic auth tests similar to server 2026-03-11 14:07:46 +01:00
hdbg
6deec731e2 feat(useragent): initial connection impl 2026-03-11 14:07:46 +01:00
hdbg
f5a5c62181 refactor(transport): simplify converters 2026-03-11 14:07:46 +01:00
hdbg
b8afd94b21 refactor(transport): implemented Bi stream based abstraction for actor communication with next loop override 2026-03-11 14:07:46 +01:00
hdbg
7b57965952 housekeeping(useragent): rename 2026-03-11 14:07:06 +01:00
hdbg
9dca7aff27 feat(proto): add URL parsing and TLS certificate management 2026-03-11 14:07:06 +01:00
hdbg
4d1f047baf misc: create license and readme 2026-03-11 14:05:42 +01:00
hdbg
925c7a211f refactor(server): reogranized actors, context, and db modules into <dir>/mod.rs structure 2026-03-11 14:05:42 +01:00
hdbg
d81120f59c refactor(server::tests): moved integration-like tests into tests/ 2026-03-11 14:05:42 +01:00
hdbg
e118eceb85 refactor(server): separated global actors into their own handle 2026-03-11 14:05:42 +01:00
hdbg
4a84fe9339 refactor(server): actors reorganization & linter fixes 2026-03-11 14:05:42 +01:00
hdbg
c6e13dc476 feat(keyholder): add seal method and unseal integration tests 2026-03-11 14:05:42 +01:00
hdbg
8f5d4cc385 feat(server::user-agent): Unseal implemented 2026-03-11 14:05:42 +01:00
hdbg
2ffd60973d test(keyholder): remove unused imports from test modules 2026-03-11 14:05:42 +01:00
hdbg
08af101b2e fix(ci): add protoc installation for lints 2026-03-11 14:05:42 +01:00
hdbg
bb58868333 fix(ci): add clippy installation in mise.toml 2026-03-11 14:05:42 +01:00
hdbg
b05cdeec66 refactor(actors): rename BootstrapActor to Bootstrapper 2026-03-11 14:05:42 +01:00
hdbg
9ec465706a chore(supply-chain): update cargo-vet audits and trusted publishers 2026-03-11 14:05:42 +01:00
hdbg
46a3c1768c feat(server::key_holder): unique index on (root_key_id, nonce) to avoid nonce reuse 2026-03-11 14:05:42 +01:00
hdbg
6c8a67c520 feat(server::key_holder): ability to remotely get current state 2026-03-11 14:05:42 +01:00
hdbg
bbaed3fb97 refactor(keyholder): rename KeyHolderActor to KeyHolder and optimize db connection lifetime 2026-03-11 14:05:42 +01:00
hdbg
4700bc407e security(server::key_holder): replaced nonce-caching with exclusive transaction fetching nonce from the database 2026-03-11 14:05:42 +01:00
hdbg
281fbcb31d feat(server): UserAgent seal/unseal 2026-03-11 14:05:42 +01:00
hdbg
a55221573b feat(unseal): add unseal protocol support for user agents 2026-03-11 14:03:46 +01:00
hdbg
45acb45a05 feat(server): boot mechanism 2026-03-11 14:03:46 +01:00
hdbg
11f1caa6da ci: add server linting pipeline for Rust code quality checks 2026-03-11 14:03:46 +01:00
hdbg
f769c9119b test(user-agent): add challenge-response auth flow test 2026-03-11 14:03:45 +01:00
hdbg
1145642255 tests(server): UserAgent invalid bootstrap token 2026-03-11 14:03:45 +01:00
hdbg
9f33277a4f security(server): cargo-vet proper init 2026-03-11 14:03:45 +01:00
hdbg
0a8e1dce3f housekeeping(server): trimmed-down dependencies 2026-03-11 14:03:45 +01:00
26 changed files with 1808 additions and 82 deletions

View File

@@ -0,0 +1,31 @@
Extension Discovery Cache
=========================
This folder is used by `package:extension_discovery` to cache lists of
packages that contains extensions for other packages.
DO NOT USE THIS FOLDER
----------------------
* Do not read (or rely) the contents of this folder.
* Do write to this folder.
If you're interested in the lists of extensions stored in this folder use the
API offered by package `extension_discovery` to get this information.
If this package doesn't work for your use-case, then don't try to read the
contents of this folder. It may change, and will not remain stable.
Use package `extension_discovery`
---------------------------------
If you want to access information from this folder.
Feel free to delete this folder
-------------------------------
Files in this folder act as a cache, and the cache is discarded if the files
are older than the modification time of `.dart_tool/package_config.json`.
Hence, it should never be necessary to clear this cache manually, if you find a
need to do please file a bug.

View File

@@ -0,0 +1 @@
{"version":2,"entries":[{"package":"app","rootUri":"../","packageUri":"lib/"}]}

View File

@@ -0,0 +1,178 @@
{
"configVersion": 2,
"packages": [
{
"name": "async",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/async-2.13.0",
"packageUri": "lib/",
"languageVersion": "3.4"
},
{
"name": "boolean_selector",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/boolean_selector-2.1.2",
"packageUri": "lib/",
"languageVersion": "3.1"
},
{
"name": "characters",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/characters-1.4.0",
"packageUri": "lib/",
"languageVersion": "3.4"
},
{
"name": "clock",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/clock-1.1.2",
"packageUri": "lib/",
"languageVersion": "3.4"
},
{
"name": "collection",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/collection-1.19.1",
"packageUri": "lib/",
"languageVersion": "3.4"
},
{
"name": "cupertino_icons",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/cupertino_icons-1.0.8",
"packageUri": "lib/",
"languageVersion": "3.1"
},
{
"name": "fake_async",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/fake_async-1.3.3",
"packageUri": "lib/",
"languageVersion": "3.3"
},
{
"name": "flutter",
"rootUri": "file:///Users/kaska/.local/share/mise/installs/flutter/3.38.9-stable/packages/flutter",
"packageUri": "lib/",
"languageVersion": "3.8"
},
{
"name": "flutter_lints",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/flutter_lints-6.0.0",
"packageUri": "lib/",
"languageVersion": "3.8"
},
{
"name": "flutter_test",
"rootUri": "file:///Users/kaska/.local/share/mise/installs/flutter/3.38.9-stable/packages/flutter_test",
"packageUri": "lib/",
"languageVersion": "3.8"
},
{
"name": "leak_tracker",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/leak_tracker-11.0.2",
"packageUri": "lib/",
"languageVersion": "3.2"
},
{
"name": "leak_tracker_flutter_testing",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/leak_tracker_flutter_testing-3.0.10",
"packageUri": "lib/",
"languageVersion": "3.2"
},
{
"name": "leak_tracker_testing",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/leak_tracker_testing-3.0.2",
"packageUri": "lib/",
"languageVersion": "3.2"
},
{
"name": "lints",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/lints-6.1.0",
"packageUri": "lib/",
"languageVersion": "3.8"
},
{
"name": "matcher",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/matcher-0.12.17",
"packageUri": "lib/",
"languageVersion": "3.4"
},
{
"name": "material_color_utilities",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/material_color_utilities-0.11.1",
"packageUri": "lib/",
"languageVersion": "2.17"
},
{
"name": "meta",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/meta-1.17.0",
"packageUri": "lib/",
"languageVersion": "3.5"
},
{
"name": "path",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/path-1.9.1",
"packageUri": "lib/",
"languageVersion": "3.4"
},
{
"name": "sky_engine",
"rootUri": "file:///Users/kaska/.local/share/mise/installs/flutter/3.38.9-stable/bin/cache/pkg/sky_engine",
"packageUri": "lib/",
"languageVersion": "3.8"
},
{
"name": "source_span",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/source_span-1.10.2",
"packageUri": "lib/",
"languageVersion": "3.1"
},
{
"name": "stack_trace",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/stack_trace-1.12.1",
"packageUri": "lib/",
"languageVersion": "3.4"
},
{
"name": "stream_channel",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/stream_channel-2.1.4",
"packageUri": "lib/",
"languageVersion": "3.3"
},
{
"name": "string_scanner",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/string_scanner-1.4.1",
"packageUri": "lib/",
"languageVersion": "3.1"
},
{
"name": "term_glyph",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/term_glyph-1.2.2",
"packageUri": "lib/",
"languageVersion": "3.1"
},
{
"name": "test_api",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/test_api-0.7.7",
"packageUri": "lib/",
"languageVersion": "3.5"
},
{
"name": "vector_math",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/vector_math-2.2.0",
"packageUri": "lib/",
"languageVersion": "3.1"
},
{
"name": "vm_service",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/vm_service-15.0.2",
"packageUri": "lib/",
"languageVersion": "3.5"
},
{
"name": "app",
"rootUri": "../",
"packageUri": "lib/",
"languageVersion": "3.10"
}
],
"generator": "pub",
"generatorVersion": "3.10.8",
"flutterRoot": "file:///Users/kaska/.local/share/mise/installs/flutter/3.38.9-stable",
"flutterVersion": "3.38.9",
"pubCache": "file:///Users/kaska/.pub-cache"
}

View File

@@ -0,0 +1,230 @@
{
"roots": [
"app"
],
"packages": [
{
"name": "app",
"version": "1.0.0+1",
"dependencies": [
"cupertino_icons",
"flutter"
],
"devDependencies": [
"flutter_lints",
"flutter_test"
]
},
{
"name": "flutter_lints",
"version": "6.0.0",
"dependencies": [
"lints"
]
},
{
"name": "flutter_test",
"version": "0.0.0",
"dependencies": [
"clock",
"collection",
"fake_async",
"flutter",
"leak_tracker_flutter_testing",
"matcher",
"meta",
"path",
"stack_trace",
"stream_channel",
"test_api",
"vector_math"
]
},
{
"name": "cupertino_icons",
"version": "1.0.8",
"dependencies": []
},
{
"name": "flutter",
"version": "0.0.0",
"dependencies": [
"characters",
"collection",
"material_color_utilities",
"meta",
"sky_engine",
"vector_math"
]
},
{
"name": "lints",
"version": "6.1.0",
"dependencies": []
},
{
"name": "stream_channel",
"version": "2.1.4",
"dependencies": [
"async"
]
},
{
"name": "meta",
"version": "1.17.0",
"dependencies": []
},
{
"name": "collection",
"version": "1.19.1",
"dependencies": []
},
{
"name": "leak_tracker_flutter_testing",
"version": "3.0.10",
"dependencies": [
"flutter",
"leak_tracker",
"leak_tracker_testing",
"matcher",
"meta"
]
},
{
"name": "vector_math",
"version": "2.2.0",
"dependencies": []
},
{
"name": "stack_trace",
"version": "1.12.1",
"dependencies": [
"path"
]
},
{
"name": "clock",
"version": "1.1.2",
"dependencies": []
},
{
"name": "fake_async",
"version": "1.3.3",
"dependencies": [
"clock",
"collection"
]
},
{
"name": "path",
"version": "1.9.1",
"dependencies": []
},
{
"name": "matcher",
"version": "0.12.17",
"dependencies": [
"async",
"meta",
"stack_trace",
"term_glyph",
"test_api"
]
},
{
"name": "test_api",
"version": "0.7.7",
"dependencies": [
"async",
"boolean_selector",
"collection",
"meta",
"source_span",
"stack_trace",
"stream_channel",
"string_scanner",
"term_glyph"
]
},
{
"name": "sky_engine",
"version": "0.0.0",
"dependencies": []
},
{
"name": "material_color_utilities",
"version": "0.11.1",
"dependencies": [
"collection"
]
},
{
"name": "characters",
"version": "1.4.0",
"dependencies": []
},
{
"name": "async",
"version": "2.13.0",
"dependencies": [
"collection",
"meta"
]
},
{
"name": "leak_tracker_testing",
"version": "3.0.2",
"dependencies": [
"leak_tracker",
"matcher",
"meta"
]
},
{
"name": "leak_tracker",
"version": "11.0.2",
"dependencies": [
"clock",
"collection",
"meta",
"path",
"vm_service"
]
},
{
"name": "term_glyph",
"version": "1.2.2",
"dependencies": []
},
{
"name": "string_scanner",
"version": "1.4.1",
"dependencies": [
"source_span"
]
},
{
"name": "source_span",
"version": "1.10.2",
"dependencies": [
"collection",
"path",
"term_glyph"
]
},
{
"name": "boolean_selector",
"version": "2.1.2",
"dependencies": [
"source_span",
"string_scanner"
]
},
{
"name": "vm_service",
"version": "15.0.2",
"dependencies": []
}
],
"configVersion": 1
}

1
app/.dart_tool/version Normal file
View File

@@ -0,0 +1 @@
3.38.9

View File

@@ -0,0 +1,11 @@
// This is a generated file; do not edit or check into version control.
FLUTTER_ROOT=/Users/kaska/.local/share/mise/installs/flutter/3.38.9-stable
FLUTTER_APPLICATION_PATH=/Users/kaska/Documents/Projects/Major/arbiter/app
COCOAPODS_PARALLEL_CODE_SIGN=true
FLUTTER_BUILD_DIR=build
FLUTTER_BUILD_NAME=1.0.0
FLUTTER_BUILD_NUMBER=1
DART_OBFUSCATION=false
TRACK_WIDGET_CREATION=true
TREE_SHAKE_ICONS=false
PACKAGE_CONFIG=.dart_tool/package_config.json

View File

@@ -0,0 +1,12 @@
#!/bin/sh
# This is a generated file; do not edit or check into version control.
export "FLUTTER_ROOT=/Users/kaska/.local/share/mise/installs/flutter/3.38.9-stable"
export "FLUTTER_APPLICATION_PATH=/Users/kaska/Documents/Projects/Major/arbiter/app"
export "COCOAPODS_PARALLEL_CODE_SIGN=true"
export "FLUTTER_BUILD_DIR=build"
export "FLUTTER_BUILD_NAME=1.0.0"
export "FLUTTER_BUILD_NUMBER=1"
export "DART_OBFUSCATION=false"
export "TRACK_WIDGET_CREATION=true"
export "TREE_SHAKE_ICONS=false"
export "PACKAGE_CONFIG=.dart_tool/package_config.json"

Binary file not shown.

76
server/Cargo.lock generated
View File

@@ -67,9 +67,9 @@ dependencies = [
[[package]] [[package]]
name = "alloy-chains" name = "alloy-chains"
version = "0.2.30" version = "0.2.31"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90f374d3c6d729268bbe2d0e0ff992bb97898b2df756691a62ee1d5f0506bc39" checksum = "6d9d22005bf31b018f31ef9ecadb5d2c39cf4f6acc8db0456f72c815f3d7f757"
dependencies = [ dependencies = [
"alloy-primitives", "alloy-primitives",
"num_enum", "num_enum",
@@ -634,13 +634,12 @@ dependencies = [
[[package]] [[package]]
name = "alloy-trie" name = "alloy-trie"
version = "0.9.4" version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d7fd448ab0a017de542de1dcca7a58e7019fe0e7a34ed3f9543ebddf6aceffa" checksum = "3f14b5d9b2c2173980202c6ff470d96e7c5e202c65a9f67884ad565226df7fbb"
dependencies = [ dependencies = [
"alloy-primitives", "alloy-primitives",
"alloy-rlp", "alloy-rlp",
"arrayvec",
"derive_more", "derive_more",
"nybbles", "nybbles",
"serde", "serde",
@@ -687,9 +686,11 @@ dependencies = [
"async-trait", "async-trait",
"base64", "base64",
"futures", "futures",
"hex",
"kameo", "kameo",
"miette", "miette",
"prost", "prost",
"prost-types",
"rand 0.10.0", "rand 0.10.0",
"rcgen", "rcgen",
"rstest", "rstest",
@@ -977,9 +978,6 @@ name = "arrayvec"
version = "0.7.6" version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "asn1-rs" name = "asn1-rs"
@@ -2452,9 +2450,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]] [[package]]
name = "hybrid-array" name = "hybrid-array"
version = "0.4.7" version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1b229d73f5803b562cc26e4da0396c8610a4ee209f4fac8fa4f8d709166dc45" checksum = "8655f91cd07f2b9d0c24137bd650fe69617773435ee5ec83022377777ce65ef1"
dependencies = [ dependencies = [
"typenum", "typenum",
] ]
@@ -2887,9 +2885,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.182" version = "0.2.183"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d"
[[package]] [[package]]
name = "libm" name = "libm"
@@ -3434,9 +3432,9 @@ dependencies = [
[[package]] [[package]]
name = "proc-macro-crate" name = "proc-macro-crate"
version = "3.4.0" version = "3.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f"
dependencies = [ dependencies = [
"toml_edit", "toml_edit",
] ]
@@ -3565,6 +3563,7 @@ version = "0.14.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8991c4cbdb8bc5b11f0b074ffe286c30e523de90fee5ba8132f1399f23cb3dd7" checksum = "8991c4cbdb8bc5b11f0b074ffe286c30e523de90fee5ba8132f1399f23cb3dd7"
dependencies = [ dependencies = [
"chrono",
"prost", "prost",
] ]
@@ -3616,9 +3615,9 @@ dependencies = [
[[package]] [[package]]
name = "quinn-proto" name = "quinn-proto"
version = "0.11.13" version = "0.11.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098"
dependencies = [ dependencies = [
"bytes", "bytes",
"getrandom 0.3.4", "getrandom 0.3.4",
@@ -4475,12 +4474,12 @@ dependencies = [
[[package]] [[package]]
name = "socket2" name = "socket2"
version = "0.6.2" version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e"
dependencies = [ dependencies = [
"libc", "libc",
"windows-sys 0.60.2", "windows-sys 0.61.2",
] ]
[[package]] [[package]]
@@ -4639,9 +4638,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]] [[package]]
name = "tempfile" name = "tempfile"
version = "3.26.0" version = "3.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82a72c767771b47409d2345987fda8628641887d5466101319899796367354a0" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
dependencies = [ dependencies = [
"fastrand", "fastrand",
"getrandom 0.4.2", "getrandom 0.4.2",
@@ -4857,7 +4856,7 @@ checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863"
dependencies = [ dependencies = [
"serde_core", "serde_core",
"serde_spanned", "serde_spanned",
"toml_datetime", "toml_datetime 0.7.5+spec-1.1.0",
"toml_parser", "toml_parser",
"winnow", "winnow",
] ]
@@ -4872,13 +4871,22 @@ dependencies = [
] ]
[[package]] [[package]]
name = "toml_edit" name = "toml_datetime"
version = "0.23.10+spec-1.0.0" version = "1.0.0+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" checksum = "32c2555c699578a4f59f0cc68e5116c8d7cabbd45e1409b989d4be085b53f13e"
dependencies = [
"serde_core",
]
[[package]]
name = "toml_edit"
version = "0.25.4+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7193cbd0ce53dc966037f54351dbbcf0d5a642c7f0038c382ef9e677ce8c13f2"
dependencies = [ dependencies = [
"indexmap 2.13.0", "indexmap 2.13.0",
"toml_datetime", "toml_datetime 1.0.0+spec-1.1.0",
"toml_parser", "toml_parser",
"winnow", "winnow",
] ]
@@ -5194,9 +5202,9 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
[[package]] [[package]]
name = "uuid" name = "uuid"
version = "1.21.0" version = "1.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b672338555252d43fd2240c714dc444b8c6fb0a5c5335e65a07bba7742735ddb" checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37"
dependencies = [ dependencies = [
"js-sys", "js-sys",
"wasm-bindgen", "wasm-bindgen",
@@ -5646,9 +5654,9 @@ checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
[[package]] [[package]]
name = "winnow" name = "winnow"
version = "0.7.14" version = "0.7.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]
@@ -5820,18 +5828,18 @@ dependencies = [
[[package]] [[package]]
name = "zerocopy" name = "zerocopy"
version = "0.8.40" version = "0.8.42"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a789c6e490b576db9f7e6b6d661bcc9799f7c0ac8352f56ea20193b2681532e5" checksum = "f2578b716f8a7a858b7f02d5bd870c14bf4ddbbcf3a4c05414ba6503640505e3"
dependencies = [ dependencies = [
"zerocopy-derive", "zerocopy-derive",
] ]
[[package]] [[package]]
name = "zerocopy-derive" name = "zerocopy-derive"
version = "0.8.40" version = "0.8.42"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f65c489a7071a749c849713807783f70672b28094011623e200cb86dcb835953" checksum = "7e6cc098ea4d3bd6246687de65af3f920c430e236bee1e3bf2e441463f08a02f"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",

View File

@@ -25,6 +25,7 @@ async-trait = "0.1.89"
futures = "0.3.31" futures = "0.3.31"
tokio-stream = { version = "0.1.18", features = ["full"] } tokio-stream = { version = "0.1.18", features = ["full"] }
kameo = "0.19.2" kameo = "0.19.2"
prost-types = { version = "0.14.3", features = ["chrono"] }
x25519-dalek = { version = "2.0.1", features = ["getrandom"] } x25519-dalek = { version = "2.0.1", features = ["getrandom"] }
rstest = "0.26.1" rstest = "0.26.1"
rustls-pki-types = "1.14.0" rustls-pki-types = "1.14.0"

View File

@@ -9,6 +9,7 @@ license = "Apache-2.0"
tonic.workspace = true tonic.workspace = true
tokio.workspace = true tokio.workspace = true
futures.workspace = true futures.workspace = true
hex = "0.4.3"
tonic-prost = "0.14.3" tonic-prost = "0.14.3"
prost = "0.14.3" prost = "0.14.3"
kameo.workspace = true kameo.workspace = true
@@ -17,6 +18,7 @@ miette.workspace = true
thiserror.workspace = true thiserror.workspace = true
rustls-pki-types.workspace = true rustls-pki-types.workspace = true
base64 = "0.22.1" base64 = "0.22.1"
prost-types.workspace = true
tracing.workspace = true tracing.workspace = true
async-trait.workspace = true async-trait.workspace = true

View File

@@ -51,6 +51,7 @@ fn parse_auth_event(payload: ClientRequestPayload) -> Result<AuthEvents, Error>
solution: signature, solution: signature,
})) }))
} }
_ => Err(Error::UnexpectedMessagePayload) ,
} }
} }

View File

@@ -21,7 +21,7 @@ pub mod types {
}; };
#[derive(Debug, FromSqlRow, AsExpression)] #[derive(Debug, FromSqlRow, AsExpression)]
#[sql_type = "Integer"] #[diesel(sql_type = Integer)]
#[repr(transparent)] // hint compiler to optimize the wrapper struct away #[repr(transparent)] // hint compiler to optimize the wrapper struct away
pub struct SqliteTimestamp(pub DateTime<Utc>); pub struct SqliteTimestamp(pub DateTime<Utc>);
impl SqliteTimestamp { impl SqliteTimestamp {
@@ -56,7 +56,7 @@ pub mod types {
fn from_sql( fn from_sql(
mut bytes: <Sqlite as diesel::backend::Backend>::RawValue<'_>, mut bytes: <Sqlite as diesel::backend::Backend>::RawValue<'_>,
) -> diesel::deserialize::Result<Self> { ) -> diesel::deserialize::Result<Self> {
let Some(SqliteType::Integer) = bytes.value_type() else { let Some(SqliteType::Long) = bytes.value_type() else {
return Err(format!( return Err(format!(
"Expected Integer type for SqliteTimestamp, got {:?}", "Expected Integer type for SqliteTimestamp, got {:?}",
bytes.value_type() bytes.value_type()
@@ -64,8 +64,8 @@ pub mod types {
.into()); .into());
}; };
let unix_timestamp = bytes.read_integer(); let unix_timestamp = bytes.read_long();
let datetime = DateTime::from_timestamp(unix_timestamp as i64, 0) let datetime = DateTime::from_timestamp(unix_timestamp, 0)
.ok_or("Timestamp is out of bounds")?; .ok_or("Timestamp is out of bounds")?;
Ok(SqliteTimestamp(datetime)) Ok(SqliteTimestamp(datetime))

View File

@@ -1,24 +1,24 @@
pub mod abi; pub mod abi;
pub mod safe_signer; pub mod safe_signer;
use alloy::{consensus::TxEip1559, primitives::{TxKind, U256}}; use alloy::{
consensus::TxEip1559,
primitives::{TxKind, U256},
};
use chrono::Utc; use chrono::Utc;
use diesel::{QueryResult, insert_into, sqlite::Sqlite}; use diesel::{ExpressionMethods as _, QueryDsl, QueryResult, insert_into, sqlite::Sqlite};
use diesel_async::{AsyncConnection, RunQueryDsl}; use diesel_async::{AsyncConnection, RunQueryDsl};
use crate::{ use crate::{
db::{ db::{
self, self,
models::{ models::{EvmBasicGrant, NewEvmBasicGrant, NewEvmTransactionLog, SqliteTimestamp},
EvmBasicGrant, NewEvmBasicGrant, NewEvmTransactionLog,
SqliteTimestamp,
},
schema::{self, evm_transaction_log}, schema::{self, evm_transaction_log},
}, },
evm::policies::{ evm::policies::{
DatabaseID, EvalContext, EvalViolation, FullGrant, Grant, Policy, SharedGrantSettings, DatabaseID, EvalContext, EvalViolation, FullGrant, Grant, Policy, SharedGrantSettings,
SpecificGrant, SpecificMeaning, SpecificGrant, SpecificMeaning, ether_transfer::EtherTransfer,
ether_transfer::EtherTransfer, token_transfers::TokenTransfer, token_transfers::TokenTransfer,
}, },
}; };
@@ -55,7 +55,6 @@ pub enum VetError {
Evaluated(SpecificMeaning, #[source] PolicyError), Evaluated(SpecificMeaning, #[source] PolicyError),
} }
#[derive(Debug, thiserror::Error, miette::Diagnostic)] #[derive(Debug, thiserror::Error, miette::Diagnostic)]
pub enum SignError { pub enum SignError {
#[error("Database connection pool error")] #[error("Database connection pool error")]
@@ -118,8 +117,7 @@ async fn check_shared_constraints(
let now = Utc::now(); let now = Utc::now();
// Validity window // Validity window
if shared.valid_from.map_or(false, |t| now < t) if shared.valid_from.map_or(false, |t| now < t) || shared.valid_until.map_or(false, |t| now > t)
|| shared.valid_until.map_or(false, |t| now > t)
{ {
violations.push(EvalViolation::InvalidTime); violations.push(EvalViolation::InvalidTime);
} }
@@ -128,9 +126,9 @@ async fn check_shared_constraints(
let fee_exceeded = shared let fee_exceeded = shared
.max_gas_fee_per_gas .max_gas_fee_per_gas
.map_or(false, |cap| U256::from(context.max_fee_per_gas) > cap); .map_or(false, |cap| U256::from(context.max_fee_per_gas) > cap);
let priority_exceeded = shared let priority_exceeded = shared.max_priority_fee_per_gas.map_or(false, |cap| {
.max_priority_fee_per_gas U256::from(context.max_priority_fee_per_gas) > cap
.map_or(false, |cap| U256::from(context.max_priority_fee_per_gas) > cap); });
if fee_exceeded || priority_exceeded { if fee_exceeded || priority_exceeded {
violations.push(EvalViolation::GasLimitExceeded { violations.push(EvalViolation::GasLimitExceeded {
max_gas_fee_per_gas: shared.max_gas_fee_per_gas, max_gas_fee_per_gas: shared.max_gas_fee_per_gas,
@@ -274,13 +272,23 @@ impl Engine {
EtherTransfer::find_all_grants(&mut conn) EtherTransfer::find_all_grants(&mut conn)
.await? .await?
.into_iter() .into_iter()
.map(Grant::from), .map(|g| Grant {
id: g.id,
shared_grant_id: g.shared_grant_id,
shared: g.shared,
settings: SpecificGrant::EtherTransfer(g.settings),
}),
); );
grants.extend( grants.extend(
TokenTransfer::find_all_grants(&mut conn) TokenTransfer::find_all_grants(&mut conn)
.await? .await?
.into_iter() .into_iter()
.map(Grant::from), .map(|g| Grant {
id: g.id,
shared_grant_id: g.shared_grant_id,
shared: g.shared,
settings: SpecificGrant::TokenTransfer(g.settings),
}),
); );
Ok(grants) Ok(grants)

View File

@@ -17,6 +17,7 @@ use crate::{
pub mod ether_transfer; pub mod ether_transfer;
pub mod token_transfers; pub mod token_transfers;
#[derive(Debug, Clone)]
pub struct EvalContext { pub struct EvalContext {
// Which wallet is this transaction for // Which wallet is this transaction for
pub client_id: i32, pub client_id: i32,
@@ -72,6 +73,7 @@ pub struct Grant<PolicySettings> {
pub settings: PolicySettings, pub settings: PolicySettings,
} }
pub trait Policy: Sized { pub trait Policy: Sized {
type Settings: Send + Sync + 'static + Into<SpecificGrant>; type Settings: Send + Sync + 'static + Into<SpecificGrant>;
type Meaning: Display + std::fmt::Debug + Send + Sync + 'static + Into<SpecificMeaning>; type Meaning: Display + std::fmt::Debug + Send + Sync + 'static + Into<SpecificMeaning>;
@@ -201,19 +203,6 @@ pub enum SpecificGrant {
TokenTransfer(token_transfers::Settings), TokenTransfer(token_transfers::Settings),
} }
/// Blanket conversion from a typed `Grant<S>` into `Grant<SpecificGrant>`.
/// Lets the engine collect across all policies into one `Vec<Grant<SpecificGrant>>`.
impl<S: Into<SpecificGrant>> From<Grant<S>> for Grant<SpecificGrant> {
fn from(g: Grant<S>) -> Self {
Grant {
id: g.id,
shared_grant_id: g.shared_grant_id,
shared: g.shared,
settings: g.settings.into(),
}
}
}
pub struct FullGrant<PolicyGrant> { pub struct FullGrant<PolicyGrant> {
pub basic: SharedGrantSettings, pub basic: SharedGrantSettings,
pub specific: PolicyGrant, pub specific: PolicyGrant,

View File

@@ -3,7 +3,7 @@ use std::fmt::Display;
use alloy::primitives::{Address, U256}; use alloy::primitives::{Address, U256};
use chrono::{DateTime, Duration, Utc}; use chrono::{DateTime, Duration, Utc};
use diesel::dsl::insert_into; use diesel::dsl::{auto_type, insert_into};
use diesel::sqlite::Sqlite; use diesel::sqlite::Sqlite;
use diesel::{ExpressionMethods, JoinOnDsl, prelude::*}; use diesel::{ExpressionMethods, JoinOnDsl, prelude::*};
use diesel_async::{AsyncConnection, RunQueryDsl}; use diesel_async::{AsyncConnection, RunQueryDsl};
@@ -24,11 +24,10 @@ use crate::{
evm::{policies::Policy, utils}, evm::{policies::Policy, utils},
}; };
#[diesel::auto_type] #[auto_type]
fn grant_join() -> _ { fn grant_join() -> _ {
evm_ether_transfer_grant::table.inner_join( evm_ether_transfer_grant::table.inner_join(
evm_basic_grant::table evm_basic_grant::table.on(evm_ether_transfer_grant::basic_grant_id.eq(evm_basic_grant::id)),
.on(evm_ether_transfer_grant::basic_grant_id.eq(evm_basic_grant::id)),
) )
} }
@@ -197,11 +196,16 @@ impl Policy for EtherTransfer {
// Find a grant where: // Find a grant where:
// 1. The basic grant's wallet_id and client_id match the context // 1. The basic grant's wallet_id and client_id match the context
// 2. Any of the grant's targets match the context's `to` address // 2. Any of the grant's targets match the context's `to` address
let grant: Option<(EvmBasicGrant, EvmEtherTransferGrant)> = grant_join() let grant: Option<(EvmBasicGrant, EvmEtherTransferGrant)> = evm_ether_transfer_grant::table
.filter(evm_basic_grant::wallet_id.eq(context.wallet_id)) .inner_join(evm_basic_grant::table)
.filter(evm_basic_grant::client_id.eq(context.client_id)) .inner_join(evm_ether_transfer_grant_target::table)
.filter(evm_ether_transfer_grant_target::address.eq(&target_bytes)) .filter(
.filter(evm_basic_grant::revoked_at.is_null()) evm_basic_grant::wallet_id
.eq(context.wallet_id)
.and(evm_basic_grant::client_id.eq(context.client_id))
.and(evm_basic_grant::revoked_at.is_null())
.and(evm_ether_transfer_grant_target::address.eq(&target_bytes)),
)
.select(( .select((
EvmBasicGrant::as_select(), EvmBasicGrant::as_select(),
EvmEtherTransferGrant::as_select(), EvmEtherTransferGrant::as_select(),
@@ -270,7 +274,10 @@ impl Policy for EtherTransfer {
) -> QueryResult<Vec<Grant<Self::Settings>>> { ) -> QueryResult<Vec<Grant<Self::Settings>>> {
let grants: Vec<(EvmBasicGrant, EvmEtherTransferGrant)> = grant_join() let grants: Vec<(EvmBasicGrant, EvmEtherTransferGrant)> = grant_join()
.filter(evm_basic_grant::revoked_at.is_null()) .filter(evm_basic_grant::revoked_at.is_null())
.select((EvmBasicGrant::as_select(), EvmEtherTransferGrant::as_select())) .select((
EvmBasicGrant::as_select(),
EvmEtherTransferGrant::as_select(),
))
.load(conn) .load(conn)
.await?; .await?;
@@ -295,7 +302,10 @@ impl Policy for EtherTransfer {
let mut targets_by_grant: HashMap<i32, Vec<EvmEtherTransferGrantTarget>> = HashMap::new(); let mut targets_by_grant: HashMap<i32, Vec<EvmEtherTransferGrantTarget>> = HashMap::new();
for target in all_targets { for target in all_targets {
targets_by_grant.entry(target.grant_id).or_default().push(target); targets_by_grant
.entry(target.grant_id)
.or_default()
.push(target);
} }
let limits_by_id: HashMap<i32, EvmEtherTransferLimit> = let limits_by_id: HashMap<i32, EvmEtherTransferLimit> =
@@ -326,8 +336,9 @@ impl Policy for EtherTransfer {
settings: Settings { settings: Settings {
target: targets, target: targets,
limit: VolumeRateLimit { limit: VolumeRateLimit {
max_volume: utils::try_bytes_to_u256(&limit.max_volume) max_volume: utils::try_bytes_to_u256(&limit.max_volume).map_err(
.map_err(|e| diesel::result::Error::DeserializationError(Box::new(e)))?, |e| diesel::result::Error::DeserializationError(Box::new(e)),
)?,
window: Duration::seconds(limit.window_secs as i64), window: Duration::seconds(limit.window_secs as i64),
}, },
}, },
@@ -336,3 +347,6 @@ impl Policy for EtherTransfer {
.collect() .collect()
} }
} }
#[cfg(test)]
mod tests;

View File

@@ -0,0 +1,387 @@
use alloy::primitives::{Address, Bytes, U256, address};
use chrono::{Duration, Utc};
use diesel::{SelectableHelper, insert_into};
use diesel_async::RunQueryDsl;
use crate::db::{
self, DatabaseConnection,
models::{EvmBasicGrant, NewEvmBasicGrant, NewEvmTransactionLog, SqliteTimestamp},
schema::{evm_basic_grant, evm_transaction_log},
};
use crate::evm::{
policies::{
EvalContext, EvalViolation, Grant, Policy, SharedGrantSettings, VolumeRateLimit,
},
utils,
};
use super::{EtherTransfer, Settings};
const WALLET_ID: i32 = 1;
const CLIENT_ID: i32 = 2;
const CHAIN_ID: u64 = 1;
const ALLOWED: Address = address!("1111111111111111111111111111111111111111");
const OTHER: Address = address!("2222222222222222222222222222222222222222");
fn ctx(to: Address, value: U256) -> EvalContext {
EvalContext {
wallet_id: WALLET_ID,
client_id: CLIENT_ID,
chain: CHAIN_ID,
to,
value,
calldata: Bytes::new(),
max_fee_per_gas: 0,
max_priority_fee_per_gas: 0,
}
}
async fn insert_basic(conn: &mut DatabaseConnection, revoked: bool) -> EvmBasicGrant {
insert_into(evm_basic_grant::table)
.values(NewEvmBasicGrant {
wallet_id: WALLET_ID,
client_id: CLIENT_ID,
chain_id: CHAIN_ID as i32,
valid_from: None,
valid_until: None,
max_gas_fee_per_gas: None,
max_priority_fee_per_gas: None,
rate_limit_count: None,
rate_limit_window_secs: None,
revoked_at: revoked.then(|| SqliteTimestamp(Utc::now())),
})
.returning(EvmBasicGrant::as_select())
.get_result(conn)
.await
.unwrap()
}
fn make_settings(targets: Vec<Address>, max_volume: u64) -> Settings {
Settings {
target: targets,
limit: VolumeRateLimit {
max_volume: U256::from(max_volume),
window: Duration::hours(1),
},
}
}
fn shared() -> SharedGrantSettings {
SharedGrantSettings {
wallet_id: WALLET_ID,
chain: CHAIN_ID,
valid_from: None,
valid_until: None,
max_gas_fee_per_gas: None,
max_priority_fee_per_gas: None,
rate_limit: None,
}
}
// ── analyze ─────────────────────────────────────────────────────────────
#[test]
fn analyze_matches_empty_calldata() {
let m = EtherTransfer::analyze(&ctx(ALLOWED, U256::from(1_000u64))).unwrap();
assert_eq!(m.to, ALLOWED);
assert_eq!(m.value, U256::from(1_000u64));
}
#[test]
fn analyze_rejects_nonempty_calldata() {
let context = EvalContext {
calldata: Bytes::from(vec![0xde, 0xad, 0xbe, 0xef]),
..ctx(ALLOWED, U256::from(1u64))
};
assert!(EtherTransfer::analyze(&context).is_none());
}
// ── evaluate ────────────────────────────────────────────────────────────
#[tokio::test]
async fn evaluate_passes_for_allowed_target() {
let db = db::create_test_pool().await;
let mut conn = db.get().await.unwrap();
let grant = Grant {
id: 999,
shared_grant_id: 999,
shared: shared(),
settings: make_settings(vec![ALLOWED], 1_000_000),
};
let context = ctx(ALLOWED, U256::from(100u64));
let m = EtherTransfer::analyze(&context).unwrap();
let v = EtherTransfer::evaluate(&context, &m, &grant, &mut *conn)
.await
.unwrap();
assert!(v.is_empty());
}
#[tokio::test]
async fn evaluate_rejects_disallowed_target() {
let db = db::create_test_pool().await;
let mut conn = db.get().await.unwrap();
let grant = Grant {
id: 999,
shared_grant_id: 999,
shared: shared(),
settings: make_settings(vec![ALLOWED], 1_000_000),
};
let context = ctx(OTHER, U256::from(100u64));
let m = EtherTransfer::analyze(&context).unwrap();
let v = EtherTransfer::evaluate(&context, &m, &grant, &mut *conn)
.await
.unwrap();
assert!(
v.iter()
.any(|e| matches!(e, EvalViolation::InvalidTarget { .. }))
);
}
#[tokio::test]
async fn evaluate_passes_when_volume_within_limit() {
let db = db::create_test_pool().await;
let mut conn = db.get().await.unwrap();
let basic = insert_basic(&mut conn, false).await;
let settings = make_settings(vec![ALLOWED], 1_000);
let grant_id = EtherTransfer::create_grant(&basic, &settings, &mut *conn)
.await
.unwrap();
insert_into(evm_transaction_log::table)
.values(NewEvmTransactionLog {
grant_id,
client_id: CLIENT_ID,
wallet_id: WALLET_ID,
chain_id: CHAIN_ID as i32,
eth_value: utils::u256_to_bytes(U256::from(500u64)).to_vec(),
signed_at: SqliteTimestamp(Utc::now()),
})
.execute(&mut *conn)
.await
.unwrap();
let grant = Grant {
id: grant_id,
shared_grant_id: basic.id,
shared: shared(),
settings,
};
let context = ctx(ALLOWED, U256::from(100u64));
let m = EtherTransfer::analyze(&context).unwrap();
let v = EtherTransfer::evaluate(&context, &m, &grant, &mut *conn)
.await
.unwrap();
assert!(
!v.iter()
.any(|e| matches!(e, EvalViolation::VolumetricLimitExceeded))
);
}
#[tokio::test]
async fn evaluate_rejects_volume_over_limit() {
let db = db::create_test_pool().await;
let mut conn = db.get().await.unwrap();
let basic = insert_basic(&mut conn, false).await;
let settings = make_settings(vec![ALLOWED], 1_000);
let grant_id = EtherTransfer::create_grant(&basic, &settings, &mut *conn)
.await
.unwrap();
insert_into(evm_transaction_log::table)
.values(NewEvmTransactionLog {
grant_id,
client_id: CLIENT_ID,
wallet_id: WALLET_ID,
chain_id: CHAIN_ID as i32,
eth_value: utils::u256_to_bytes(U256::from(1_001u64)).to_vec(),
signed_at: SqliteTimestamp(Utc::now()),
})
.execute(&mut *conn)
.await
.unwrap();
let grant = Grant {
id: grant_id,
shared_grant_id: basic.id,
shared: shared(),
settings,
};
let context = ctx(ALLOWED, U256::from(100u64));
let m = EtherTransfer::analyze(&context).unwrap();
let v = EtherTransfer::evaluate(&context, &m, &grant, &mut *conn)
.await
.unwrap();
assert!(
v.iter()
.any(|e| matches!(e, EvalViolation::VolumetricLimitExceeded))
);
}
#[tokio::test]
async fn evaluate_passes_at_exactly_volume_limit() {
let db = db::create_test_pool().await;
let mut conn = db.get().await.unwrap();
let basic = insert_basic(&mut conn, false).await;
let settings = make_settings(vec![ALLOWED], 1_000);
let grant_id = EtherTransfer::create_grant(&basic, &settings, &mut *conn)
.await
.unwrap();
// Exactly at the limit — the check is `>`, so this should not violate
insert_into(evm_transaction_log::table)
.values(NewEvmTransactionLog {
grant_id,
client_id: CLIENT_ID,
wallet_id: WALLET_ID,
chain_id: CHAIN_ID as i32,
eth_value: utils::u256_to_bytes(U256::from(1_000u64)).to_vec(),
signed_at: SqliteTimestamp(Utc::now()),
})
.execute(&mut *conn)
.await
.unwrap();
let grant = Grant {
id: grant_id,
shared_grant_id: basic.id,
shared: shared(),
settings,
};
let context = ctx(ALLOWED, U256::from(100u64));
let m = EtherTransfer::analyze(&context).unwrap();
let v = EtherTransfer::evaluate(&context, &m, &grant, &mut *conn)
.await
.unwrap();
assert!(
!v.iter()
.any(|e| matches!(e, EvalViolation::VolumetricLimitExceeded))
);
}
// ── try_find_grant ───────────────────────────────────────────────────────
#[tokio::test]
async fn try_find_grant_roundtrip() {
let db = db::create_test_pool().await;
let mut conn = db.get().await.unwrap();
let basic = insert_basic(&mut conn, false).await;
let settings = make_settings(vec![ALLOWED], 1_000_000);
EtherTransfer::create_grant(&basic, &settings, &mut *conn)
.await
.unwrap();
let found = EtherTransfer::try_find_grant(&ctx(ALLOWED, U256::from(1u64)), &mut *conn)
.await
.unwrap();
assert!(found.is_some());
let g = found.unwrap();
assert_eq!(g.settings.target, vec![ALLOWED]);
assert_eq!(g.settings.limit.max_volume, U256::from(1_000_000u64));
}
#[tokio::test]
async fn try_find_grant_revoked_returns_none() {
let db = db::create_test_pool().await;
let mut conn = db.get().await.unwrap();
let basic = insert_basic(&mut conn, true).await;
let settings = make_settings(vec![ALLOWED], 1_000_000);
EtherTransfer::create_grant(&basic, &settings, &mut *conn)
.await
.unwrap();
let found = EtherTransfer::try_find_grant(&ctx(ALLOWED, U256::from(1u64)), &mut *conn)
.await
.unwrap();
assert!(found.is_none());
}
#[tokio::test]
async fn try_find_grant_wrong_target_returns_none() {
let db = db::create_test_pool().await;
let mut conn = db.get().await.unwrap();
let basic = insert_basic(&mut conn, false).await;
let settings = make_settings(vec![ALLOWED], 1_000_000);
EtherTransfer::create_grant(&basic, &settings, &mut *conn)
.await
.unwrap();
let found = EtherTransfer::try_find_grant(&ctx(OTHER, U256::from(1u64)), &mut *conn)
.await
.unwrap();
assert!(found.is_none());
}
// ── find_all_grants ──────────────────────────────────────────────────────
#[tokio::test]
async fn find_all_grants_empty_db() {
let db = db::create_test_pool().await;
let mut conn = db.get().await.unwrap();
let all = EtherTransfer::find_all_grants(&mut *conn).await.unwrap();
assert!(all.is_empty());
}
#[tokio::test]
async fn find_all_grants_excludes_revoked() {
let db = db::create_test_pool().await;
let mut conn = db.get().await.unwrap();
let settings = make_settings(vec![ALLOWED], 1_000_000);
let active = insert_basic(&mut conn, false).await;
EtherTransfer::create_grant(&active, &settings, &mut *conn)
.await
.unwrap();
let revoked = insert_basic(&mut conn, true).await;
EtherTransfer::create_grant(&revoked, &settings, &mut *conn)
.await
.unwrap();
let all = EtherTransfer::find_all_grants(&mut *conn).await.unwrap();
assert_eq!(all.len(), 1);
assert_eq!(all[0].settings.target, vec![ALLOWED]);
}
#[tokio::test]
async fn find_all_grants_multiple_targets() {
let db = db::create_test_pool().await;
let mut conn = db.get().await.unwrap();
let basic = insert_basic(&mut conn, false).await;
let settings = make_settings(vec![ALLOWED, OTHER], 1_000_000);
EtherTransfer::create_grant(&basic, &settings, &mut *conn)
.await
.unwrap();
let all = EtherTransfer::find_all_grants(&mut *conn).await.unwrap();
assert_eq!(all.len(), 1);
assert_eq!(all[0].settings.target.len(), 2);
assert_eq!(all[0].settings.limit.max_volume, U256::from(1_000_000u64));
}
#[tokio::test]
async fn find_all_grants_multiple_grants() {
let db = db::create_test_pool().await;
let mut conn = db.get().await.unwrap();
let basic1 = insert_basic(&mut conn, false).await;
EtherTransfer::create_grant(&basic1, &make_settings(vec![ALLOWED], 500), &mut *conn)
.await
.unwrap();
let basic2 = insert_basic(&mut conn, false).await;
EtherTransfer::create_grant(&basic2, &make_settings(vec![OTHER], 1_000), &mut *conn)
.await
.unwrap();
let all = EtherTransfer::find_all_grants(&mut *conn).await.unwrap();
assert_eq!(all.len(), 2);
}

View File

@@ -6,7 +6,7 @@ use alloy::{
}; };
use arbiter_tokens_registry::evm::nonfungible::{self, TokenInfo}; use arbiter_tokens_registry::evm::nonfungible::{self, TokenInfo};
use chrono::{DateTime, Duration, Utc}; use chrono::{DateTime, Duration, Utc};
use diesel::dsl::insert_into; use diesel::dsl::{auto_type, insert_into};
use diesel::sqlite::Sqlite; use diesel::sqlite::Sqlite;
use diesel::{ExpressionMethods, prelude::*}; use diesel::{ExpressionMethods, prelude::*};
use diesel_async::{AsyncConnection, RunQueryDsl}; use diesel_async::{AsyncConnection, RunQueryDsl};
@@ -29,7 +29,7 @@ use crate::evm::{
use super::{DatabaseID, EvalContext, EvalViolation}; use super::{DatabaseID, EvalContext, EvalViolation};
#[diesel::auto_type] #[auto_type]
fn grant_join() -> _ { fn grant_join() -> _ {
evm_token_transfer_grant::table.inner_join( evm_token_transfer_grant::table.inner_join(
evm_basic_grant::table.on(evm_token_transfer_grant::basic_grant_id.eq(evm_basic_grant::id)), evm_basic_grant::table.on(evm_token_transfer_grant::basic_grant_id.eq(evm_basic_grant::id)),
@@ -380,3 +380,6 @@ impl Policy for TokenTransfer {
.collect() .collect()
} }
} }
#[cfg(test)]
mod tests;

View File

@@ -0,0 +1,397 @@
use alloy::primitives::{Address, Bytes, U256, address};
use alloy::sol_types::SolCall;
use chrono::{Duration, Utc};
use diesel::{SelectableHelper, insert_into};
use diesel_async::RunQueryDsl;
use crate::db::{
self, DatabaseConnection,
models::{EvmBasicGrant, NewEvmBasicGrant, SqliteTimestamp},
schema::evm_basic_grant,
};
use crate::evm::{
abi::IERC20::transferCall,
policies::{EvalContext, EvalViolation, Grant, Policy, SharedGrantSettings, VolumeRateLimit},
utils,
};
use super::{Settings, TokenTransfer};
// DAI on Ethereum mainnet — present in the static token registry
const CHAIN_ID: u64 = 1;
const DAI: Address = address!("6B175474E89094C44Da98b954EedeAC495271d0F");
const WALLET_ID: i32 = 1;
const CLIENT_ID: i32 = 2;
const RECIPIENT: Address = address!("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
const OTHER: Address = address!("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");
const UNKNOWN_TOKEN: Address = address!("cccccccccccccccccccccccccccccccccccccccc");
/// Encode `transfer(to, value)` raw params (no 4-byte selector).
/// `abi_decode_raw_validate` expects exactly this format.
fn transfer_calldata(to: Address, value: U256) -> Bytes {
let mut raw = Vec::new();
transferCall { to, value }.abi_encode_raw(&mut raw);
Bytes::from(raw)
}
fn ctx(to: Address, calldata: Bytes) -> EvalContext {
EvalContext {
wallet_id: WALLET_ID,
client_id: CLIENT_ID,
chain: CHAIN_ID,
to,
value: U256::ZERO,
calldata,
max_fee_per_gas: 0,
max_priority_fee_per_gas: 0,
}
}
async fn insert_basic(conn: &mut DatabaseConnection, revoked: bool) -> EvmBasicGrant {
insert_into(evm_basic_grant::table)
.values(NewEvmBasicGrant {
wallet_id: WALLET_ID,
client_id: CLIENT_ID,
chain_id: CHAIN_ID as i32,
valid_from: None,
valid_until: None,
max_gas_fee_per_gas: None,
max_priority_fee_per_gas: None,
rate_limit_count: None,
rate_limit_window_secs: None,
revoked_at: revoked.then(|| SqliteTimestamp(Utc::now())),
})
.returning(EvmBasicGrant::as_select())
.get_result(conn)
.await
.unwrap()
}
fn make_settings(target: Option<Address>, max_volume: Option<u64>) -> Settings {
Settings {
token_contract: DAI,
target,
volume_limits: max_volume
.map(|v| {
vec![VolumeRateLimit {
max_volume: U256::from(v),
window: Duration::hours(1),
}]
})
.unwrap_or_default(),
}
}
fn shared() -> SharedGrantSettings {
SharedGrantSettings {
wallet_id: WALLET_ID,
chain: CHAIN_ID,
valid_from: None,
valid_until: None,
max_gas_fee_per_gas: None,
max_priority_fee_per_gas: None,
rate_limit: None,
}
}
// ── analyze ─────────────────────────────────────────────────────────────
#[test]
fn analyze_known_token_valid_calldata() {
let calldata = transfer_calldata(RECIPIENT, U256::from(100u64));
let m = TokenTransfer::analyze(&ctx(DAI, calldata)).unwrap();
assert_eq!(m.to, RECIPIENT);
assert_eq!(m.value, U256::from(100u64));
}
#[test]
fn analyze_unknown_token_returns_none() {
let calldata = transfer_calldata(RECIPIENT, U256::from(100u64));
assert!(TokenTransfer::analyze(&ctx(UNKNOWN_TOKEN, calldata)).is_none());
}
#[test]
fn analyze_invalid_calldata_returns_none() {
let calldata = Bytes::from(vec![0xde, 0xad, 0xbe, 0xef]);
assert!(TokenTransfer::analyze(&ctx(DAI, calldata)).is_none());
}
#[test]
fn analyze_empty_calldata_returns_none() {
assert!(TokenTransfer::analyze(&ctx(DAI, Bytes::new())).is_none());
}
// ── evaluate ────────────────────────────────────────────────────────────
#[tokio::test]
async fn evaluate_rejects_nonzero_eth_value() {
let db = db::create_test_pool().await;
let mut conn = db.get().await.unwrap();
let grant = Grant {
id: 999,
shared_grant_id: 999,
shared: shared(),
settings: make_settings(None, None),
};
let calldata = transfer_calldata(RECIPIENT, U256::from(100u64));
let mut context = ctx(DAI, calldata);
context.value = U256::from(1u64); // ETH attached to an ERC-20 call
let m = TokenTransfer::analyze(&EvalContext { value: U256::ZERO, ..context.clone() })
.unwrap();
let v = TokenTransfer::evaluate(&context, &m, &grant, &mut *conn).await.unwrap();
assert!(v.iter().any(|e| matches!(e, EvalViolation::InvalidTransactionType)));
}
#[tokio::test]
async fn evaluate_passes_any_recipient_when_no_restriction() {
let db = db::create_test_pool().await;
let mut conn = db.get().await.unwrap();
let grant = Grant {
id: 999,
shared_grant_id: 999,
shared: shared(),
settings: make_settings(None, None),
};
let calldata = transfer_calldata(RECIPIENT, U256::from(100u64));
let context = ctx(DAI, calldata);
let m = TokenTransfer::analyze(&context).unwrap();
let v = TokenTransfer::evaluate(&context, &m, &grant, &mut *conn).await.unwrap();
assert!(v.is_empty());
}
#[tokio::test]
async fn evaluate_passes_matching_restricted_recipient() {
let db = db::create_test_pool().await;
let mut conn = db.get().await.unwrap();
let grant = Grant {
id: 999,
shared_grant_id: 999,
shared: shared(),
settings: make_settings(Some(RECIPIENT), None),
};
let calldata = transfer_calldata(RECIPIENT, U256::from(100u64));
let context = ctx(DAI, calldata);
let m = TokenTransfer::analyze(&context).unwrap();
let v = TokenTransfer::evaluate(&context, &m, &grant, &mut *conn).await.unwrap();
assert!(v.is_empty());
}
#[tokio::test]
async fn evaluate_rejects_wrong_restricted_recipient() {
let db = db::create_test_pool().await;
let mut conn = db.get().await.unwrap();
let grant = Grant {
id: 999,
shared_grant_id: 999,
shared: shared(),
settings: make_settings(Some(RECIPIENT), None),
};
let calldata = transfer_calldata(OTHER, U256::from(100u64));
let context = ctx(DAI, calldata);
let m = TokenTransfer::analyze(&context).unwrap();
let v = TokenTransfer::evaluate(&context, &m, &grant, &mut *conn).await.unwrap();
assert!(v.iter().any(|e| matches!(e, EvalViolation::InvalidTarget { .. })));
}
#[tokio::test]
async fn evaluate_passes_volume_within_limit() {
let db = db::create_test_pool().await;
let mut conn = db.get().await.unwrap();
let basic = insert_basic(&mut conn, false).await;
let settings = make_settings(None, Some(1_000));
let grant_id = TokenTransfer::create_grant(&basic, &settings, &mut *conn).await.unwrap();
// Record a past transfer of 500 (within 1000 limit)
use crate::db::{models::NewEvmTokenTransferLog, schema::evm_token_transfer_log};
insert_into(evm_token_transfer_log::table)
.values(NewEvmTokenTransferLog {
grant_id,
log_id: 0,
chain_id: CHAIN_ID as i32,
token_contract: DAI.to_vec(),
recipient_address: RECIPIENT.to_vec(),
value: utils::u256_to_bytes(U256::from(500u64)).to_vec(),
})
.execute(&mut *conn)
.await
.unwrap();
let grant = Grant { id: grant_id, shared_grant_id: basic.id, shared: shared(), settings };
let calldata = transfer_calldata(RECIPIENT, U256::from(100u64));
let context = ctx(DAI, calldata);
let m = TokenTransfer::analyze(&context).unwrap();
let v = TokenTransfer::evaluate(&context, &m, &grant, &mut *conn).await.unwrap();
assert!(!v.iter().any(|e| matches!(e, EvalViolation::VolumetricLimitExceeded)));
}
#[tokio::test]
async fn evaluate_rejects_volume_over_limit() {
let db = db::create_test_pool().await;
let mut conn = db.get().await.unwrap();
let basic = insert_basic(&mut conn, false).await;
let settings = make_settings(None, Some(1_000));
let grant_id = TokenTransfer::create_grant(&basic, &settings, &mut *conn).await.unwrap();
use crate::db::{models::NewEvmTokenTransferLog, schema::evm_token_transfer_log};
insert_into(evm_token_transfer_log::table)
.values(NewEvmTokenTransferLog {
grant_id,
log_id: 0,
chain_id: CHAIN_ID as i32,
token_contract: DAI.to_vec(),
recipient_address: RECIPIENT.to_vec(),
value: utils::u256_to_bytes(U256::from(1_001u64)).to_vec(),
})
.execute(&mut *conn)
.await
.unwrap();
let grant = Grant { id: grant_id, shared_grant_id: basic.id, shared: shared(), settings };
let calldata = transfer_calldata(RECIPIENT, U256::from(100u64));
let context = ctx(DAI, calldata);
let m = TokenTransfer::analyze(&context).unwrap();
let v = TokenTransfer::evaluate(&context, &m, &grant, &mut *conn).await.unwrap();
assert!(v.iter().any(|e| matches!(e, EvalViolation::VolumetricLimitExceeded)));
}
#[tokio::test]
async fn evaluate_no_volume_limits_always_passes() {
let db = db::create_test_pool().await;
let mut conn = db.get().await.unwrap();
let grant = Grant {
id: 999,
shared_grant_id: 999,
shared: shared(),
settings: make_settings(None, None), // no volume limits
};
let calldata = transfer_calldata(RECIPIENT, U256::from(u64::MAX));
let context = ctx(DAI, calldata);
let m = TokenTransfer::analyze(&context).unwrap();
let v = TokenTransfer::evaluate(&context, &m, &grant, &mut *conn).await.unwrap();
assert!(!v.iter().any(|e| matches!(e, EvalViolation::VolumetricLimitExceeded)));
}
// ── try_find_grant ───────────────────────────────────────────────────────
#[tokio::test]
async fn try_find_grant_roundtrip() {
let db = db::create_test_pool().await;
let mut conn = db.get().await.unwrap();
let basic = insert_basic(&mut conn, false).await;
let settings = make_settings(Some(RECIPIENT), Some(5_000));
TokenTransfer::create_grant(&basic, &settings, &mut *conn).await.unwrap();
let calldata = transfer_calldata(RECIPIENT, U256::from(100u64));
let found = TokenTransfer::try_find_grant(&ctx(DAI, calldata), &mut *conn)
.await
.unwrap();
assert!(found.is_some());
let g = found.unwrap();
assert_eq!(g.settings.token_contract, DAI);
assert_eq!(g.settings.target, Some(RECIPIENT));
assert_eq!(g.settings.volume_limits.len(), 1);
assert_eq!(g.settings.volume_limits[0].max_volume, U256::from(5_000u64));
}
#[tokio::test]
async fn try_find_grant_revoked_returns_none() {
let db = db::create_test_pool().await;
let mut conn = db.get().await.unwrap();
let basic = insert_basic(&mut conn, true).await;
let settings = make_settings(None, None);
TokenTransfer::create_grant(&basic, &settings, &mut *conn).await.unwrap();
let calldata = transfer_calldata(RECIPIENT, U256::from(1u64));
let found = TokenTransfer::try_find_grant(&ctx(DAI, calldata), &mut *conn)
.await
.unwrap();
assert!(found.is_none());
}
#[tokio::test]
async fn try_find_grant_unknown_token_returns_none() {
let db = db::create_test_pool().await;
let mut conn = db.get().await.unwrap();
let basic = insert_basic(&mut conn, false).await;
let settings = make_settings(None, None);
TokenTransfer::create_grant(&basic, &settings, &mut *conn).await.unwrap();
// Query with a different token contract
let calldata = transfer_calldata(RECIPIENT, U256::from(1u64));
let found = TokenTransfer::try_find_grant(&ctx(UNKNOWN_TOKEN, calldata), &mut *conn)
.await
.unwrap();
assert!(found.is_none());
}
// ── find_all_grants ──────────────────────────────────────────────────────
#[tokio::test]
async fn find_all_grants_empty_db() {
let db = db::create_test_pool().await;
let mut conn = db.get().await.unwrap();
let all = TokenTransfer::find_all_grants(&mut *conn).await.unwrap();
assert!(all.is_empty());
}
#[tokio::test]
async fn find_all_grants_excludes_revoked() {
let db = db::create_test_pool().await;
let mut conn = db.get().await.unwrap();
let settings = make_settings(None, Some(1_000));
let active = insert_basic(&mut conn, false).await;
TokenTransfer::create_grant(&active, &settings, &mut *conn).await.unwrap();
let revoked = insert_basic(&mut conn, true).await;
TokenTransfer::create_grant(&revoked, &settings, &mut *conn).await.unwrap();
let all = TokenTransfer::find_all_grants(&mut *conn).await.unwrap();
assert_eq!(all.len(), 1);
}
#[tokio::test]
async fn find_all_grants_loads_volume_limits() {
let db = db::create_test_pool().await;
let mut conn = db.get().await.unwrap();
let basic = insert_basic(&mut conn, false).await;
let settings = make_settings(None, Some(9_999));
TokenTransfer::create_grant(&basic, &settings, &mut *conn).await.unwrap();
let all = TokenTransfer::find_all_grants(&mut *conn).await.unwrap();
assert_eq!(all.len(), 1);
assert_eq!(all[0].settings.volume_limits.len(), 1);
assert_eq!(all[0].settings.volume_limits[0].max_volume, U256::from(9_999u64));
}
#[tokio::test]
async fn find_all_grants_multiple_grants_batch_loaded() {
let db = db::create_test_pool().await;
let mut conn = db.get().await.unwrap();
let b1 = insert_basic(&mut conn, false).await;
TokenTransfer::create_grant(&b1, &make_settings(None, Some(1_000)), &mut *conn)
.await
.unwrap();
let b2 = insert_basic(&mut conn, false).await;
TokenTransfer::create_grant(&b2, &make_settings(Some(RECIPIENT), Some(2_000)), &mut *conn)
.await
.unwrap();
let all = TokenTransfer::find_all_grants(&mut *conn).await.unwrap();
assert_eq!(all.len(), 2);
}

View File

@@ -0,0 +1,31 @@
Extension Discovery Cache
=========================
This folder is used by `package:extension_discovery` to cache lists of
packages that contains extensions for other packages.
DO NOT USE THIS FOLDER
----------------------
* Do not read (or rely) the contents of this folder.
* Do write to this folder.
If you're interested in the lists of extensions stored in this folder use the
API offered by package `extension_discovery` to get this information.
If this package doesn't work for your use-case, then don't try to read the
contents of this folder. It may change, and will not remain stable.
Use package `extension_discovery`
---------------------------------
If you want to access information from this folder.
Feel free to delete this folder
-------------------------------
Files in this folder act as a cache, and the cache is discarded if the files
are older than the modification time of `.dart_tool/package_config.json`.
Hence, it should never be necessary to clear this cache manually, if you find a
need to do please file a bug.

View File

@@ -0,0 +1 @@
{"version":2,"entries":[{"package":"arbiter","rootUri":"../","packageUri":"lib/"}]}

View File

@@ -0,0 +1,172 @@
{
"configVersion": 2,
"packages": [
{
"name": "async",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/async-2.13.0",
"packageUri": "lib/",
"languageVersion": "3.4"
},
{
"name": "boolean_selector",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/boolean_selector-2.1.2",
"packageUri": "lib/",
"languageVersion": "3.1"
},
{
"name": "characters",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/characters-1.4.0",
"packageUri": "lib/",
"languageVersion": "3.4"
},
{
"name": "clock",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/clock-1.1.2",
"packageUri": "lib/",
"languageVersion": "3.4"
},
{
"name": "collection",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/collection-1.19.1",
"packageUri": "lib/",
"languageVersion": "3.4"
},
{
"name": "fake_async",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/fake_async-1.3.3",
"packageUri": "lib/",
"languageVersion": "3.3"
},
{
"name": "flutter",
"rootUri": "file:///Users/kaska/.local/share/mise/installs/flutter/3.38.9-stable/packages/flutter",
"packageUri": "lib/",
"languageVersion": "3.8"
},
{
"name": "flutter_lints",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/flutter_lints-6.0.0",
"packageUri": "lib/",
"languageVersion": "3.8"
},
{
"name": "flutter_test",
"rootUri": "file:///Users/kaska/.local/share/mise/installs/flutter/3.38.9-stable/packages/flutter_test",
"packageUri": "lib/",
"languageVersion": "3.8"
},
{
"name": "leak_tracker",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/leak_tracker-11.0.2",
"packageUri": "lib/",
"languageVersion": "3.2"
},
{
"name": "leak_tracker_flutter_testing",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/leak_tracker_flutter_testing-3.0.10",
"packageUri": "lib/",
"languageVersion": "3.2"
},
{
"name": "leak_tracker_testing",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/leak_tracker_testing-3.0.2",
"packageUri": "lib/",
"languageVersion": "3.2"
},
{
"name": "lints",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/lints-6.1.0",
"packageUri": "lib/",
"languageVersion": "3.8"
},
{
"name": "matcher",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/matcher-0.12.17",
"packageUri": "lib/",
"languageVersion": "3.4"
},
{
"name": "material_color_utilities",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/material_color_utilities-0.11.1",
"packageUri": "lib/",
"languageVersion": "2.17"
},
{
"name": "meta",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/meta-1.17.0",
"packageUri": "lib/",
"languageVersion": "3.5"
},
{
"name": "path",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/path-1.9.1",
"packageUri": "lib/",
"languageVersion": "3.4"
},
{
"name": "sky_engine",
"rootUri": "file:///Users/kaska/.local/share/mise/installs/flutter/3.38.9-stable/bin/cache/pkg/sky_engine",
"packageUri": "lib/",
"languageVersion": "3.8"
},
{
"name": "source_span",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/source_span-1.10.2",
"packageUri": "lib/",
"languageVersion": "3.1"
},
{
"name": "stack_trace",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/stack_trace-1.12.1",
"packageUri": "lib/",
"languageVersion": "3.4"
},
{
"name": "stream_channel",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/stream_channel-2.1.4",
"packageUri": "lib/",
"languageVersion": "3.3"
},
{
"name": "string_scanner",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/string_scanner-1.4.1",
"packageUri": "lib/",
"languageVersion": "3.1"
},
{
"name": "term_glyph",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/term_glyph-1.2.2",
"packageUri": "lib/",
"languageVersion": "3.1"
},
{
"name": "test_api",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/test_api-0.7.7",
"packageUri": "lib/",
"languageVersion": "3.5"
},
{
"name": "vector_math",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/vector_math-2.2.0",
"packageUri": "lib/",
"languageVersion": "3.1"
},
{
"name": "vm_service",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/vm_service-15.0.2",
"packageUri": "lib/",
"languageVersion": "3.5"
},
{
"name": "arbiter",
"rootUri": "../",
"packageUri": "lib/",
"languageVersion": "3.10"
}
],
"generator": "pub",
"generatorVersion": "3.10.8",
"flutterRoot": "file:///Users/kaska/.local/share/mise/installs/flutter/3.38.9-stable",
"flutterVersion": "3.38.9",
"pubCache": "file:///Users/kaska/.pub-cache"
}

View File

@@ -0,0 +1,224 @@
{
"roots": [
"arbiter"
],
"packages": [
{
"name": "arbiter",
"version": "0.1.0",
"dependencies": [
"flutter"
],
"devDependencies": [
"flutter_lints",
"flutter_test"
]
},
{
"name": "flutter_lints",
"version": "6.0.0",
"dependencies": [
"lints"
]
},
{
"name": "flutter_test",
"version": "0.0.0",
"dependencies": [
"clock",
"collection",
"fake_async",
"flutter",
"leak_tracker_flutter_testing",
"matcher",
"meta",
"path",
"stack_trace",
"stream_channel",
"test_api",
"vector_math"
]
},
{
"name": "flutter",
"version": "0.0.0",
"dependencies": [
"characters",
"collection",
"material_color_utilities",
"meta",
"sky_engine",
"vector_math"
]
},
{
"name": "lints",
"version": "6.1.0",
"dependencies": []
},
{
"name": "stream_channel",
"version": "2.1.4",
"dependencies": [
"async"
]
},
{
"name": "meta",
"version": "1.17.0",
"dependencies": []
},
{
"name": "collection",
"version": "1.19.1",
"dependencies": []
},
{
"name": "leak_tracker_flutter_testing",
"version": "3.0.10",
"dependencies": [
"flutter",
"leak_tracker",
"leak_tracker_testing",
"matcher",
"meta"
]
},
{
"name": "vector_math",
"version": "2.2.0",
"dependencies": []
},
{
"name": "stack_trace",
"version": "1.12.1",
"dependencies": [
"path"
]
},
{
"name": "clock",
"version": "1.1.2",
"dependencies": []
},
{
"name": "fake_async",
"version": "1.3.3",
"dependencies": [
"clock",
"collection"
]
},
{
"name": "path",
"version": "1.9.1",
"dependencies": []
},
{
"name": "matcher",
"version": "0.12.17",
"dependencies": [
"async",
"meta",
"stack_trace",
"term_glyph",
"test_api"
]
},
{
"name": "test_api",
"version": "0.7.7",
"dependencies": [
"async",
"boolean_selector",
"collection",
"meta",
"source_span",
"stack_trace",
"stream_channel",
"string_scanner",
"term_glyph"
]
},
{
"name": "sky_engine",
"version": "0.0.0",
"dependencies": []
},
{
"name": "material_color_utilities",
"version": "0.11.1",
"dependencies": [
"collection"
]
},
{
"name": "characters",
"version": "1.4.0",
"dependencies": []
},
{
"name": "async",
"version": "2.13.0",
"dependencies": [
"collection",
"meta"
]
},
{
"name": "leak_tracker_testing",
"version": "3.0.2",
"dependencies": [
"leak_tracker",
"matcher",
"meta"
]
},
{
"name": "leak_tracker",
"version": "11.0.2",
"dependencies": [
"clock",
"collection",
"meta",
"path",
"vm_service"
]
},
{
"name": "term_glyph",
"version": "1.2.2",
"dependencies": []
},
{
"name": "string_scanner",
"version": "1.4.1",
"dependencies": [
"source_span"
]
},
{
"name": "source_span",
"version": "1.10.2",
"dependencies": [
"collection",
"path",
"term_glyph"
]
},
{
"name": "boolean_selector",
"version": "2.1.2",
"dependencies": [
"source_span",
"string_scanner"
]
},
{
"name": "vm_service",
"version": "15.0.2",
"dependencies": []
}
],
"configVersion": 1
}

View File

@@ -0,0 +1 @@
3.38.9

View File

@@ -0,0 +1,11 @@
// This is a generated file; do not edit or check into version control.
FLUTTER_ROOT=/Users/kaska/.local/share/mise/installs/flutter/3.38.9-stable
FLUTTER_APPLICATION_PATH=/Users/kaska/Documents/Projects/Major/arbiter/useragent
COCOAPODS_PARALLEL_CODE_SIGN=true
FLUTTER_BUILD_DIR=build
FLUTTER_BUILD_NAME=0.1.0
FLUTTER_BUILD_NUMBER=0.1.0
DART_OBFUSCATION=false
TRACK_WIDGET_CREATION=true
TREE_SHAKE_ICONS=false
PACKAGE_CONFIG=.dart_tool/package_config.json

View File

@@ -0,0 +1,12 @@
#!/bin/sh
# This is a generated file; do not edit or check into version control.
export "FLUTTER_ROOT=/Users/kaska/.local/share/mise/installs/flutter/3.38.9-stable"
export "FLUTTER_APPLICATION_PATH=/Users/kaska/Documents/Projects/Major/arbiter/useragent"
export "COCOAPODS_PARALLEL_CODE_SIGN=true"
export "FLUTTER_BUILD_DIR=build"
export "FLUTTER_BUILD_NAME=0.1.0"
export "FLUTTER_BUILD_NUMBER=0.1.0"
export "DART_OBFUSCATION=false"
export "TRACK_WIDGET_CREATION=true"
export "TREE_SHAKE_ICONS=false"
export "PACKAGE_CONFIG=.dart_tool/package_config.json"