Compare commits
41 Commits
push-lspny
...
f6116b03e7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f6116b03e7 | ||
|
|
1c7fe46595 | ||
|
|
23f60cc98e | ||
|
|
0c85e1f167 | ||
|
|
4ac904c8f8 | ||
|
|
b1188d602f | ||
| 632b431c66 | |||
|
|
f709650bb1 | ||
|
|
004b14a168 | ||
|
|
4169b2ba42 | ||
|
|
3cc63474a8 | ||
|
|
3478204b9f | ||
|
|
61c65ddbcb | ||
|
|
3401205cbd | ||
|
|
1799aef6f8 | ||
|
|
fe8c5e1bd2 | ||
|
|
cbbe1f8881 | ||
|
|
7438d62695 | ||
|
|
4236f2c36d | ||
|
|
76ff535619 | ||
|
|
b3566c8af6 | ||
|
|
bdb9f01757 | ||
|
|
0805e7a846 | ||
|
|
eb9cbc88e9 | ||
|
|
dd716da4cd | ||
|
|
1545db7428 | ||
|
|
20ac84b60c | ||
|
|
8f6dda871b | ||
|
|
47108ed8ad | ||
|
|
359df73c2e | ||
|
|
ce03b7e15d | ||
|
|
e4038d9188 | ||
|
|
c82339d764 | ||
|
|
c5b51f4b70 | ||
|
|
6b8f8c9ff7 | ||
|
|
8263bc6b6f | ||
|
|
a6c849f268 | ||
|
|
d8d65da0b4 | ||
|
|
abdf4e3893 | ||
|
|
4bac70a6e9 | ||
|
|
54a41743be |
@@ -27,82 +27,6 @@ This document covers concrete technology choices and dependencies. For the archi
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## EVM Policy Engine
|
|
||||||
|
|
||||||
### Overview
|
|
||||||
|
|
||||||
The EVM engine classifies incoming transactions, enforces grant constraints, and records executions. It is the sole path through which a wallet key is used for signing.
|
|
||||||
|
|
||||||
The central abstraction is the `Policy` trait. Each implementation handles one semantic transaction category and owns its own database tables for grant storage and transaction logging.
|
|
||||||
|
|
||||||
### Transaction Evaluation Flow
|
|
||||||
|
|
||||||
`Engine::evaluate_transaction` runs the following steps in order:
|
|
||||||
|
|
||||||
1. **Classify** — Each registered policy's `analyze(context)` inspects the transaction fields (`chain`, `to`, `value`, `calldata`). The first one returning `Some(meaning)` wins. If none match, the transaction is rejected as `UnsupportedTransactionType`.
|
|
||||||
2. **Find grant** — `Policy::try_find_grant` queries for a non-revoked grant covering this wallet, client, chain, and target address.
|
|
||||||
3. **Check shared constraints** — `check_shared_constraints` runs in the engine before any policy-specific logic. It enforces the validity window, gas fee caps, and transaction count rate limit (see below).
|
|
||||||
4. **Evaluate** — `Policy::evaluate` checks the decoded meaning against the grant's policy-specific constraints and returns any violations.
|
|
||||||
5. **Record** — If `RunKind::Execution` and there are no violations, the engine writes to `evm_transaction_log` and calls `Policy::record_transaction` for any policy-specific logging (e.g., token transfer volume).
|
|
||||||
|
|
||||||
### Policy Trait
|
|
||||||
|
|
||||||
| Method | Purpose |
|
|
||||||
|---|---|
|
|
||||||
| `analyze` | Pure — classifies a transaction into a typed `Meaning`, or `None` if this policy doesn't apply |
|
|
||||||
| `evaluate` | Checks the `Meaning` against a `Grant`; returns a list of `EvalViolation`s |
|
|
||||||
| `create_grant` | Inserts policy-specific rows; returns the specific grant ID |
|
|
||||||
| `try_find_grant` | Finds a matching non-revoked grant for the given `EvalContext` |
|
|
||||||
| `find_all_grants` | Returns all non-revoked grants (used for listing) |
|
|
||||||
| `record_transaction` | Persists policy-specific data after execution |
|
|
||||||
|
|
||||||
`analyze` and `evaluate` are intentionally separate: classification is pure and cheap, while evaluation may involve DB queries (e.g., fetching past transfer volume).
|
|
||||||
|
|
||||||
### Registered Policies
|
|
||||||
|
|
||||||
**EtherTransfer** — plain ETH transfers (empty calldata)
|
|
||||||
|
|
||||||
- Grant requires: allowlist of recipient addresses + one volumetric rate limit (max ETH over a time window)
|
|
||||||
- Violations: recipient not in allowlist, cumulative ETH volume exceeded
|
|
||||||
|
|
||||||
**TokenTransfer** — ERC-20 `transfer(address,uint256)` calls
|
|
||||||
|
|
||||||
- Recognised by ABI-decoding the `transfer(address,uint256)` selector against a static registry of known token contracts (`arbiter_tokens_registry`)
|
|
||||||
- Grant requires: token contract address, optional recipient restriction, zero or more volumetric rate limits
|
|
||||||
- Violations: recipient mismatch, any volumetric limit exceeded
|
|
||||||
|
|
||||||
### Grant Model
|
|
||||||
|
|
||||||
Every grant has two layers:
|
|
||||||
|
|
||||||
- **Shared (`evm_basic_grant`)** — wallet, chain, validity period, gas fee caps, transaction count rate limit. One row per grant regardless of type.
|
|
||||||
- **Specific** — policy-owned tables (`evm_ether_transfer_grant`, `evm_token_transfer_grant`, etc.) holding type-specific configuration.
|
|
||||||
|
|
||||||
`find_all_grants` uses a `#[diesel::auto_type]` base join between the specific and shared tables, then batch-loads related rows (targets, volume limits) in two additional queries to avoid N+1.
|
|
||||||
|
|
||||||
The engine exposes `list_all_grants` which collects across all policy types into `Vec<Grant<SpecificGrant>>` via a blanket `From<Grant<S>> for Grant<SpecificGrant>` conversion.
|
|
||||||
|
|
||||||
### Shared Constraints (enforced by the engine)
|
|
||||||
|
|
||||||
These are checked centrally in `check_shared_constraints` before policy evaluation:
|
|
||||||
|
|
||||||
| Constraint | Fields | Behaviour |
|
|
||||||
|---|---|---|
|
|
||||||
| Validity window | `valid_from`, `valid_until` | Emits `InvalidTime` if current time is outside the range |
|
|
||||||
| Gas fee cap | `max_gas_fee_per_gas`, `max_priority_fee_per_gas` | Emits `GasLimitExceeded` if either cap is breached |
|
|
||||||
| Tx count rate limit | `rate_limit` (`count` + `window`) | Counts rows in `evm_transaction_log` within the window; emits `RateLimitExceeded` if at or above the limit |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Known Limitations
|
|
||||||
|
|
||||||
- **Only EIP-1559 transactions are supported.** Legacy and EIP-2930 types are rejected outright.
|
|
||||||
- **No opaque-calldata (unknown contract) grant type.** The architecture describes a category for unrecognised contracts, but no policy implements it yet. Any transaction that is not a plain ETH transfer or a known ERC-20 transfer is unconditionally rejected.
|
|
||||||
- **Token registry is static.** Tokens are recognised only if they appear in the hard-coded `arbiter_tokens_registry` crate. There is no mechanism to register additional contracts at runtime.
|
|
||||||
- **Nonce management is not implemented.** The architecture lists nonce deduplication as a core responsibility, but no nonce tracking or enforcement exists yet.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Memory Protection
|
## Memory Protection
|
||||||
|
|
||||||
The unsealed root key must be held in a hardened memory cell resistant to dumps, page swaps, and hibernation.
|
The unsealed root key must be held in a hardened memory cell resistant to dumps, page swaps, and hibernation.
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
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.
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"version":2,"entries":[{"package":"app","rootUri":"../","packageUri":"lib/"}]}
|
|
||||||
@@ -1,178 +0,0 @@
|
|||||||
{
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
@@ -1,230 +0,0 @@
|
|||||||
{
|
|
||||||
"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 +0,0 @@
|
|||||||
3.38.9
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
// 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
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
#!/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"
|
|
||||||
@@ -2,8 +2,6 @@ syntax = "proto3";
|
|||||||
|
|
||||||
package arbiter.client;
|
package arbiter.client;
|
||||||
|
|
||||||
import "evm.proto";
|
|
||||||
|
|
||||||
message AuthChallengeRequest {
|
message AuthChallengeRequest {
|
||||||
bytes pubkey = 1;
|
bytes pubkey = 1;
|
||||||
}
|
}
|
||||||
@@ -23,8 +21,6 @@ message ClientRequest {
|
|||||||
oneof payload {
|
oneof payload {
|
||||||
AuthChallengeRequest auth_challenge_request = 1;
|
AuthChallengeRequest auth_challenge_request = 1;
|
||||||
AuthChallengeSolution auth_challenge_solution = 2;
|
AuthChallengeSolution auth_challenge_solution = 2;
|
||||||
arbiter.evm.EvmSignTransactionRequest evm_sign_transaction = 3;
|
|
||||||
arbiter.evm.EvmAnalyzeTransactionRequest evm_analyze_transaction = 4;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,7 +28,5 @@ message ClientResponse {
|
|||||||
oneof payload {
|
oneof payload {
|
||||||
AuthChallenge auth_challenge = 1;
|
AuthChallenge auth_challenge = 1;
|
||||||
AuthOk auth_ok = 2;
|
AuthOk auth_ok = 2;
|
||||||
arbiter.evm.EvmSignTransactionResponse evm_sign_transaction = 3;
|
|
||||||
arbiter.evm.EvmAnalyzeTransactionResponse evm_analyze_transaction = 4;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,6 @@ syntax = "proto3";
|
|||||||
|
|
||||||
package arbiter.evm;
|
package arbiter.evm;
|
||||||
|
|
||||||
import "google/protobuf/empty.proto";
|
|
||||||
import "google/protobuf/timestamp.proto";
|
|
||||||
|
|
||||||
enum EvmError {
|
enum EvmError {
|
||||||
EVM_ERROR_UNSPECIFIED = 0;
|
EVM_ERROR_UNSPECIFIED = 0;
|
||||||
EVM_ERROR_VAULT_SEALED = 1;
|
EVM_ERROR_VAULT_SEALED = 1;
|
||||||
@@ -32,185 +29,3 @@ message WalletListResponse {
|
|||||||
EvmError error = 2;
|
EvmError error = 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Grant types ---
|
|
||||||
|
|
||||||
message TransactionRateLimit {
|
|
||||||
uint32 count = 1;
|
|
||||||
int64 window_secs = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message VolumeRateLimit {
|
|
||||||
bytes max_volume = 1; // U256 as big-endian bytes
|
|
||||||
int64 window_secs = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message SharedSettings {
|
|
||||||
int32 wallet_id = 1;
|
|
||||||
uint64 chain_id = 2;
|
|
||||||
optional google.protobuf.Timestamp valid_from = 3;
|
|
||||||
optional google.protobuf.Timestamp valid_until = 4;
|
|
||||||
optional bytes max_gas_fee_per_gas = 5; // U256 as big-endian bytes
|
|
||||||
optional bytes max_priority_fee_per_gas = 6; // U256 as big-endian bytes
|
|
||||||
optional TransactionRateLimit rate_limit = 7;
|
|
||||||
}
|
|
||||||
|
|
||||||
message EtherTransferSettings {
|
|
||||||
repeated bytes targets = 1; // list of 20-byte Ethereum addresses
|
|
||||||
VolumeRateLimit limit = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message TokenTransferSettings {
|
|
||||||
bytes token_contract = 1; // 20-byte Ethereum address
|
|
||||||
optional bytes target = 2; // 20-byte Ethereum address; absent means any recipient allowed
|
|
||||||
repeated VolumeRateLimit volume_limits = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message SpecificGrant {
|
|
||||||
oneof grant {
|
|
||||||
EtherTransferSettings ether_transfer = 1;
|
|
||||||
TokenTransferSettings token_transfer = 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
message EtherTransferMeaning {
|
|
||||||
bytes to = 1; // 20-byte Ethereum address
|
|
||||||
bytes value = 2; // U256 as big-endian bytes
|
|
||||||
}
|
|
||||||
|
|
||||||
message TokenInfo {
|
|
||||||
string symbol = 1;
|
|
||||||
bytes address = 2; // 20-byte Ethereum address
|
|
||||||
uint64 chain_id = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mirror of token_transfers::Meaning
|
|
||||||
message TokenTransferMeaning {
|
|
||||||
TokenInfo token = 1;
|
|
||||||
bytes to = 2; // 20-byte Ethereum address
|
|
||||||
bytes value = 3; // U256 as big-endian bytes
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mirror of policies::SpecificMeaning
|
|
||||||
message SpecificMeaning {
|
|
||||||
oneof meaning {
|
|
||||||
EtherTransferMeaning ether_transfer = 1;
|
|
||||||
TokenTransferMeaning token_transfer = 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Eval error types ---
|
|
||||||
message GasLimitExceededViolation {
|
|
||||||
optional bytes max_gas_fee_per_gas = 1; // U256 as big-endian bytes
|
|
||||||
optional bytes max_priority_fee_per_gas = 2; // U256 as big-endian bytes
|
|
||||||
}
|
|
||||||
|
|
||||||
message EvalViolation {
|
|
||||||
oneof kind {
|
|
||||||
bytes invalid_target = 1; // 20-byte Ethereum address
|
|
||||||
GasLimitExceededViolation gas_limit_exceeded = 2;
|
|
||||||
google.protobuf.Empty rate_limit_exceeded = 3;
|
|
||||||
google.protobuf.Empty volumetric_limit_exceeded = 4;
|
|
||||||
google.protobuf.Empty invalid_time = 5;
|
|
||||||
google.protobuf.Empty invalid_transaction_type = 6;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Transaction was classified but no grant covers it
|
|
||||||
message NoMatchingGrantError {
|
|
||||||
SpecificMeaning meaning = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Transaction was classified and a grant was found, but constraints were violated
|
|
||||||
message PolicyViolationsError {
|
|
||||||
SpecificMeaning meaning = 1;
|
|
||||||
repeated EvalViolation violations = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
// top-level error returned when transaction evaluation fails
|
|
||||||
message TransactionEvalError {
|
|
||||||
oneof kind {
|
|
||||||
google.protobuf.Empty contract_creation_not_supported = 1;
|
|
||||||
google.protobuf.Empty unsupported_transaction_type = 2;
|
|
||||||
NoMatchingGrantError no_matching_grant = 3;
|
|
||||||
PolicyViolationsError policy_violations = 4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- UserAgent grant management ---
|
|
||||||
message EvmGrantCreateRequest {
|
|
||||||
int32 client_id = 1;
|
|
||||||
SharedSettings shared = 2;
|
|
||||||
SpecificGrant specific = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message EvmGrantCreateResponse {
|
|
||||||
oneof result {
|
|
||||||
int32 grant_id = 1;
|
|
||||||
EvmError error = 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
message EvmGrantDeleteRequest {
|
|
||||||
int32 grant_id = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message EvmGrantDeleteResponse {
|
|
||||||
oneof result {
|
|
||||||
google.protobuf.Empty ok = 1;
|
|
||||||
EvmError error = 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Basic grant info returned in grant listings
|
|
||||||
message GrantEntry {
|
|
||||||
int32 id = 1;
|
|
||||||
int32 client_id = 2;
|
|
||||||
SharedSettings shared = 3;
|
|
||||||
SpecificGrant specific = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
message EvmGrantListRequest {
|
|
||||||
optional int32 wallet_id = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message EvmGrantListResponse {
|
|
||||||
oneof result {
|
|
||||||
EvmGrantList grants = 1;
|
|
||||||
EvmError error = 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
message EvmGrantList {
|
|
||||||
repeated GrantEntry grants = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Client transaction operations ---
|
|
||||||
|
|
||||||
message EvmSignTransactionRequest {
|
|
||||||
bytes wallet_address = 1; // 20-byte Ethereum address
|
|
||||||
bytes rlp_transaction = 2; // RLP-encoded EIP-1559 transaction (unsigned)
|
|
||||||
}
|
|
||||||
|
|
||||||
// oneof because signing and evaluation happen atomically — a signing failure
|
|
||||||
// is always either an eval error or an internal error, never a partial success
|
|
||||||
message EvmSignTransactionResponse {
|
|
||||||
oneof result {
|
|
||||||
bytes signature = 1; // 65-byte signature: r[32] || s[32] || v[1]
|
|
||||||
TransactionEvalError eval_error = 2;
|
|
||||||
EvmError error = 3;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
message EvmAnalyzeTransactionRequest {
|
|
||||||
bytes wallet_address = 1; // 20-byte Ethereum address
|
|
||||||
bytes rlp_transaction = 2; // RLP-encoded EIP-1559 transaction
|
|
||||||
}
|
|
||||||
|
|
||||||
message EvmAnalyzeTransactionResponse {
|
|
||||||
oneof result {
|
|
||||||
SpecificMeaning meaning = 1;
|
|
||||||
TransactionEvalError eval_error = 2;
|
|
||||||
EvmError error = 3;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -58,9 +58,6 @@ message UserAgentRequest {
|
|||||||
google.protobuf.Empty query_vault_state = 5;
|
google.protobuf.Empty query_vault_state = 5;
|
||||||
google.protobuf.Empty evm_wallet_create = 6;
|
google.protobuf.Empty evm_wallet_create = 6;
|
||||||
google.protobuf.Empty evm_wallet_list = 7;
|
google.protobuf.Empty evm_wallet_list = 7;
|
||||||
arbiter.evm.EvmGrantCreateRequest evm_grant_create = 8;
|
|
||||||
arbiter.evm.EvmGrantDeleteRequest evm_grant_delete = 9;
|
|
||||||
arbiter.evm.EvmGrantListRequest evm_grant_list = 10;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
message UserAgentResponse {
|
message UserAgentResponse {
|
||||||
@@ -72,8 +69,5 @@ message UserAgentResponse {
|
|||||||
VaultState vault_state = 5;
|
VaultState vault_state = 5;
|
||||||
arbiter.evm.WalletCreateResponse evm_wallet_create = 6;
|
arbiter.evm.WalletCreateResponse evm_wallet_create = 6;
|
||||||
arbiter.evm.WalletListResponse evm_wallet_list = 7;
|
arbiter.evm.WalletListResponse evm_wallet_list = 7;
|
||||||
arbiter.evm.EvmGrantCreateResponse evm_grant_create = 8;
|
|
||||||
arbiter.evm.EvmGrantDeleteResponse evm_grant_delete = 9;
|
|
||||||
arbiter.evm.EvmGrantListResponse evm_grant_list = 10;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
74
server/Cargo.lock
generated
74
server/Cargo.lock
generated
@@ -67,9 +67,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "alloy-chains"
|
name = "alloy-chains"
|
||||||
version = "0.2.31"
|
version = "0.2.30"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6d9d22005bf31b018f31ef9ecadb5d2c39cf4f6acc8db0456f72c815f3d7f757"
|
checksum = "90f374d3c6d729268bbe2d0e0ff992bb97898b2df756691a62ee1d5f0506bc39"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"alloy-primitives",
|
"alloy-primitives",
|
||||||
"num_enum",
|
"num_enum",
|
||||||
@@ -634,12 +634,13 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "alloy-trie"
|
name = "alloy-trie"
|
||||||
version = "0.9.5"
|
version = "0.9.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3f14b5d9b2c2173980202c6ff470d96e7c5e202c65a9f67884ad565226df7fbb"
|
checksum = "4d7fd448ab0a017de542de1dcca7a58e7019fe0e7a34ed3f9543ebddf6aceffa"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"alloy-primitives",
|
"alloy-primitives",
|
||||||
"alloy-rlp",
|
"alloy-rlp",
|
||||||
|
"arrayvec",
|
||||||
"derive_more",
|
"derive_more",
|
||||||
"nybbles",
|
"nybbles",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -686,11 +687,9 @@ 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",
|
||||||
@@ -978,6 +977,9 @@ 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"
|
||||||
@@ -2450,9 +2452,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hybrid-array"
|
name = "hybrid-array"
|
||||||
version = "0.4.8"
|
version = "0.4.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8655f91cd07f2b9d0c24137bd650fe69617773435ee5ec83022377777ce65ef1"
|
checksum = "e1b229d73f5803b562cc26e4da0396c8610a4ee209f4fac8fa4f8d709166dc45"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"typenum",
|
"typenum",
|
||||||
]
|
]
|
||||||
@@ -2885,9 +2887,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.183"
|
version = "0.2.182"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d"
|
checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libm"
|
name = "libm"
|
||||||
@@ -3432,9 +3434,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro-crate"
|
name = "proc-macro-crate"
|
||||||
version = "3.5.0"
|
version = "3.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f"
|
checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"toml_edit",
|
"toml_edit",
|
||||||
]
|
]
|
||||||
@@ -3563,7 +3565,6 @@ 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",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -3615,9 +3616,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quinn-proto"
|
name = "quinn-proto"
|
||||||
version = "0.11.14"
|
version = "0.11.13"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098"
|
checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"getrandom 0.3.4",
|
"getrandom 0.3.4",
|
||||||
@@ -4474,12 +4475,12 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "socket2"
|
name = "socket2"
|
||||||
version = "0.6.3"
|
version = "0.6.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e"
|
checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.60.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -4638,9 +4639,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tempfile"
|
name = "tempfile"
|
||||||
version = "3.27.0"
|
version = "3.26.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
|
checksum = "82a72c767771b47409d2345987fda8628641887d5466101319899796367354a0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"fastrand",
|
"fastrand",
|
||||||
"getrandom 0.4.2",
|
"getrandom 0.4.2",
|
||||||
@@ -4856,7 +4857,7 @@ checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_core",
|
"serde_core",
|
||||||
"serde_spanned",
|
"serde_spanned",
|
||||||
"toml_datetime 0.7.5+spec-1.1.0",
|
"toml_datetime",
|
||||||
"toml_parser",
|
"toml_parser",
|
||||||
"winnow",
|
"winnow",
|
||||||
]
|
]
|
||||||
@@ -4870,23 +4871,14 @@ dependencies = [
|
|||||||
"serde_core",
|
"serde_core",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "toml_datetime"
|
|
||||||
version = "1.0.0+spec-1.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "32c2555c699578a4f59f0cc68e5116c8d7cabbd45e1409b989d4be085b53f13e"
|
|
||||||
dependencies = [
|
|
||||||
"serde_core",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_edit"
|
name = "toml_edit"
|
||||||
version = "0.25.4+spec-1.1.0"
|
version = "0.23.10+spec-1.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7193cbd0ce53dc966037f54351dbbcf0d5a642c7f0038c382ef9e677ce8c13f2"
|
checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap 2.13.0",
|
"indexmap 2.13.0",
|
||||||
"toml_datetime 1.0.0+spec-1.1.0",
|
"toml_datetime",
|
||||||
"toml_parser",
|
"toml_parser",
|
||||||
"winnow",
|
"winnow",
|
||||||
]
|
]
|
||||||
@@ -5202,9 +5194,9 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uuid"
|
name = "uuid"
|
||||||
version = "1.22.0"
|
version = "1.21.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37"
|
checksum = "b672338555252d43fd2240c714dc444b8c6fb0a5c5335e65a07bba7742735ddb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
@@ -5654,9 +5646,9 @@ checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winnow"
|
name = "winnow"
|
||||||
version = "0.7.15"
|
version = "0.7.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945"
|
checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
@@ -5828,18 +5820,18 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerocopy"
|
name = "zerocopy"
|
||||||
version = "0.8.42"
|
version = "0.8.40"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f2578b716f8a7a858b7f02d5bd870c14bf4ddbbcf3a4c05414ba6503640505e3"
|
checksum = "a789c6e490b576db9f7e6b6d661bcc9799f7c0ac8352f56ea20193b2681532e5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"zerocopy-derive",
|
"zerocopy-derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerocopy-derive"
|
name = "zerocopy-derive"
|
||||||
version = "0.8.42"
|
version = "0.8.40"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7e6cc098ea4d3bd6246687de65af3f920c430e236bee1e3bf2e441463f08a02f"
|
checksum = "f65c489a7071a749c849713807783f70672b28094011623e200cb86dcb835953"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ 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"
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ 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
|
||||||
@@ -18,7 +17,6 @@ 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
|
||||||
|
|
||||||
|
|||||||
@@ -51,7 +51,6 @@ fn parse_auth_event(payload: ClientRequestPayload) -> Result<AuthEvents, Error>
|
|||||||
solution: signature,
|
solution: signature,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
_ => Err(Error::UnexpectedMessagePayload) ,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ pub mod types {
|
|||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, FromSqlRow, AsExpression)]
|
#[derive(Debug, FromSqlRow, AsExpression)]
|
||||||
#[diesel(sql_type = Integer)]
|
#[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::Long) = bytes.value_type() else {
|
let Some(SqliteType::Integer) = 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_long();
|
let unix_timestamp = bytes.read_integer();
|
||||||
let datetime = DateTime::from_timestamp(unix_timestamp, 0)
|
let datetime = DateTime::from_timestamp(unix_timestamp as i64, 0)
|
||||||
.ok_or("Timestamp is out of bounds")?;
|
.ok_or("Timestamp is out of bounds")?;
|
||||||
|
|
||||||
Ok(SqliteTimestamp(datetime))
|
Ok(SqliteTimestamp(datetime))
|
||||||
|
|||||||
@@ -1,24 +1,23 @@
|
|||||||
pub mod abi;
|
pub mod abi;
|
||||||
pub mod safe_signer;
|
pub mod safe_signer;
|
||||||
|
|
||||||
use alloy::{
|
use alloy::{consensus::TxEip1559, primitives::TxKind, signers::Signature};
|
||||||
consensus::TxEip1559,
|
|
||||||
primitives::{TxKind, U256},
|
|
||||||
};
|
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use diesel::{ExpressionMethods as _, QueryDsl, QueryResult, insert_into, sqlite::Sqlite};
|
use diesel::{QueryResult, insert_into};
|
||||||
use diesel_async::{AsyncConnection, RunQueryDsl};
|
use diesel_async::{AsyncConnection, RunQueryDsl};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
db::{
|
db::{
|
||||||
self,
|
self,
|
||||||
models::{EvmBasicGrant, NewEvmBasicGrant, NewEvmTransactionLog, SqliteTimestamp},
|
models::{
|
||||||
|
EvmBasicGrant, EvmTransactionLog, NewEvmBasicGrant, NewEvmTransactionLog,
|
||||||
|
SqliteTimestamp,
|
||||||
|
},
|
||||||
schema::{self, evm_transaction_log},
|
schema::{self, evm_transaction_log},
|
||||||
},
|
},
|
||||||
evm::policies::{
|
evm::policies::{
|
||||||
DatabaseID, EvalContext, EvalViolation, FullGrant, Grant, Policy, SharedGrantSettings,
|
EvalContext, EvalViolation, FullGrant, Policy, SpecificMeaning,
|
||||||
SpecificGrant, SpecificMeaning, ether_transfer::EtherTransfer,
|
ether_transfer::EtherTransfer, token_transfers::TokenTransfer,
|
||||||
token_transfers::TokenTransfer,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -55,6 +54,7 @@ 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")]
|
||||||
@@ -87,17 +87,6 @@ pub enum CreationError {
|
|||||||
Database(#[from] diesel::result::Error),
|
Database(#[from] diesel::result::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error, miette::Diagnostic)]
|
|
||||||
pub enum ListGrantsError {
|
|
||||||
#[error("Database connection pool error")]
|
|
||||||
#[diagnostic(code(arbiter_server::evm::list_grants_error::pool))]
|
|
||||||
Pool(#[from] db::PoolError),
|
|
||||||
|
|
||||||
#[error("Database returned error")]
|
|
||||||
#[diagnostic(code(arbiter_server::evm::list_grants_error::database))]
|
|
||||||
Database(#[from] diesel::result::Error),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Controls whether a transaction should be executed or only validated
|
/// Controls whether a transaction should be executed or only validated
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum RunKind {
|
pub enum RunKind {
|
||||||
@@ -107,53 +96,6 @@ pub enum RunKind {
|
|||||||
CheckOnly,
|
CheckOnly,
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn check_shared_constraints(
|
|
||||||
context: &EvalContext,
|
|
||||||
shared: &SharedGrantSettings,
|
|
||||||
shared_grant_id: DatabaseID,
|
|
||||||
conn: &mut impl AsyncConnection<Backend = Sqlite>,
|
|
||||||
) -> QueryResult<Vec<EvalViolation>> {
|
|
||||||
let mut violations = Vec::new();
|
|
||||||
let now = Utc::now();
|
|
||||||
|
|
||||||
// Validity window
|
|
||||||
if shared.valid_from.map_or(false, |t| now < t) || shared.valid_until.map_or(false, |t| now > t)
|
|
||||||
{
|
|
||||||
violations.push(EvalViolation::InvalidTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Gas fee caps
|
|
||||||
let fee_exceeded = shared
|
|
||||||
.max_gas_fee_per_gas
|
|
||||||
.map_or(false, |cap| U256::from(context.max_fee_per_gas) > cap);
|
|
||||||
let priority_exceeded = shared.max_priority_fee_per_gas.map_or(false, |cap| {
|
|
||||||
U256::from(context.max_priority_fee_per_gas) > cap
|
|
||||||
});
|
|
||||||
if fee_exceeded || priority_exceeded {
|
|
||||||
violations.push(EvalViolation::GasLimitExceeded {
|
|
||||||
max_gas_fee_per_gas: shared.max_gas_fee_per_gas,
|
|
||||||
max_priority_fee_per_gas: shared.max_priority_fee_per_gas,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Transaction count rate limit
|
|
||||||
if let Some(rate_limit) = &shared.rate_limit {
|
|
||||||
let window_start = SqliteTimestamp(now - rate_limit.window);
|
|
||||||
let count: i64 = evm_transaction_log::table
|
|
||||||
.filter(evm_transaction_log::grant_id.eq(shared_grant_id))
|
|
||||||
.filter(evm_transaction_log::signed_at.ge(window_start))
|
|
||||||
.count()
|
|
||||||
.get_result(conn)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
if count >= rate_limit.count as i64 {
|
|
||||||
violations.push(EvalViolation::RateLimitExceeded);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(violations)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Supporting only EIP-1559 transactions for now, but we can easily extend this to support legacy transactions if needed
|
// Supporting only EIP-1559 transactions for now, but we can easily extend this to support legacy transactions if needed
|
||||||
pub struct Engine {
|
pub struct Engine {
|
||||||
db: db::DatabasePool,
|
db: db::DatabasePool,
|
||||||
@@ -172,11 +114,7 @@ impl Engine {
|
|||||||
.await?
|
.await?
|
||||||
.ok_or(PolicyError::NoMatchingGrant)?;
|
.ok_or(PolicyError::NoMatchingGrant)?;
|
||||||
|
|
||||||
let mut violations =
|
let violations = P::evaluate(&context, meaning, &grant, &mut conn).await?;
|
||||||
check_shared_constraints(&context, &grant.shared, grant.shared_grant_id, &mut conn)
|
|
||||||
.await?;
|
|
||||||
violations.extend(P::evaluate(&context, meaning, &grant, &mut conn).await?);
|
|
||||||
|
|
||||||
if !violations.is_empty() {
|
if !violations.is_empty() {
|
||||||
return Err(PolicyError::Violations(violations));
|
return Err(PolicyError::Violations(violations));
|
||||||
} else if run_kind == RunKind::Execution {
|
} else if run_kind == RunKind::Execution {
|
||||||
@@ -263,37 +201,6 @@ impl Engine {
|
|||||||
Ok(id)
|
Ok(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn list_all_grants(&self) -> Result<Vec<Grant<SpecificGrant>>, ListGrantsError> {
|
|
||||||
let mut conn = self.db.get().await?;
|
|
||||||
|
|
||||||
let mut grants: Vec<Grant<SpecificGrant>> = Vec::new();
|
|
||||||
|
|
||||||
grants.extend(
|
|
||||||
EtherTransfer::find_all_grants(&mut conn)
|
|
||||||
.await?
|
|
||||||
.into_iter()
|
|
||||||
.map(|g| Grant {
|
|
||||||
id: g.id,
|
|
||||||
shared_grant_id: g.shared_grant_id,
|
|
||||||
shared: g.shared,
|
|
||||||
settings: SpecificGrant::EtherTransfer(g.settings),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
grants.extend(
|
|
||||||
TokenTransfer::find_all_grants(&mut conn)
|
|
||||||
.await?
|
|
||||||
.into_iter()
|
|
||||||
.map(|g| Grant {
|
|
||||||
id: g.id,
|
|
||||||
shared_grant_id: g.shared_grant_id,
|
|
||||||
shared: g.shared,
|
|
||||||
settings: SpecificGrant::TokenTransfer(g.settings),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(grants)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn evaluate_transaction(
|
pub async fn evaluate_transaction(
|
||||||
&self,
|
&self,
|
||||||
wallet_id: i32,
|
wallet_id: i32,
|
||||||
@@ -308,11 +215,9 @@ impl Engine {
|
|||||||
wallet_id,
|
wallet_id,
|
||||||
client_id,
|
client_id,
|
||||||
chain: transaction.chain_id,
|
chain: transaction.chain_id,
|
||||||
to,
|
to: to,
|
||||||
value: transaction.value,
|
value: transaction.value,
|
||||||
calldata: transaction.input.clone(),
|
calldata: transaction.input.clone(),
|
||||||
max_fee_per_gas: transaction.max_fee_per_gas,
|
|
||||||
max_priority_fee_per_gas: transaction.max_priority_fee_per_gas,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(meaning) = EtherTransfer::analyze(&context) {
|
if let Some(meaning) = EtherTransfer::analyze(&context) {
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ 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,
|
||||||
@@ -28,10 +27,6 @@ pub struct EvalContext {
|
|||||||
pub to: Address,
|
pub to: Address,
|
||||||
pub value: U256,
|
pub value: U256,
|
||||||
pub calldata: Bytes,
|
pub calldata: Bytes,
|
||||||
|
|
||||||
// Gas pricing (EIP-1559)
|
|
||||||
pub max_fee_per_gas: u128,
|
|
||||||
pub max_priority_fee_per_gas: u128,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Error, Diagnostic)]
|
#[derive(Debug, Error, Diagnostic)]
|
||||||
@@ -73,7 +68,6 @@ 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>;
|
||||||
@@ -103,11 +97,6 @@ pub trait Policy: Sized {
|
|||||||
conn: &mut impl AsyncConnection<Backend = Sqlite>,
|
conn: &mut impl AsyncConnection<Backend = Sqlite>,
|
||||||
) -> impl Future<Output = QueryResult<Option<Grant<Self::Settings>>>> + Send;
|
) -> impl Future<Output = QueryResult<Option<Grant<Self::Settings>>>> + Send;
|
||||||
|
|
||||||
// Return all non-revoked grants, eagerly loading policy-specific settings
|
|
||||||
fn find_all_grants(
|
|
||||||
conn: &mut impl AsyncConnection<Backend = Sqlite>,
|
|
||||||
) -> impl Future<Output = QueryResult<Vec<Grant<Self::Settings>>>> + Send;
|
|
||||||
|
|
||||||
// Records, updates or deletes rate limits
|
// Records, updates or deletes rate limits
|
||||||
// In other words, records grant-specific things after transaction is executed
|
// In other words, records grant-specific things after transaction is executed
|
||||||
fn record_transaction(
|
fn record_transaction(
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
use std::collections::HashMap;
|
|
||||||
use std::fmt::Display;
|
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::{auto_type, insert_into};
|
use diesel::dsl::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};
|
||||||
@@ -12,7 +11,7 @@ use crate::db::models::{
|
|||||||
EvmBasicGrant, EvmEtherTransferGrant, EvmEtherTransferGrantTarget, EvmEtherTransferLimit,
|
EvmBasicGrant, EvmEtherTransferGrant, EvmEtherTransferGrantTarget, EvmEtherTransferLimit,
|
||||||
NewEvmEtherTransferLimit, SqliteTimestamp,
|
NewEvmEtherTransferLimit, SqliteTimestamp,
|
||||||
};
|
};
|
||||||
use crate::db::schema::{evm_basic_grant, evm_ether_transfer_limit, evm_transaction_log};
|
use crate::db::schema::{evm_ether_transfer_limit, evm_transaction_log};
|
||||||
use crate::evm::policies::{
|
use crate::evm::policies::{
|
||||||
Grant, SharedGrantSettings, SpecificGrant, SpecificMeaning, VolumeRateLimit,
|
Grant, SharedGrantSettings, SpecificGrant, SpecificMeaning, VolumeRateLimit,
|
||||||
};
|
};
|
||||||
@@ -24,13 +23,6 @@ use crate::{
|
|||||||
evm::{policies::Policy, utils},
|
evm::{policies::Policy, utils},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[auto_type]
|
|
||||||
fn grant_join() -> _ {
|
|
||||||
evm_ether_transfer_grant::table.inner_join(
|
|
||||||
evm_basic_grant::table.on(evm_ether_transfer_grant::basic_grant_id.eq(evm_basic_grant::id)),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
use super::{DatabaseID, EvalContext, EvalViolation};
|
use super::{DatabaseID, EvalContext, EvalViolation};
|
||||||
|
|
||||||
// Plain ether transfer
|
// Plain ether transfer
|
||||||
@@ -191,21 +183,27 @@ impl Policy for EtherTransfer {
|
|||||||
context: &EvalContext,
|
context: &EvalContext,
|
||||||
conn: &mut impl AsyncConnection<Backend = Sqlite>,
|
conn: &mut impl AsyncConnection<Backend = Sqlite>,
|
||||||
) -> diesel::result::QueryResult<Option<Grant<Self::Settings>>> {
|
) -> diesel::result::QueryResult<Option<Grant<Self::Settings>>> {
|
||||||
|
use crate::db::schema::{
|
||||||
|
evm_basic_grant, evm_ether_transfer_grant, evm_ether_transfer_grant_target,
|
||||||
|
};
|
||||||
|
|
||||||
let target_bytes = context.to.to_vec();
|
let target_bytes = context.to.to_vec();
|
||||||
|
|
||||||
// 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)> = evm_ether_transfer_grant::table
|
let grant: Option<(EvmBasicGrant, EvmEtherTransferGrant)> = evm_ether_transfer_grant::table
|
||||||
.inner_join(evm_basic_grant::table)
|
.inner_join(
|
||||||
.inner_join(evm_ether_transfer_grant_target::table)
|
evm_basic_grant::table
|
||||||
.filter(
|
.on(evm_ether_transfer_grant::basic_grant_id.eq(evm_basic_grant::id)),
|
||||||
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)),
|
|
||||||
)
|
)
|
||||||
|
.inner_join(
|
||||||
|
evm_ether_transfer_grant_target::table
|
||||||
|
.on(evm_ether_transfer_grant::id.eq(evm_ether_transfer_grant_target::grant_id)),
|
||||||
|
)
|
||||||
|
.filter(evm_basic_grant::wallet_id.eq(context.wallet_id))
|
||||||
|
.filter(evm_basic_grant::client_id.eq(context.client_id))
|
||||||
|
.filter(evm_ether_transfer_grant_target::address.eq(&target_bytes))
|
||||||
.select((
|
.select((
|
||||||
EvmBasicGrant::as_select(),
|
EvmBasicGrant::as_select(),
|
||||||
EvmEtherTransferGrant::as_select(),
|
EvmEtherTransferGrant::as_select(),
|
||||||
@@ -268,85 +266,4 @@ impl Policy for EtherTransfer {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn find_all_grants(
|
|
||||||
conn: &mut impl AsyncConnection<Backend = Sqlite>,
|
|
||||||
) -> QueryResult<Vec<Grant<Self::Settings>>> {
|
|
||||||
let grants: Vec<(EvmBasicGrant, EvmEtherTransferGrant)> = grant_join()
|
|
||||||
.filter(evm_basic_grant::revoked_at.is_null())
|
|
||||||
.select((
|
|
||||||
EvmBasicGrant::as_select(),
|
|
||||||
EvmEtherTransferGrant::as_select(),
|
|
||||||
))
|
|
||||||
.load(conn)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
if grants.is_empty() {
|
|
||||||
return Ok(Vec::new());
|
|
||||||
}
|
|
||||||
|
|
||||||
let grant_ids: Vec<i32> = grants.iter().map(|(_, g)| g.id).collect();
|
|
||||||
let limit_ids: Vec<i32> = grants.iter().map(|(_, g)| g.limit_id).collect();
|
|
||||||
|
|
||||||
let all_targets: Vec<EvmEtherTransferGrantTarget> = evm_ether_transfer_grant_target::table
|
|
||||||
.filter(evm_ether_transfer_grant_target::grant_id.eq_any(&grant_ids))
|
|
||||||
.select(EvmEtherTransferGrantTarget::as_select())
|
|
||||||
.load(conn)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let all_limits: Vec<EvmEtherTransferLimit> = evm_ether_transfer_limit::table
|
|
||||||
.filter(evm_ether_transfer_limit::id.eq_any(&limit_ids))
|
|
||||||
.select(EvmEtherTransferLimit::as_select())
|
|
||||||
.load(conn)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let mut targets_by_grant: HashMap<i32, Vec<EvmEtherTransferGrantTarget>> = HashMap::new();
|
|
||||||
for target in all_targets {
|
|
||||||
targets_by_grant
|
|
||||||
.entry(target.grant_id)
|
|
||||||
.or_default()
|
|
||||||
.push(target);
|
|
||||||
}
|
|
||||||
|
|
||||||
let limits_by_id: HashMap<i32, EvmEtherTransferLimit> =
|
|
||||||
all_limits.into_iter().map(|l| (l.id, l)).collect();
|
|
||||||
|
|
||||||
grants
|
|
||||||
.into_iter()
|
|
||||||
.map(|(basic, specific)| {
|
|
||||||
let targets: Vec<Address> = targets_by_grant
|
|
||||||
.get(&specific.id)
|
|
||||||
.map(|v| v.as_slice())
|
|
||||||
.unwrap_or_default()
|
|
||||||
.iter()
|
|
||||||
.filter_map(|t| {
|
|
||||||
let arr: [u8; 20] = t.address.clone().try_into().ok()?;
|
|
||||||
Some(Address::from(arr))
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let limit = limits_by_id
|
|
||||||
.get(&specific.limit_id)
|
|
||||||
.ok_or(diesel::result::Error::NotFound)?;
|
|
||||||
|
|
||||||
Ok(Grant {
|
|
||||||
id: specific.id,
|
|
||||||
shared_grant_id: specific.basic_grant_id,
|
|
||||||
shared: SharedGrantSettings::try_from_model(basic)?,
|
|
||||||
settings: Settings {
|
|
||||||
target: targets,
|
|
||||||
limit: VolumeRateLimit {
|
|
||||||
max_volume: utils::try_bytes_to_u256(&limit.max_volume).map_err(
|
|
||||||
|e| diesel::result::Error::DeserializationError(Box::new(e)),
|
|
||||||
)?,
|
|
||||||
window: Duration::seconds(limit.window_secs as i64),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests;
|
|
||||||
@@ -1,387 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
@@ -1,12 +1,10 @@
|
|||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use alloy::{
|
use alloy::{
|
||||||
primitives::{Address, U256},
|
primitives::{Address, U256},
|
||||||
sol_types::SolCall,
|
sol_types::SolCall,
|
||||||
};
|
};
|
||||||
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::{auto_type, insert_into};
|
use diesel::dsl::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};
|
||||||
@@ -16,26 +14,16 @@ use crate::db::models::{
|
|||||||
NewEvmTokenTransferLog, NewEvmTokenTransferVolumeLimit, SqliteTimestamp,
|
NewEvmTokenTransferLog, NewEvmTokenTransferVolumeLimit, SqliteTimestamp,
|
||||||
};
|
};
|
||||||
use crate::db::schema::{
|
use crate::db::schema::{
|
||||||
evm_basic_grant, evm_token_transfer_grant, evm_token_transfer_log,
|
evm_token_transfer_grant, evm_token_transfer_log, evm_token_transfer_volume_limit,
|
||||||
evm_token_transfer_volume_limit,
|
|
||||||
};
|
};
|
||||||
use crate::evm::{
|
use crate::evm::{
|
||||||
abi::IERC20::transferCall,
|
abi::IERC20::transferCall,
|
||||||
policies::{
|
policies::{Grant, Policy, SharedGrantSettings, SpecificGrant, SpecificMeaning, VolumeRateLimit},
|
||||||
Grant, Policy, SharedGrantSettings, SpecificGrant, SpecificMeaning, VolumeRateLimit,
|
|
||||||
},
|
|
||||||
utils,
|
utils,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{DatabaseID, EvalContext, EvalViolation};
|
use super::{DatabaseID, EvalContext, EvalViolation};
|
||||||
|
|
||||||
#[auto_type]
|
|
||||||
fn grant_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)),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
pub struct Meaning {
|
pub struct Meaning {
|
||||||
token: &'static TokenInfo,
|
token: &'static TokenInfo,
|
||||||
@@ -204,10 +192,15 @@ impl Policy for TokenTransfer {
|
|||||||
context: &EvalContext,
|
context: &EvalContext,
|
||||||
conn: &mut impl AsyncConnection<Backend = Sqlite>,
|
conn: &mut impl AsyncConnection<Backend = Sqlite>,
|
||||||
) -> QueryResult<Option<Grant<Self::Settings>>> {
|
) -> QueryResult<Option<Grant<Self::Settings>>> {
|
||||||
|
use crate::db::schema::{evm_basic_grant, evm_token_transfer_grant};
|
||||||
|
|
||||||
let token_contract_bytes = context.to.to_vec();
|
let token_contract_bytes = context.to.to_vec();
|
||||||
|
|
||||||
let grant: Option<(EvmBasicGrant, EvmTokenTransferGrant)> = grant_join()
|
let grant: Option<(EvmBasicGrant, EvmTokenTransferGrant)> = evm_token_transfer_grant::table
|
||||||
.filter(evm_basic_grant::revoked_at.is_null())
|
.inner_join(
|
||||||
|
evm_basic_grant::table
|
||||||
|
.on(evm_token_transfer_grant::basic_grant_id.eq(evm_basic_grant::id)),
|
||||||
|
)
|
||||||
.filter(evm_basic_grant::wallet_id.eq(context.wallet_id))
|
.filter(evm_basic_grant::wallet_id.eq(context.wallet_id))
|
||||||
.filter(evm_basic_grant::client_id.eq(context.client_id))
|
.filter(evm_basic_grant::client_id.eq(context.client_id))
|
||||||
.filter(evm_token_transfer_grant::token_contract.eq(&token_contract_bytes))
|
.filter(evm_token_transfer_grant::token_contract.eq(&token_contract_bytes))
|
||||||
@@ -295,91 +288,4 @@ impl Policy for TokenTransfer {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn find_all_grants(
|
|
||||||
conn: &mut impl AsyncConnection<Backend = Sqlite>,
|
|
||||||
) -> QueryResult<Vec<Grant<Self::Settings>>> {
|
|
||||||
let grants: Vec<(EvmBasicGrant, EvmTokenTransferGrant)> = grant_join()
|
|
||||||
.filter(evm_basic_grant::revoked_at.is_null())
|
|
||||||
.select((
|
|
||||||
EvmBasicGrant::as_select(),
|
|
||||||
EvmTokenTransferGrant::as_select(),
|
|
||||||
))
|
|
||||||
.load(conn)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
if grants.is_empty() {
|
|
||||||
return Ok(Vec::new());
|
|
||||||
}
|
|
||||||
|
|
||||||
let grant_ids: Vec<i32> = grants.iter().map(|(_, g)| g.id).collect();
|
|
||||||
|
|
||||||
let all_volume_limits: Vec<EvmTokenTransferVolumeLimit> =
|
|
||||||
evm_token_transfer_volume_limit::table
|
|
||||||
.filter(evm_token_transfer_volume_limit::grant_id.eq_any(&grant_ids))
|
|
||||||
.select(EvmTokenTransferVolumeLimit::as_select())
|
|
||||||
.load(conn)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let mut limits_by_grant: HashMap<i32, Vec<EvmTokenTransferVolumeLimit>> = HashMap::new();
|
|
||||||
for limit in all_volume_limits {
|
|
||||||
limits_by_grant
|
|
||||||
.entry(limit.grant_id)
|
|
||||||
.or_default()
|
|
||||||
.push(limit);
|
|
||||||
}
|
|
||||||
|
|
||||||
grants
|
|
||||||
.into_iter()
|
|
||||||
.map(|(basic, specific)| {
|
|
||||||
let volume_limits: Vec<VolumeRateLimit> = limits_by_grant
|
|
||||||
.get(&specific.id)
|
|
||||||
.map(|v| v.as_slice())
|
|
||||||
.unwrap_or_default()
|
|
||||||
.iter()
|
|
||||||
.map(|row| {
|
|
||||||
Ok(VolumeRateLimit {
|
|
||||||
max_volume: utils::try_bytes_to_u256(&row.max_volume).map_err(|e| {
|
|
||||||
diesel::result::Error::DeserializationError(Box::new(e))
|
|
||||||
})?,
|
|
||||||
window: Duration::seconds(row.window_secs as i64),
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.collect::<QueryResult<Vec<_>>>()?;
|
|
||||||
|
|
||||||
let token_contract: [u8; 20] =
|
|
||||||
specific.token_contract.clone().try_into().map_err(|_| {
|
|
||||||
diesel::result::Error::DeserializationError(
|
|
||||||
"Invalid token contract address length".into(),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let target: Option<Address> = match &specific.receiver {
|
|
||||||
None => None,
|
|
||||||
Some(bytes) => {
|
|
||||||
let arr: [u8; 20] = bytes.clone().try_into().map_err(|_| {
|
|
||||||
diesel::result::Error::DeserializationError(
|
|
||||||
"Invalid receiver address length".into(),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
Some(Address::from(arr))
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Grant {
|
|
||||||
id: specific.id,
|
|
||||||
shared_grant_id: specific.basic_grant_id,
|
|
||||||
shared: SharedGrantSettings::try_from_model(basic)?,
|
|
||||||
settings: Settings {
|
|
||||||
token_contract: Address::from(token_contract),
|
|
||||||
target,
|
|
||||||
volume_limits,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests;
|
|
||||||
@@ -1,397 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
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.
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"version":2,"entries":[{"package":"arbiter","rootUri":"../","packageUri":"lib/"}]}
|
|
||||||
@@ -1,172 +0,0 @@
|
|||||||
{
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
@@ -1,224 +0,0 @@
|
|||||||
{
|
|
||||||
"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
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
3.38.9
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
// 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
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
#!/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"
|
|
||||||
Reference in New Issue
Block a user