@wlfi-agent/cli 1.4.17 → 1.4.19
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 +756 -194
- 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 +101 -33
- package/src/lib/admin-setup.ts +285 -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
|
@@ -2,6 +2,7 @@ use std::collections::BTreeSet;
|
|
|
2
2
|
|
|
3
3
|
use alloy_primitives::U256;
|
|
4
4
|
use alloy_sol_types::{sol, SolCall};
|
|
5
|
+
use serde::{Deserialize, Serialize};
|
|
5
6
|
use time::OffsetDateTime;
|
|
6
7
|
use uuid::Uuid;
|
|
7
8
|
|
|
@@ -53,6 +54,22 @@ fn manual_approval_capability_token_rejects_invalid_secret() {
|
|
|
53
54
|
assert!(matches!(err, DomainError::InvalidRelayCapabilitySecret));
|
|
54
55
|
}
|
|
55
56
|
|
|
57
|
+
#[test]
|
|
58
|
+
fn manual_approval_capability_helpers_reject_blank_and_short_inputs() {
|
|
59
|
+
let approval_request_id = Uuid::new_v4();
|
|
60
|
+
let err = manual_approval_capability_token(" ", approval_request_id)
|
|
61
|
+
.expect_err("must reject blank secret");
|
|
62
|
+
assert!(matches!(err, DomainError::InvalidRelayCapabilitySecret));
|
|
63
|
+
|
|
64
|
+
let err = manual_approval_capability_token("11", approval_request_id)
|
|
65
|
+
.expect_err("must reject short secret");
|
|
66
|
+
assert!(matches!(err, DomainError::InvalidRelayCapabilitySecret));
|
|
67
|
+
|
|
68
|
+
let err =
|
|
69
|
+
manual_approval_capability_hash(" ").expect_err("must reject blank capability token");
|
|
70
|
+
assert!(matches!(err, DomainError::InvalidRelayCapabilityToken));
|
|
71
|
+
}
|
|
72
|
+
|
|
56
73
|
#[test]
|
|
57
74
|
fn address_deserialize_rejects_invalid_values() {
|
|
58
75
|
let invalid =
|
|
@@ -88,6 +105,110 @@ fn policy_set_cannot_be_empty() {
|
|
|
88
105
|
assert!(matches!(result, Err(DomainError::EmptyPolicySet)));
|
|
89
106
|
}
|
|
90
107
|
|
|
108
|
+
#[test]
|
|
109
|
+
fn policy_attachment_applies_to_all_and_selected_ids() {
|
|
110
|
+
let first = Uuid::new_v4();
|
|
111
|
+
let second = Uuid::new_v4();
|
|
112
|
+
let attachment = PolicyAttachment::policy_set(BTreeSet::from([first])).expect("policy set");
|
|
113
|
+
|
|
114
|
+
assert!(PolicyAttachment::AllPolicies.applies_to(first));
|
|
115
|
+
assert!(attachment.applies_to(first));
|
|
116
|
+
assert!(!attachment.applies_to(second));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
#[test]
|
|
120
|
+
fn spending_policy_rejects_invalid_ranges_and_network_sets() {
|
|
121
|
+
let recipient: EvmAddress = "0x1111111111111111111111111111111111111111"
|
|
122
|
+
.parse()
|
|
123
|
+
.expect("recipient");
|
|
124
|
+
let asset = AssetId::Erc20(
|
|
125
|
+
"0x2222222222222222222222222222222222222222"
|
|
126
|
+
.parse()
|
|
127
|
+
.expect("token"),
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
let err = SpendingPolicy::new(
|
|
131
|
+
1,
|
|
132
|
+
PolicyType::PerTxMaxSpending,
|
|
133
|
+
0,
|
|
134
|
+
EntityScope::All,
|
|
135
|
+
EntityScope::All,
|
|
136
|
+
EntityScope::All,
|
|
137
|
+
)
|
|
138
|
+
.expect_err("zero max amount");
|
|
139
|
+
assert!(matches!(err, DomainError::InvalidAmount));
|
|
140
|
+
|
|
141
|
+
let err = SpendingPolicy::new_manual_approval(
|
|
142
|
+
1,
|
|
143
|
+
0,
|
|
144
|
+
10,
|
|
145
|
+
EntityScope::Set(BTreeSet::from([recipient.clone()])),
|
|
146
|
+
EntityScope::Set(BTreeSet::from([asset.clone()])),
|
|
147
|
+
EntityScope::Set(BTreeSet::from([1])),
|
|
148
|
+
)
|
|
149
|
+
.expect_err("zero min amount");
|
|
150
|
+
assert!(matches!(err, DomainError::InvalidAmount));
|
|
151
|
+
|
|
152
|
+
let err = SpendingPolicy::new_manual_approval(
|
|
153
|
+
1,
|
|
154
|
+
11,
|
|
155
|
+
10,
|
|
156
|
+
EntityScope::Set(BTreeSet::from([recipient.clone()])),
|
|
157
|
+
EntityScope::Set(BTreeSet::from([asset.clone()])),
|
|
158
|
+
EntityScope::Set(BTreeSet::from([1])),
|
|
159
|
+
)
|
|
160
|
+
.expect_err("min greater than max");
|
|
161
|
+
assert!(matches!(err, DomainError::InvalidAmount));
|
|
162
|
+
|
|
163
|
+
let err = SpendingPolicy::new(
|
|
164
|
+
1,
|
|
165
|
+
PolicyType::PerTxMaxSpending,
|
|
166
|
+
10,
|
|
167
|
+
EntityScope::Set(BTreeSet::from([recipient])),
|
|
168
|
+
EntityScope::Set(BTreeSet::from([asset])),
|
|
169
|
+
EntityScope::Set(BTreeSet::from([0])),
|
|
170
|
+
)
|
|
171
|
+
.expect_err("zero chain id");
|
|
172
|
+
assert!(matches!(err, DomainError::InvalidChainId));
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
|
|
176
|
+
struct U128Wrapper {
|
|
177
|
+
#[serde(with = "super::u128_as_decimal_string")]
|
|
178
|
+
value: u128,
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
|
|
182
|
+
struct OptionalU128Wrapper {
|
|
183
|
+
#[serde(with = "super::u128_as_decimal_string::option")]
|
|
184
|
+
value: Option<u128>,
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
#[test]
|
|
188
|
+
fn u128_decimal_string_helpers_roundtrip_and_reject_invalid_values() {
|
|
189
|
+
let encoded = serde_json::to_string(&U128Wrapper { value: 42 }).expect("encode");
|
|
190
|
+
assert_eq!(encoded, r#"{"value":"42"}"#);
|
|
191
|
+
let decoded: U128Wrapper = serde_json::from_str(&encoded).expect("decode");
|
|
192
|
+
assert_eq!(decoded, U128Wrapper { value: 42 });
|
|
193
|
+
|
|
194
|
+
let some = serde_json::to_string(&OptionalU128Wrapper { value: Some(7) }).expect("encode");
|
|
195
|
+
assert_eq!(some, r#"{"value":"7"}"#);
|
|
196
|
+
let decoded: OptionalU128Wrapper = serde_json::from_str(&some).expect("decode");
|
|
197
|
+
assert_eq!(decoded, OptionalU128Wrapper { value: Some(7) });
|
|
198
|
+
|
|
199
|
+
let none = serde_json::to_string(&OptionalU128Wrapper { value: None }).expect("encode");
|
|
200
|
+
assert_eq!(none, r#"{"value":null}"#);
|
|
201
|
+
let decoded: OptionalU128Wrapper = serde_json::from_str(&none).expect("decode");
|
|
202
|
+
assert_eq!(decoded, OptionalU128Wrapper { value: None });
|
|
203
|
+
|
|
204
|
+
let err = serde_json::from_str::<U128Wrapper>(r#"{"value":"nope"}"#).expect_err("invalid u128");
|
|
205
|
+
assert!(err.to_string().contains("invalid digit"));
|
|
206
|
+
|
|
207
|
+
let err =
|
|
208
|
+
serde_json::from_str::<OptionalU128Wrapper>(r#"{"value":"bad"}"#).expect_err("invalid option u128");
|
|
209
|
+
assert!(err.to_string().contains("invalid digit"));
|
|
210
|
+
}
|
|
211
|
+
|
|
91
212
|
#[test]
|
|
92
213
|
fn parse_erc20_transfer_call_succeeds() {
|
|
93
214
|
let to = alloy_primitives::Address::from([0x11; 20]);
|
|
@@ -430,6 +551,32 @@ sol! {
|
|
|
430
551
|
|
|
431
552
|
function permit(address owner, PermitSingle permitSingle, bytes signature);
|
|
432
553
|
function transferWithAuthorization(address from, address to, uint256 value, uint256 validAfter, uint256 validBefore, bytes32 nonce, bytes signature);
|
|
554
|
+
function receiveWithAuthorization(address from, address to, uint256 value, uint256 validAfter, uint256 validBefore, bytes32 nonce, bytes signature);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
#[test]
|
|
558
|
+
fn asset_id_display_formats_native_and_erc20_variants() {
|
|
559
|
+
let token: EvmAddress = "0x1234000000000000000000000000000000000000"
|
|
560
|
+
.parse()
|
|
561
|
+
.expect("token");
|
|
562
|
+
|
|
563
|
+
assert_eq!(AssetId::NativeEth.to_string(), "native_eth");
|
|
564
|
+
assert_eq!(AssetId::Erc20(token).to_string(), "erc20:0x1234000000000000000000000000000000000000");
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
#[test]
|
|
568
|
+
fn spending_policy_rejects_empty_network_sets() {
|
|
569
|
+
let err = SpendingPolicy::new(
|
|
570
|
+
1,
|
|
571
|
+
PolicyType::PerTxMaxSpending,
|
|
572
|
+
10,
|
|
573
|
+
EntityScope::All,
|
|
574
|
+
EntityScope::All,
|
|
575
|
+
EntityScope::Set(BTreeSet::new()),
|
|
576
|
+
)
|
|
577
|
+
.expect_err("empty network set must fail");
|
|
578
|
+
|
|
579
|
+
assert!(matches!(err, DomainError::InvalidChainId));
|
|
433
580
|
}
|
|
434
581
|
|
|
435
582
|
#[test]
|
|
@@ -617,6 +764,474 @@ fn permit2_and_eip3009_actions_produce_signing_hashes() {
|
|
|
617
764
|
assert_ne!(transfer_hash, receive_hash);
|
|
618
765
|
}
|
|
619
766
|
|
|
767
|
+
#[test]
|
|
768
|
+
fn permit2_and_eip3009_validation_reject_invalid_inputs() {
|
|
769
|
+
let base_permit = Permit2Permit {
|
|
770
|
+
chain_id: 1,
|
|
771
|
+
permit2_contract: "0x000000000022d473030f116ddee9f6b43ac78ba3"
|
|
772
|
+
.parse()
|
|
773
|
+
.expect("permit2"),
|
|
774
|
+
token: "0x1111111111111111111111111111111111111111"
|
|
775
|
+
.parse()
|
|
776
|
+
.expect("token"),
|
|
777
|
+
spender: "0x2222222222222222222222222222222222222222"
|
|
778
|
+
.parse()
|
|
779
|
+
.expect("spender"),
|
|
780
|
+
amount_wei: 123,
|
|
781
|
+
expiration: 100,
|
|
782
|
+
nonce: 7,
|
|
783
|
+
sig_deadline: 200,
|
|
784
|
+
};
|
|
785
|
+
assert!(matches!(
|
|
786
|
+
Permit2Permit {
|
|
787
|
+
chain_id: 0,
|
|
788
|
+
..base_permit.clone()
|
|
789
|
+
}
|
|
790
|
+
.validate(),
|
|
791
|
+
Err(DomainError::InvalidChainId)
|
|
792
|
+
));
|
|
793
|
+
assert!(matches!(
|
|
794
|
+
Permit2Permit {
|
|
795
|
+
amount_wei: 0,
|
|
796
|
+
..base_permit.clone()
|
|
797
|
+
}
|
|
798
|
+
.validate(),
|
|
799
|
+
Err(DomainError::InvalidAmount)
|
|
800
|
+
));
|
|
801
|
+
assert!(matches!(
|
|
802
|
+
Permit2Permit {
|
|
803
|
+
expiration: (1u64 << 48),
|
|
804
|
+
..base_permit.clone()
|
|
805
|
+
}
|
|
806
|
+
.validate(),
|
|
807
|
+
Err(DomainError::AmountOutOfRange)
|
|
808
|
+
));
|
|
809
|
+
assert!(matches!(
|
|
810
|
+
Permit2Permit {
|
|
811
|
+
nonce: (1u64 << 48),
|
|
812
|
+
..base_permit
|
|
813
|
+
}
|
|
814
|
+
.validate(),
|
|
815
|
+
Err(DomainError::AmountOutOfRange)
|
|
816
|
+
));
|
|
817
|
+
|
|
818
|
+
let base_eip3009 = Eip3009Transfer {
|
|
819
|
+
chain_id: 1,
|
|
820
|
+
token: "0x3333333333333333333333333333333333333333"
|
|
821
|
+
.parse()
|
|
822
|
+
.expect("token"),
|
|
823
|
+
token_name: "USD Coin".to_string(),
|
|
824
|
+
token_version: Some(String::new()),
|
|
825
|
+
from: "0x4444444444444444444444444444444444444444"
|
|
826
|
+
.parse()
|
|
827
|
+
.expect("from"),
|
|
828
|
+
to: "0x5555555555555555555555555555555555555555"
|
|
829
|
+
.parse()
|
|
830
|
+
.expect("to"),
|
|
831
|
+
amount_wei: 456,
|
|
832
|
+
valid_after: 10,
|
|
833
|
+
valid_before: 20,
|
|
834
|
+
nonce_hex: format!("0x{}", hex::encode([0xabu8; 32])),
|
|
835
|
+
};
|
|
836
|
+
let _ = base_eip3009
|
|
837
|
+
.transfer_signing_hash()
|
|
838
|
+
.expect("empty version should be ignored");
|
|
839
|
+
assert!(matches!(
|
|
840
|
+
Eip3009Transfer {
|
|
841
|
+
chain_id: 0,
|
|
842
|
+
..base_eip3009.clone()
|
|
843
|
+
}
|
|
844
|
+
.validate(),
|
|
845
|
+
Err(DomainError::InvalidChainId)
|
|
846
|
+
));
|
|
847
|
+
assert!(matches!(
|
|
848
|
+
Eip3009Transfer {
|
|
849
|
+
amount_wei: 0,
|
|
850
|
+
..base_eip3009.clone()
|
|
851
|
+
}
|
|
852
|
+
.validate(),
|
|
853
|
+
Err(DomainError::InvalidAmount)
|
|
854
|
+
));
|
|
855
|
+
assert!(matches!(
|
|
856
|
+
Eip3009Transfer {
|
|
857
|
+
token_name: " ".to_string(),
|
|
858
|
+
..base_eip3009.clone()
|
|
859
|
+
}
|
|
860
|
+
.validate(),
|
|
861
|
+
Err(DomainError::InvalidTypedDataDomain(_))
|
|
862
|
+
));
|
|
863
|
+
assert!(matches!(
|
|
864
|
+
Eip3009Transfer {
|
|
865
|
+
valid_before: 10,
|
|
866
|
+
..base_eip3009.clone()
|
|
867
|
+
}
|
|
868
|
+
.validate(),
|
|
869
|
+
Err(DomainError::InvalidAuthorizationWindow)
|
|
870
|
+
));
|
|
871
|
+
assert!(matches!(
|
|
872
|
+
Eip3009Transfer {
|
|
873
|
+
nonce_hex: "0x1234".to_string(),
|
|
874
|
+
..base_eip3009
|
|
875
|
+
}
|
|
876
|
+
.validate(),
|
|
877
|
+
Err(DomainError::InvalidTypedDataDomain(_))
|
|
878
|
+
));
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
#[test]
|
|
882
|
+
fn broadcast_tx_validation_covers_remaining_error_paths() {
|
|
883
|
+
let base_tx = BroadcastTx {
|
|
884
|
+
chain_id: 1,
|
|
885
|
+
nonce: 0,
|
|
886
|
+
to: "0xf0109fc8df283027b6285cc889f5aa624eac1f55"
|
|
887
|
+
.parse()
|
|
888
|
+
.expect("to"),
|
|
889
|
+
value_wei: 0,
|
|
890
|
+
data_hex: "0x".to_string(),
|
|
891
|
+
gas_limit: 21_000,
|
|
892
|
+
max_fee_per_gas_wei: 1_000_000_000,
|
|
893
|
+
max_priority_fee_per_gas_wei: 1_000_000_000,
|
|
894
|
+
tx_type: 0x02,
|
|
895
|
+
delegation_enabled: false,
|
|
896
|
+
};
|
|
897
|
+
|
|
898
|
+
assert!(matches!(
|
|
899
|
+
BroadcastTx {
|
|
900
|
+
chain_id: 0,
|
|
901
|
+
..base_tx.clone()
|
|
902
|
+
}
|
|
903
|
+
.validate(),
|
|
904
|
+
Err(DomainError::InvalidChainId)
|
|
905
|
+
));
|
|
906
|
+
assert!(matches!(
|
|
907
|
+
BroadcastTx {
|
|
908
|
+
gas_limit: 0,
|
|
909
|
+
..base_tx.clone()
|
|
910
|
+
}
|
|
911
|
+
.validate(),
|
|
912
|
+
Err(DomainError::InvalidGasConfiguration)
|
|
913
|
+
));
|
|
914
|
+
assert!(matches!(
|
|
915
|
+
BroadcastTx {
|
|
916
|
+
max_priority_fee_per_gas_wei: 2_000_000_000,
|
|
917
|
+
..base_tx.clone()
|
|
918
|
+
}
|
|
919
|
+
.validate(),
|
|
920
|
+
Err(DomainError::InvalidGasConfiguration)
|
|
921
|
+
));
|
|
922
|
+
assert!(matches!(
|
|
923
|
+
BroadcastTx {
|
|
924
|
+
gas_limit: u64::MAX,
|
|
925
|
+
max_fee_per_gas_wei: u128::MAX,
|
|
926
|
+
..base_tx.clone()
|
|
927
|
+
}
|
|
928
|
+
.validate(),
|
|
929
|
+
Err(DomainError::AmountOutOfRange)
|
|
930
|
+
));
|
|
931
|
+
assert!(matches!(
|
|
932
|
+
base_tx
|
|
933
|
+
.clone()
|
|
934
|
+
.eip1559_signed_raw_transaction(2, [0u8; 32], [0u8; 32]),
|
|
935
|
+
Err(DomainError::InvalidSignatureParity)
|
|
936
|
+
));
|
|
937
|
+
assert!(matches!(
|
|
938
|
+
BroadcastTx {
|
|
939
|
+
tx_type: EIP7702_TX_TYPE,
|
|
940
|
+
..base_tx
|
|
941
|
+
}
|
|
942
|
+
.eip1559_signed_raw_transaction(1, [0u8; 32], [0u8; 32]),
|
|
943
|
+
Err(DomainError::UnsupportedTransactionType(_))
|
|
944
|
+
));
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
#[test]
|
|
948
|
+
fn agent_action_helpers_cover_remaining_variants_and_none_paths() {
|
|
949
|
+
let approve = AgentAction::Approve {
|
|
950
|
+
chain_id: 1,
|
|
951
|
+
token: "0x1111111111111111111111111111111111111111"
|
|
952
|
+
.parse()
|
|
953
|
+
.expect("token"),
|
|
954
|
+
spender: "0x2222222222222222222222222222222222222222"
|
|
955
|
+
.parse()
|
|
956
|
+
.expect("spender"),
|
|
957
|
+
amount_wei: 12,
|
|
958
|
+
};
|
|
959
|
+
assert_eq!(
|
|
960
|
+
approve.recipient(),
|
|
961
|
+
"0x2222222222222222222222222222222222222222"
|
|
962
|
+
.parse()
|
|
963
|
+
.expect("spender")
|
|
964
|
+
);
|
|
965
|
+
assert_eq!(approve.max_fee_per_gas_wei(), None);
|
|
966
|
+
assert_eq!(approve.max_priority_fee_per_gas_wei(), None);
|
|
967
|
+
assert_eq!(approve.calldata_len_bytes(), None);
|
|
968
|
+
assert_eq!(approve.signing_hash().expect("non-typed action"), None);
|
|
969
|
+
|
|
970
|
+
let receive_authorization = Eip3009Transfer {
|
|
971
|
+
chain_id: 1,
|
|
972
|
+
token: "0x3333333333333333333333333333333333333333"
|
|
973
|
+
.parse()
|
|
974
|
+
.expect("token"),
|
|
975
|
+
token_name: "USD Coin".to_string(),
|
|
976
|
+
token_version: Some("2".to_string()),
|
|
977
|
+
from: "0x4444444444444444444444444444444444444444"
|
|
978
|
+
.parse()
|
|
979
|
+
.expect("from"),
|
|
980
|
+
to: "0x5555555555555555555555555555555555555555"
|
|
981
|
+
.parse()
|
|
982
|
+
.expect("to"),
|
|
983
|
+
amount_wei: 34,
|
|
984
|
+
valid_after: 10,
|
|
985
|
+
valid_before: 20,
|
|
986
|
+
nonce_hex: format!("0x{}", hex::encode([0xceu8; 32])),
|
|
987
|
+
};
|
|
988
|
+
let receive = AgentAction::Eip3009ReceiveWithAuthorization {
|
|
989
|
+
authorization: receive_authorization,
|
|
990
|
+
};
|
|
991
|
+
assert_eq!(
|
|
992
|
+
receive.asset(),
|
|
993
|
+
AssetId::Erc20(
|
|
994
|
+
"0x3333333333333333333333333333333333333333"
|
|
995
|
+
.parse()
|
|
996
|
+
.expect("token")
|
|
997
|
+
)
|
|
998
|
+
);
|
|
999
|
+
assert_eq!(
|
|
1000
|
+
receive.recipient(),
|
|
1001
|
+
"0x5555555555555555555555555555555555555555"
|
|
1002
|
+
.parse()
|
|
1003
|
+
.expect("to")
|
|
1004
|
+
);
|
|
1005
|
+
|
|
1006
|
+
assert!(matches!(
|
|
1007
|
+
AgentAction::TransferNative {
|
|
1008
|
+
chain_id: 1,
|
|
1009
|
+
to: "0x6666666666666666666666666666666666666666"
|
|
1010
|
+
.parse()
|
|
1011
|
+
.expect("to"),
|
|
1012
|
+
amount_wei: 0,
|
|
1013
|
+
}
|
|
1014
|
+
.validate(),
|
|
1015
|
+
Err(DomainError::InvalidAmount)
|
|
1016
|
+
));
|
|
1017
|
+
assert!(matches!(
|
|
1018
|
+
AgentAction::TransferNative {
|
|
1019
|
+
chain_id: 0,
|
|
1020
|
+
to: "0x6666666666666666666666666666666666666666"
|
|
1021
|
+
.parse()
|
|
1022
|
+
.expect("to"),
|
|
1023
|
+
amount_wei: 1,
|
|
1024
|
+
}
|
|
1025
|
+
.validate(),
|
|
1026
|
+
Err(DomainError::InvalidChainId)
|
|
1027
|
+
));
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
#[test]
|
|
1031
|
+
fn action_from_erc20_calldata_covers_transfer_and_zero_chain_guard() {
|
|
1032
|
+
let to = alloy_primitives::Address::from([0x11; 20]);
|
|
1033
|
+
let calldata = transferCall {
|
|
1034
|
+
to,
|
|
1035
|
+
value: U256::from(42_u64),
|
|
1036
|
+
}
|
|
1037
|
+
.abi_encode();
|
|
1038
|
+
let token: EvmAddress = "0x3333333333333333333333333333333333333333"
|
|
1039
|
+
.parse()
|
|
1040
|
+
.expect("token");
|
|
1041
|
+
|
|
1042
|
+
assert!(matches!(
|
|
1043
|
+
action_from_erc20_calldata(0, token.clone(), &calldata),
|
|
1044
|
+
Err(DomainError::InvalidChainId)
|
|
1045
|
+
));
|
|
1046
|
+
assert_eq!(
|
|
1047
|
+
action_from_erc20_calldata(1, token.clone(), &calldata).expect("transfer action"),
|
|
1048
|
+
AgentAction::Transfer {
|
|
1049
|
+
chain_id: 1,
|
|
1050
|
+
token,
|
|
1051
|
+
to: "0x1111111111111111111111111111111111111111"
|
|
1052
|
+
.parse()
|
|
1053
|
+
.expect("to"),
|
|
1054
|
+
amount_wei: 42,
|
|
1055
|
+
}
|
|
1056
|
+
);
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
#[test]
|
|
1060
|
+
fn broadcast_action_derives_approve_and_receive_with_authorization_scope() {
|
|
1061
|
+
let approve_calldata = approveCall {
|
|
1062
|
+
spender: alloy_primitives::Address::from([0x77; 20]),
|
|
1063
|
+
value: U256::from(88_u64),
|
|
1064
|
+
}
|
|
1065
|
+
.abi_encode();
|
|
1066
|
+
let approve_action = AgentAction::BroadcastTx {
|
|
1067
|
+
tx: BroadcastTx {
|
|
1068
|
+
chain_id: 1,
|
|
1069
|
+
nonce: 0,
|
|
1070
|
+
to: "0x8888000000000000000000000000000000000000"
|
|
1071
|
+
.parse()
|
|
1072
|
+
.expect("token"),
|
|
1073
|
+
value_wei: 0,
|
|
1074
|
+
data_hex: format!("0x{}", hex::encode(approve_calldata)),
|
|
1075
|
+
gas_limit: 80_000,
|
|
1076
|
+
max_fee_per_gas_wei: 1_000_000_000,
|
|
1077
|
+
max_priority_fee_per_gas_wei: 1_000_000_000,
|
|
1078
|
+
tx_type: 0x02,
|
|
1079
|
+
delegation_enabled: false,
|
|
1080
|
+
},
|
|
1081
|
+
};
|
|
1082
|
+
assert_eq!(
|
|
1083
|
+
approve_action.recipient(),
|
|
1084
|
+
"0x7777777777777777777777777777777777777777"
|
|
1085
|
+
.parse()
|
|
1086
|
+
.expect("spender")
|
|
1087
|
+
);
|
|
1088
|
+
assert_eq!(approve_action.amount_wei(), 88);
|
|
1089
|
+
|
|
1090
|
+
let receive_calldata = receiveWithAuthorizationCall {
|
|
1091
|
+
from: alloy_primitives::Address::from([0x11; 20]),
|
|
1092
|
+
to: alloy_primitives::Address::from([0x22; 20]),
|
|
1093
|
+
value: U256::from(99_u64),
|
|
1094
|
+
validAfter: U256::from(1_u64),
|
|
1095
|
+
validBefore: U256::from(2_u64),
|
|
1096
|
+
nonce: [0x44; 32].into(),
|
|
1097
|
+
signature: vec![0xaa, 0xbb].into(),
|
|
1098
|
+
}
|
|
1099
|
+
.abi_encode();
|
|
1100
|
+
let receive_action = AgentAction::BroadcastTx {
|
|
1101
|
+
tx: BroadcastTx {
|
|
1102
|
+
chain_id: 1,
|
|
1103
|
+
nonce: 0,
|
|
1104
|
+
to: "0x9999000000000000000000000000000000000000"
|
|
1105
|
+
.parse()
|
|
1106
|
+
.expect("token"),
|
|
1107
|
+
value_wei: 0,
|
|
1108
|
+
data_hex: format!("0x{}", hex::encode(receive_calldata)),
|
|
1109
|
+
gas_limit: 90_000,
|
|
1110
|
+
max_fee_per_gas_wei: 1_000_000_000,
|
|
1111
|
+
max_priority_fee_per_gas_wei: 1_000_000_000,
|
|
1112
|
+
tx_type: 0x02,
|
|
1113
|
+
delegation_enabled: false,
|
|
1114
|
+
},
|
|
1115
|
+
};
|
|
1116
|
+
assert_eq!(
|
|
1117
|
+
receive_action.recipient(),
|
|
1118
|
+
"0x2222222222222222222222222222222222222222"
|
|
1119
|
+
.parse()
|
|
1120
|
+
.expect("recipient")
|
|
1121
|
+
);
|
|
1122
|
+
assert_eq!(receive_action.amount_wei(), 99);
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
#[test]
|
|
1126
|
+
fn broadcast_tx_supports_long_rlp_payloads_and_rejects_out_of_range_token_amounts() {
|
|
1127
|
+
let tx = BroadcastTx {
|
|
1128
|
+
chain_id: 1,
|
|
1129
|
+
nonce: 0,
|
|
1130
|
+
to: "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
|
1131
|
+
.parse()
|
|
1132
|
+
.expect("to"),
|
|
1133
|
+
value_wei: 0,
|
|
1134
|
+
data_hex: format!("0x{}", "11".repeat(64)),
|
|
1135
|
+
gas_limit: 21_000,
|
|
1136
|
+
max_fee_per_gas_wei: 1_000_000_000,
|
|
1137
|
+
max_priority_fee_per_gas_wei: 1_000_000_000,
|
|
1138
|
+
tx_type: 0x02,
|
|
1139
|
+
delegation_enabled: false,
|
|
1140
|
+
};
|
|
1141
|
+
let raw = tx
|
|
1142
|
+
.eip1559_signed_raw_transaction(1, [0x11; 32], [0x22; 32])
|
|
1143
|
+
.expect("long payload should encode");
|
|
1144
|
+
assert_eq!(raw.first().copied(), Some(0x02));
|
|
1145
|
+
|
|
1146
|
+
let oversized_approve = approveCall {
|
|
1147
|
+
spender: alloy_primitives::Address::from([0x33; 20]),
|
|
1148
|
+
value: U256::from(u128::MAX) + U256::from(1u8),
|
|
1149
|
+
}
|
|
1150
|
+
.abi_encode();
|
|
1151
|
+
assert!(matches!(
|
|
1152
|
+
parse_erc20_call(&oversized_approve),
|
|
1153
|
+
Err(DomainError::AmountOutOfRange)
|
|
1154
|
+
));
|
|
1155
|
+
|
|
1156
|
+
let oversized_permit = permitCall {
|
|
1157
|
+
owner: alloy_primitives::Address::from([0x66; 20]),
|
|
1158
|
+
permitSingle: PermitSingle {
|
|
1159
|
+
details: PermitDetails {
|
|
1160
|
+
token: alloy_primitives::Address::from([0x44; 20]),
|
|
1161
|
+
amount: alloy_primitives::U160::MAX,
|
|
1162
|
+
expiration: alloy_primitives::aliases::U48::from_be_slice(&100u64.to_be_bytes()[2..]),
|
|
1163
|
+
nonce: alloy_primitives::aliases::U48::from_be_slice(&7u64.to_be_bytes()[2..]),
|
|
1164
|
+
},
|
|
1165
|
+
spender: alloy_primitives::Address::from([0x55; 20]),
|
|
1166
|
+
sigDeadline: U256::from(1234u64),
|
|
1167
|
+
},
|
|
1168
|
+
signature: vec![0x12, 0x34].into(),
|
|
1169
|
+
}
|
|
1170
|
+
.abi_encode();
|
|
1171
|
+
let action = AgentAction::BroadcastTx {
|
|
1172
|
+
tx: BroadcastTx {
|
|
1173
|
+
chain_id: 1,
|
|
1174
|
+
nonce: 0,
|
|
1175
|
+
to: "0x9999000000000000000000000000000000000000"
|
|
1176
|
+
.parse()
|
|
1177
|
+
.expect("permit2 contract"),
|
|
1178
|
+
value_wei: 0,
|
|
1179
|
+
data_hex: format!("0x{}", hex::encode(oversized_permit)),
|
|
1180
|
+
gas_limit: 60_000,
|
|
1181
|
+
max_fee_per_gas_wei: 1_000_000_000,
|
|
1182
|
+
max_priority_fee_per_gas_wei: 1_000_000_000,
|
|
1183
|
+
tx_type: 0x02,
|
|
1184
|
+
delegation_enabled: false,
|
|
1185
|
+
},
|
|
1186
|
+
};
|
|
1187
|
+
assert_eq!(action.asset(), AssetId::NativeEth);
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
#[test]
|
|
1191
|
+
fn invalid_hex_payloads_are_rejected_for_transactions_and_typed_nonces() {
|
|
1192
|
+
let tx = BroadcastTx {
|
|
1193
|
+
chain_id: 1,
|
|
1194
|
+
nonce: 0,
|
|
1195
|
+
to: "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
|
1196
|
+
.parse()
|
|
1197
|
+
.expect("to"),
|
|
1198
|
+
value_wei: 0,
|
|
1199
|
+
data_hex: "0x123".to_string(),
|
|
1200
|
+
gas_limit: 21_000,
|
|
1201
|
+
max_fee_per_gas_wei: 1_000_000_000,
|
|
1202
|
+
max_priority_fee_per_gas_wei: 1_000_000_000,
|
|
1203
|
+
tx_type: 0x02,
|
|
1204
|
+
delegation_enabled: false,
|
|
1205
|
+
};
|
|
1206
|
+
assert!(matches!(
|
|
1207
|
+
tx.validate(),
|
|
1208
|
+
Err(DomainError::InvalidTransactionDataHex)
|
|
1209
|
+
));
|
|
1210
|
+
|
|
1211
|
+
let authorization = Eip3009Transfer {
|
|
1212
|
+
chain_id: 1,
|
|
1213
|
+
token: "0x3333333333333333333333333333333333333333"
|
|
1214
|
+
.parse()
|
|
1215
|
+
.expect("token"),
|
|
1216
|
+
token_name: "USD Coin".to_string(),
|
|
1217
|
+
token_version: Some("2".to_string()),
|
|
1218
|
+
from: "0x4444444444444444444444444444444444444444"
|
|
1219
|
+
.parse()
|
|
1220
|
+
.expect("from"),
|
|
1221
|
+
to: "0x5555555555555555555555555555555555555555"
|
|
1222
|
+
.parse()
|
|
1223
|
+
.expect("to"),
|
|
1224
|
+
amount_wei: 456,
|
|
1225
|
+
valid_after: 10,
|
|
1226
|
+
valid_before: 20,
|
|
1227
|
+
nonce_hex: "0xzz".to_string(),
|
|
1228
|
+
};
|
|
1229
|
+
assert!(matches!(
|
|
1230
|
+
authorization.validate(),
|
|
1231
|
+
Err(DomainError::InvalidTypedDataDomain(_))
|
|
1232
|
+
));
|
|
1233
|
+
}
|
|
1234
|
+
|
|
620
1235
|
#[test]
|
|
621
1236
|
fn nonce_reservation_request_debug_redacts_agent_auth_token() {
|
|
622
1237
|
let request = NonceReservationRequest {
|
|
@@ -625,6 +1240,7 @@ fn nonce_reservation_request_debug_redacts_agent_auth_token() {
|
|
|
625
1240
|
agent_auth_token: "super-secret-token".to_string(),
|
|
626
1241
|
chain_id: 1,
|
|
627
1242
|
min_nonce: 7,
|
|
1243
|
+
exact_nonce: false,
|
|
628
1244
|
requested_at: OffsetDateTime::UNIX_EPOCH,
|
|
629
1245
|
expires_at: OffsetDateTime::UNIX_EPOCH + time::Duration::minutes(2),
|
|
630
1246
|
};
|