@wlfi-agent/cli 1.4.17 → 1.4.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.lock +5 -0
- package/README.md +61 -28
- package/crates/vault-cli-admin/src/io_utils.rs +149 -1
- package/crates/vault-cli-admin/src/main.rs +639 -16
- package/crates/vault-cli-admin/src/shared_config.rs +18 -18
- package/crates/vault-cli-admin/src/tui/token_rpc.rs +190 -3
- package/crates/vault-cli-admin/src/tui/utils.rs +59 -0
- package/crates/vault-cli-admin/src/tui.rs +1205 -120
- package/crates/vault-cli-agent/Cargo.toml +1 -0
- package/crates/vault-cli-agent/src/io_utils.rs +163 -2
- package/crates/vault-cli-agent/src/main.rs +648 -32
- package/crates/vault-cli-daemon/Cargo.toml +4 -0
- package/crates/vault-cli-daemon/src/main.rs +617 -67
- package/crates/vault-cli-daemon/src/relay_sync.rs +776 -4
- package/crates/vault-cli-daemon/tests/system_keychain_helper_acl.rs +5 -0
- package/crates/vault-daemon/src/daemon_parts/api_impl_and_utils.rs +32 -1
- package/crates/vault-daemon/src/persistence.rs +637 -100
- package/crates/vault-daemon/src/tests.rs +1013 -3
- package/crates/vault-daemon/src/tests_parts/part2.rs +99 -0
- package/crates/vault-daemon/src/tests_parts/part4.rs +11 -7
- package/crates/vault-domain/src/nonce.rs +4 -0
- package/crates/vault-domain/src/tests.rs +616 -0
- package/crates/vault-policy/src/engine.rs +55 -32
- package/crates/vault-policy/src/tests.rs +195 -0
- package/crates/vault-sdk-agent/src/lib.rs +415 -22
- package/crates/vault-signer/Cargo.toml +3 -0
- package/crates/vault-signer/src/lib.rs +266 -40
- package/crates/vault-transport-unix/src/lib.rs +653 -5
- package/crates/vault-transport-xpc/src/tests.rs +531 -3
- package/crates/vault-transport-xpc/tests/e2e_flow.rs +3 -0
- package/dist/cli.cjs +663 -190
- package/dist/cli.cjs.map +1 -1
- package/package.json +5 -2
- package/packages/cache/.turbo/turbo-build.log +20 -20
- package/packages/cache/coverage/clover.xml +529 -394
- package/packages/cache/coverage/coverage-final.json +2 -2
- package/packages/cache/coverage/index.html +21 -21
- package/packages/cache/coverage/src/client/index.html +1 -1
- package/packages/cache/coverage/src/client/index.ts.html +1 -1
- package/packages/cache/coverage/src/errors/index.html +1 -1
- package/packages/cache/coverage/src/errors/index.ts.html +12 -12
- package/packages/cache/coverage/src/index.html +1 -1
- package/packages/cache/coverage/src/index.ts.html +1 -1
- package/packages/cache/coverage/src/service/index.html +21 -21
- package/packages/cache/coverage/src/service/index.ts.html +769 -313
- package/packages/cache/dist/{chunk-QNK6GOTI.js → chunk-KC53LH5Z.js} +35 -2
- package/packages/cache/dist/chunk-KC53LH5Z.js.map +1 -0
- package/packages/cache/dist/{chunk-QF4XKEIA.cjs → chunk-UVU7VFE3.cjs} +35 -2
- package/packages/cache/dist/chunk-UVU7VFE3.cjs.map +1 -0
- package/packages/cache/dist/index.cjs +2 -2
- package/packages/cache/dist/index.js +1 -1
- package/packages/cache/dist/service/index.cjs +2 -2
- package/packages/cache/dist/service/index.js +1 -1
- package/packages/cache/node_modules/.bin/tsc +2 -2
- package/packages/cache/node_modules/.bin/tsserver +2 -2
- package/packages/cache/node_modules/.bin/tsup +2 -2
- package/packages/cache/node_modules/.bin/tsup-node +2 -2
- package/packages/cache/node_modules/.bin/vitest +4 -4
- package/packages/cache/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
- package/packages/cache/src/service/index.test.ts +165 -19
- package/packages/cache/src/service/index.ts +38 -1
- package/packages/config/.turbo/turbo-build.log +4 -4
- package/packages/config/dist/index.cjs +0 -17
- package/packages/config/dist/index.cjs.map +1 -1
- package/packages/config/src/index.ts +0 -17
- package/packages/rpc/.turbo/turbo-build.log +11 -11
- package/packages/rpc/dist/index.cjs +0 -17
- package/packages/rpc/dist/index.cjs.map +1 -1
- package/packages/rpc/src/index.js +1 -0
- package/packages/ui/node_modules/.bin/tsc +2 -2
- package/packages/ui/node_modules/.bin/tsserver +2 -2
- package/packages/ui/node_modules/.bin/tsup +2 -2
- package/packages/ui/node_modules/.bin/tsup-node +2 -2
- package/scripts/install-cli-launcher.mjs +37 -0
- package/scripts/install-rust-binaries.mjs +47 -0
- package/scripts/run-tests-isolated.mjs +210 -0
- package/src/cli.ts +310 -50
- package/src/lib/admin-reset.ts +15 -30
- package/src/lib/admin-setup.ts +246 -55
- package/src/lib/agent-auth-migrate.ts +5 -1
- package/src/lib/asset-broadcast.ts +15 -4
- package/src/lib/config-amounts.ts +6 -4
- package/src/lib/hidden-tty-prompt.js +1 -0
- package/src/lib/hidden-tty-prompt.ts +105 -0
- package/src/lib/keychain.ts +1 -0
- package/src/lib/local-admin-access.ts +4 -29
- package/src/lib/rust.ts +129 -33
- package/src/lib/signed-tx.ts +1 -0
- package/src/lib/sudo.ts +15 -5
- package/src/lib/wallet-profile.ts +3 -0
- package/src/lib/wallet-setup.ts +52 -0
- package/packages/cache/dist/chunk-QF4XKEIA.cjs.map +0 -1
- package/packages/cache/dist/chunk-QNK6GOTI.js.map +0 -1
|
@@ -4,6 +4,54 @@ use vault_domain::{AgentAction, PolicyAttachment, PolicyType, SpendEvent, Spendi
|
|
|
4
4
|
|
|
5
5
|
use crate::{PolicyDecision, PolicyError, PolicyEvaluation, PolicyExplanation};
|
|
6
6
|
|
|
7
|
+
pub(crate) fn increment_counter_or_mark_overflow(counter: &mut u128) -> bool {
|
|
8
|
+
match counter.checked_add(1) {
|
|
9
|
+
Some(next) => {
|
|
10
|
+
*counter = next;
|
|
11
|
+
false
|
|
12
|
+
}
|
|
13
|
+
None => {
|
|
14
|
+
*counter = u128::MAX;
|
|
15
|
+
true
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
pub(crate) fn enforce_priority_fee_limit(
|
|
21
|
+
policy: &SpendingPolicy,
|
|
22
|
+
action: &AgentAction,
|
|
23
|
+
) -> Result<(), PolicyError> {
|
|
24
|
+
if let Some(requested_max_priority_fee_per_gas_wei) = action.max_priority_fee_per_gas_wei() {
|
|
25
|
+
if requested_max_priority_fee_per_gas_wei > policy.max_amount_wei {
|
|
26
|
+
return Err(PolicyError::PriorityFeePerGasLimitExceeded {
|
|
27
|
+
policy_id: policy.id,
|
|
28
|
+
max_priority_fee_per_gas_wei: policy.max_amount_wei,
|
|
29
|
+
requested_max_priority_fee_per_gas_wei,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
Ok(())
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
pub(crate) fn enforce_calldata_bytes_limit(
|
|
38
|
+
policy: &SpendingPolicy,
|
|
39
|
+
action: &AgentAction,
|
|
40
|
+
) -> Result<(), PolicyError> {
|
|
41
|
+
if let Some(requested_calldata_bytes) = action.calldata_len_bytes() {
|
|
42
|
+
let requested_calldata_bytes = requested_calldata_bytes as u128;
|
|
43
|
+
if requested_calldata_bytes > policy.max_amount_wei {
|
|
44
|
+
return Err(PolicyError::CalldataBytesLimitExceeded {
|
|
45
|
+
policy_id: policy.id,
|
|
46
|
+
max_calldata_bytes: policy.max_amount_wei,
|
|
47
|
+
requested_calldata_bytes,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
Ok(())
|
|
53
|
+
}
|
|
54
|
+
|
|
7
55
|
/// Stateless policy evaluator.
|
|
8
56
|
#[derive(Debug, Default)]
|
|
9
57
|
pub struct PolicyEngine;
|
|
@@ -140,29 +188,10 @@ impl PolicyEngine {
|
|
|
140
188
|
}
|
|
141
189
|
}
|
|
142
190
|
PolicyType::PerTxMaxPriorityFeePerGas => {
|
|
143
|
-
|
|
144
|
-
action.max_priority_fee_per_gas_wei()
|
|
145
|
-
{
|
|
146
|
-
if requested_max_priority_fee_per_gas_wei > policy.max_amount_wei {
|
|
147
|
-
return Err(PolicyError::PriorityFeePerGasLimitExceeded {
|
|
148
|
-
policy_id: policy.id,
|
|
149
|
-
max_priority_fee_per_gas_wei: policy.max_amount_wei,
|
|
150
|
-
requested_max_priority_fee_per_gas_wei,
|
|
151
|
-
});
|
|
152
|
-
}
|
|
153
|
-
}
|
|
191
|
+
enforce_priority_fee_limit(policy, action)?;
|
|
154
192
|
}
|
|
155
193
|
PolicyType::PerTxMaxCalldataBytes => {
|
|
156
|
-
|
|
157
|
-
let requested_calldata_bytes = requested_calldata_bytes as u128;
|
|
158
|
-
if requested_calldata_bytes > policy.max_amount_wei {
|
|
159
|
-
return Err(PolicyError::CalldataBytesLimitExceeded {
|
|
160
|
-
policy_id: policy.id,
|
|
161
|
-
max_calldata_bytes: policy.max_amount_wei,
|
|
162
|
-
requested_calldata_bytes,
|
|
163
|
-
});
|
|
164
|
-
}
|
|
165
|
-
}
|
|
194
|
+
enforce_calldata_bytes_limit(policy, action)?;
|
|
166
195
|
}
|
|
167
196
|
PolicyType::DailyMaxSpending => self.enforce_window_limit(
|
|
168
197
|
policy,
|
|
@@ -223,23 +252,17 @@ impl PolicyEngine {
|
|
|
223
252
|
window_start: OffsetDateTime,
|
|
224
253
|
) -> Result<(), PolicyError> {
|
|
225
254
|
let mut used_tx_count = 0u128;
|
|
226
|
-
|
|
255
|
+
let overflowed = spend_history.iter().filter(|event| {
|
|
227
256
|
event.agent_key_id == agent_key_id
|
|
228
257
|
&& event.at >= window_start
|
|
229
258
|
&& policy.assets.allows(&event.asset)
|
|
230
259
|
&& policy.recipients.allows(&event.recipient)
|
|
231
260
|
&& policy.networks.allows(&event.chain_id)
|
|
232
|
-
})
|
|
233
|
-
|
|
234
|
-
Some(next) => used_tx_count = next,
|
|
235
|
-
None => {
|
|
236
|
-
used_tx_count = u128::MAX;
|
|
237
|
-
break;
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
}
|
|
261
|
+
})
|
|
262
|
+
.any(|_| increment_counter_or_mark_overflow(&mut used_tx_count));
|
|
241
263
|
|
|
242
|
-
let exceeds_window =
|
|
264
|
+
let exceeds_window = overflowed
|
|
265
|
+
|| used_tx_count
|
|
243
266
|
.checked_add(1)
|
|
244
267
|
.is_none_or(|total| total > policy.max_amount_wei);
|
|
245
268
|
if exceeds_window {
|
|
@@ -8,6 +8,10 @@ use vault_domain::{
|
|
|
8
8
|
SpendEvent, SpendingPolicy,
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
+
use crate::engine::{
|
|
12
|
+
enforce_calldata_bytes_limit, enforce_priority_fee_limit, increment_counter_or_mark_overflow,
|
|
13
|
+
};
|
|
14
|
+
|
|
11
15
|
use super::{PolicyEngine, PolicyError};
|
|
12
16
|
|
|
13
17
|
fn addr(x: &str) -> EvmAddress {
|
|
@@ -768,6 +772,197 @@ fn fee_policy_is_not_applicable_when_action_lacks_metadata() {
|
|
|
768
772
|
assert!(result.is_ok());
|
|
769
773
|
}
|
|
770
774
|
|
|
775
|
+
#[test]
|
|
776
|
+
fn explain_reports_no_attached_policies_when_attachment_filters_everything() {
|
|
777
|
+
let engine = PolicyEngine;
|
|
778
|
+
let policy = policy_all_per_tx(100);
|
|
779
|
+
let attachment =
|
|
780
|
+
PolicyAttachment::policy_set(BTreeSet::from([Uuid::new_v4()])).expect("attachment");
|
|
781
|
+
let action = AgentAction::TransferNative {
|
|
782
|
+
chain_id: 1,
|
|
783
|
+
to: addr("0x2000000000000000000000000000000000000000"),
|
|
784
|
+
amount_wei: 1,
|
|
785
|
+
};
|
|
786
|
+
|
|
787
|
+
let explanation = engine.explain(
|
|
788
|
+
&[policy],
|
|
789
|
+
&attachment,
|
|
790
|
+
&action,
|
|
791
|
+
&[],
|
|
792
|
+
Uuid::new_v4(),
|
|
793
|
+
OffsetDateTime::now_utc(),
|
|
794
|
+
);
|
|
795
|
+
|
|
796
|
+
assert!(matches!(explanation.decision, super::PolicyDecision::Deny(PolicyError::NoAttachedPolicies)));
|
|
797
|
+
assert!(explanation.applicable_policy_ids.is_empty());
|
|
798
|
+
assert!(explanation.evaluated_policy_ids.is_empty());
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
#[test]
|
|
802
|
+
fn fee_and_calldata_limits_allow_broadcasts_within_cap() {
|
|
803
|
+
let engine = PolicyEngine;
|
|
804
|
+
let fee_policy = SpendingPolicy::new(
|
|
805
|
+
0,
|
|
806
|
+
PolicyType::PerTxMaxPriorityFeePerGas,
|
|
807
|
+
1_000_000_000,
|
|
808
|
+
EntityScope::All,
|
|
809
|
+
EntityScope::All,
|
|
810
|
+
EntityScope::All,
|
|
811
|
+
)
|
|
812
|
+
.expect("policy");
|
|
813
|
+
let calldata_policy = SpendingPolicy::new(
|
|
814
|
+
1,
|
|
815
|
+
PolicyType::PerTxMaxCalldataBytes,
|
|
816
|
+
4,
|
|
817
|
+
EntityScope::All,
|
|
818
|
+
EntityScope::All,
|
|
819
|
+
EntityScope::All,
|
|
820
|
+
)
|
|
821
|
+
.expect("policy");
|
|
822
|
+
let action = broadcast_action_with_fees(2_000_000_000, 1_000_000_000, "0xdeadbeef");
|
|
823
|
+
|
|
824
|
+
let result = engine.evaluate(
|
|
825
|
+
&[fee_policy, calldata_policy],
|
|
826
|
+
&PolicyAttachment::AllPolicies,
|
|
827
|
+
&action,
|
|
828
|
+
&[],
|
|
829
|
+
Uuid::new_v4(),
|
|
830
|
+
OffsetDateTime::now_utc(),
|
|
831
|
+
);
|
|
832
|
+
|
|
833
|
+
assert!(result.is_ok());
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
#[test]
|
|
837
|
+
fn weekly_limit_and_manual_approval_paths_are_enforced() {
|
|
838
|
+
let engine = PolicyEngine;
|
|
839
|
+
let now = OffsetDateTime::now_utc();
|
|
840
|
+
let agent_key_id = Uuid::new_v4();
|
|
841
|
+
let token = addr("0x3000000000000000000000000000000000000000");
|
|
842
|
+
let recipient = addr("0x4000000000000000000000000000000000000000");
|
|
843
|
+
|
|
844
|
+
let weekly_policy = SpendingPolicy::new(
|
|
845
|
+
0,
|
|
846
|
+
PolicyType::WeeklyMaxSpending,
|
|
847
|
+
100,
|
|
848
|
+
EntityScope::All,
|
|
849
|
+
EntityScope::All,
|
|
850
|
+
EntityScope::All,
|
|
851
|
+
)
|
|
852
|
+
.expect("policy");
|
|
853
|
+
let weekly_history = vec![SpendEvent {
|
|
854
|
+
agent_key_id,
|
|
855
|
+
chain_id: 1,
|
|
856
|
+
asset: AssetId::Erc20(token.clone()),
|
|
857
|
+
recipient: recipient.clone(),
|
|
858
|
+
amount_wei: 80,
|
|
859
|
+
at: now - Duration::days(2),
|
|
860
|
+
}];
|
|
861
|
+
let weekly_action = AgentAction::Transfer {
|
|
862
|
+
chain_id: 1,
|
|
863
|
+
token: token.clone(),
|
|
864
|
+
to: recipient.clone(),
|
|
865
|
+
amount_wei: 30,
|
|
866
|
+
};
|
|
867
|
+
assert!(matches!(
|
|
868
|
+
engine.evaluate(
|
|
869
|
+
&[weekly_policy],
|
|
870
|
+
&PolicyAttachment::AllPolicies,
|
|
871
|
+
&weekly_action,
|
|
872
|
+
&weekly_history,
|
|
873
|
+
agent_key_id,
|
|
874
|
+
now
|
|
875
|
+
),
|
|
876
|
+
Err(PolicyError::WindowLimitExceeded { .. })
|
|
877
|
+
));
|
|
878
|
+
|
|
879
|
+
let manual_policy = SpendingPolicy::new_manual_approval(
|
|
880
|
+
0,
|
|
881
|
+
10,
|
|
882
|
+
100,
|
|
883
|
+
EntityScope::All,
|
|
884
|
+
EntityScope::All,
|
|
885
|
+
EntityScope::All,
|
|
886
|
+
)
|
|
887
|
+
.expect("policy");
|
|
888
|
+
let in_range = AgentAction::Transfer {
|
|
889
|
+
chain_id: 1,
|
|
890
|
+
token: token.clone(),
|
|
891
|
+
to: recipient.clone(),
|
|
892
|
+
amount_wei: 50,
|
|
893
|
+
};
|
|
894
|
+
assert!(matches!(
|
|
895
|
+
engine.evaluate(
|
|
896
|
+
&[manual_policy.clone()],
|
|
897
|
+
&PolicyAttachment::AllPolicies,
|
|
898
|
+
&in_range,
|
|
899
|
+
&[],
|
|
900
|
+
agent_key_id,
|
|
901
|
+
now
|
|
902
|
+
),
|
|
903
|
+
Err(PolicyError::ManualApprovalRequired { .. })
|
|
904
|
+
));
|
|
905
|
+
|
|
906
|
+
let below_range = AgentAction::Transfer {
|
|
907
|
+
chain_id: 1,
|
|
908
|
+
token,
|
|
909
|
+
to: recipient,
|
|
910
|
+
amount_wei: 5,
|
|
911
|
+
};
|
|
912
|
+
assert!(engine
|
|
913
|
+
.evaluate(
|
|
914
|
+
&[manual_policy],
|
|
915
|
+
&PolicyAttachment::AllPolicies,
|
|
916
|
+
&below_range,
|
|
917
|
+
&[],
|
|
918
|
+
agent_key_id,
|
|
919
|
+
now
|
|
920
|
+
)
|
|
921
|
+
.is_ok());
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
#[test]
|
|
925
|
+
fn tx_count_overflow_helper_saturates_at_max() {
|
|
926
|
+
let mut counter = u128::MAX;
|
|
927
|
+
|
|
928
|
+
assert!(increment_counter_or_mark_overflow(&mut counter));
|
|
929
|
+
assert_eq!(counter, u128::MAX);
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
#[test]
|
|
933
|
+
fn optional_limit_helpers_allow_missing_or_in_range_metadata() {
|
|
934
|
+
let policy = SpendingPolicy::new(
|
|
935
|
+
0,
|
|
936
|
+
PolicyType::PerTxMaxPriorityFeePerGas,
|
|
937
|
+
1_000_000_000,
|
|
938
|
+
EntityScope::All,
|
|
939
|
+
EntityScope::All,
|
|
940
|
+
EntityScope::All,
|
|
941
|
+
)
|
|
942
|
+
.expect("policy");
|
|
943
|
+
let plain_transfer = AgentAction::TransferNative {
|
|
944
|
+
chain_id: 1,
|
|
945
|
+
to: addr("0x2000000000000000000000000000000000000000"),
|
|
946
|
+
amount_wei: 1,
|
|
947
|
+
};
|
|
948
|
+
assert!(enforce_priority_fee_limit(&policy, &plain_transfer).is_ok());
|
|
949
|
+
|
|
950
|
+
let broadcast = broadcast_action_with_fees(2_000_000_000, 1_000_000_000, "0xdeadbeef");
|
|
951
|
+
assert!(enforce_priority_fee_limit(&policy, &broadcast).is_ok());
|
|
952
|
+
|
|
953
|
+
let calldata_policy = SpendingPolicy::new(
|
|
954
|
+
0,
|
|
955
|
+
PolicyType::PerTxMaxCalldataBytes,
|
|
956
|
+
4,
|
|
957
|
+
EntityScope::All,
|
|
958
|
+
EntityScope::All,
|
|
959
|
+
EntityScope::All,
|
|
960
|
+
)
|
|
961
|
+
.expect("policy");
|
|
962
|
+
assert!(enforce_calldata_bytes_limit(&calldata_policy, &plain_transfer).is_ok());
|
|
963
|
+
assert!(enforce_calldata_bytes_limit(&calldata_policy, &broadcast).is_ok());
|
|
964
|
+
}
|
|
965
|
+
|
|
771
966
|
#[test]
|
|
772
967
|
fn gas_policy_is_not_applicable_when_action_lacks_metadata() {
|
|
773
968
|
let engine = PolicyEngine;
|