Skip to content

Commit b8dba2f

Browse files
Restrict addresses on Calibnet USDFC faucet (#344)
1 parent 5b51ad9 commit b8dba2f

File tree

4 files changed

+123
-64
lines changed

4 files changed

+123
-64
lines changed

docs/api-documentation.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ curl "https://forest-explorer.chainsafe.dev/api/claim_token?faucet_info=Calibnet
8787
**Response:**
8888

8989
```bash
90-
ServerError|Invalid address: Not a valid Testnet address
90+
ServerError|Invalid address - failed to parse: Not a valid Testnet address
9191
```
9292

9393
#### 429 Too Many Requests
@@ -241,13 +241,13 @@ curl "https://forest-explorer.chainsafe.dev/api/claim_token_all?address=invalida
241241
{
242242
"faucet_info": "CalibnetUSDFC",
243243
"error": {
244-
"ServerError": "Invalid address: Not a valid Testnet address"
244+
"ServerError": "Invalid address - failed to parse: Not a valid Testnet address"
245245
}
246246
},
247247
{
248248
"faucet_info": "CalibnetFIL",
249249
"error": {
250-
"ServerError": "Invalid address: Not a valid Testnet address"
250+
"ServerError": "Invalid address - failed to parse: Not a valid Testnet address"
251251
}
252252
}
253253
]
@@ -291,6 +291,8 @@ curl "https://forest-explorer.chainsafe.dev/api/claim_token_all?address=0xAe9C4b
291291
292292
- Each address is subject to rate limiting to prevent abuse.
293293
- This API only distributes Calibnet `tFIL` and `tUSDFC` tokens.
294+
- ID address or its corresponding eth style `0xff…ID` address are restricted to
295+
claim `tUSDFC` tokens.
294296
295297
## Rate Limits
296298

e2e/test_claim_token_api_config.js

Lines changed: 16 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,21 @@ export const TEST_SCENARIOS = {
136136
name: 'Invalid address format for CalibnetUSDFC',
137137
faucet_info: FaucetTypes.CalibnetUSDFC,
138138
address: TEST_ADDRESSES.T1_FORMAT_ADDRESS,
139-
expectedStatus: STATUS_CODES.INTERNAL_SERVER_ERROR,
139+
expectedStatus: STATUS_CODES.BAD_REQUEST,
140+
expectedErrorContains: 'invalid address'
141+
},
142+
{
143+
name: 'CalibnetUSDFC (0xff...ID) - restricted address (RESTRICTED)',
144+
faucet_info: FaucetTypes.CalibnetUSDFC,
145+
address: TEST_ADDRESSES.ETH_ID_CORRESPONDING,
146+
expectedStatus: STATUS_CODES.BAD_REQUEST,
147+
expectedErrorContains: 'invalid address'
148+
},
149+
{
150+
name: 'CalibnetUSDFC (t0) - restricted address (RESTRICTED)',
151+
faucet_info: FaucetTypes.CalibnetUSDFC,
152+
address: TEST_ADDRESSES.T0_ADDRESS,
153+
expectedStatus: STATUS_CODES.BAD_REQUEST,
140154
expectedErrorContains: 'invalid address'
141155
}
142156
],
@@ -186,19 +200,6 @@ export const TEST_SCENARIOS = {
186200
faucet_info: FaucetTypes.CalibnetUSDFC,
187201
address: TEST_ADDRESSES.T410_ADDRESS,
188202
expectedStatus: STATUS_CODES.TOO_MANY_REQUESTS
189-
},
190-
{
191-
name: 'CalibnetUSDFC (t0) - RATE LIMITED (within CalibnetUSDFC cooldown)',
192-
faucet_info: FaucetTypes.CalibnetUSDFC,
193-
address: TEST_ADDRESSES.T0_ADDRESS,
194-
expectedStatus: STATUS_CODES.TOO_MANY_REQUESTS
195-
},
196-
// CalibnetUSDFC doesn't support the t1 format address
197-
{
198-
name: 'CalibnetUSDFC (ID) - RATE LIMITED (within CalibnetUSDFC cooldown)',
199-
faucet_info: FaucetTypes.CalibnetUSDFC,
200-
address: TEST_ADDRESSES.ETH_ID_CORRESPONDING,
201-
expectedStatus: STATUS_CODES.TOO_MANY_REQUESTS
202203
}
203204
],
204205

@@ -295,44 +296,6 @@ export const TEST_SCENARIOS = {
295296
expectedStatus: STATUS_CODES.TOO_MANY_REQUESTS,
296297
waitBefore: 0, // No wait needed, should be capped, already from the previous step
297298
walletCapErrorResponse: true,
298-
},
299-
300-
// TODO(forest-explorer): https://github.com/ChainSafe/forest-explorer/issues/335
301-
// Token sent to the t0 and it's eth mapping address 0x
302-
// are not accessible. Hence commenting out the following
303-
// test cases.
304-
// === CalibnetUSDFC ID Wallet (fresh wallet, 0 transactions) ===
305-
// {
306-
// name: 'CalibnetUSDFC (ID) - 1st SUCCESS (fresh wallet)',
307-
// faucet_info: FaucetTypes.CalibnetUSDFC,
308-
// address: TEST_ADDRESSES.ETH_ID_CORRESPONDING,
309-
// expectedStatus: STATUS_CODES.SUCCESS,
310-
// waitBefore: 65, // Wait for cooldown from the previous test group to expire
311-
// walletCapErrorResponse: false,
312-
// },
313-
// {
314-
// name: 'CalibnetUSDFC (ID) - 2nd SUCCESS (reaches cap)',
315-
// faucet_info: FaucetTypes.CalibnetUSDFC,
316-
// address: TEST_ADDRESSES.ETH_ID_CORRESPONDING,
317-
// expectedStatus: STATUS_CODES.SUCCESS,
318-
// waitBefore: 65, // Wait for cooldown from its own 1st transaction
319-
// walletCapErrorResponse: false,
320-
// },
321-
// {
322-
// name: 'CalibnetUSDFC (ID) - 3rd attempt (WALLET CAPPED)',
323-
// faucet_info: FaucetTypes.CalibnetUSDFC,
324-
// address: TEST_ADDRESSES.ETH_ID_CORRESPONDING,
325-
// expectedStatus: STATUS_CODES.TOO_MANY_REQUESTS,
326-
// waitBefore: 65, // Wait for cooldown from its own 2nd transaction
327-
// walletCapErrorResponse: true,
328-
// },
329-
// {
330-
// name: 'CalibnetUSDFC (t0) - check equivalence (WALLET CAPPED)',
331-
// faucet_info: FaucetTypes.CalibnetUSDFC,
332-
// address: TEST_ADDRESSES.T0_ADDRESS, // This is the same wallet as the ID address
333-
// expectedStatus: STATUS_CODES.TOO_MANY_REQUESTS,
334-
// waitBefore: 0, // No wait needed, should be capped already
335-
// walletCapErrorResponse: true,
336-
// },
299+
}
337300
]
338301
};

src/faucet/server_api.rs

Lines changed: 101 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ pub async fn claim_token(
222222

223223
let network = faucet_info.network();
224224
set_current_network(network);
225-
let recipient = parse_and_validate_address(&address, network)?;
225+
let recipient = parse_and_validate_address(&address, faucet_info)?;
226226
let rpc = Provider::from_network(network);
227227
let from = faucet_address(faucet_info)
228228
.await?
@@ -271,18 +271,36 @@ pub async fn claim_token_all(address: String) -> Result<Vec<ClaimResponse>, Serv
271271
Ok(results)
272272
}
273273

274+
/// Checks if the provided address is valid for the faucet, ensuring invalid addresses are rejected.
275+
#[cfg(feature = "ssr")]
276+
fn check_valid_address(address: Address, faucet_info: FaucetInfo) -> Result<(), ServerFnError> {
277+
use fvm_shared::address::Protocol;
278+
279+
if matches!(faucet_info, FaucetInfo::CalibnetUSDFC)
280+
&& (address.protocol() != Protocol::Delegated)
281+
{
282+
log::error!("Invalid address: {:?}", address);
283+
set_response_status(StatusCode::BAD_REQUEST);
284+
return Err(ServerFnError::ServerError("Invalid address: Only Ethereum-compatible addresses (delegated t4 addresses or native Ethereum 0x addresses) are allowed for Calibnet USDFC token claims.".to_string()));
285+
}
286+
Ok(())
287+
}
288+
274289
#[cfg(feature = "ssr")]
275290
fn parse_and_validate_address(
276291
address: &str,
277-
network: fvm_shared::address::Network,
292+
faucet_info: FaucetInfo,
278293
) -> Result<Address, ServerFnError> {
279-
match crate::utils::address::parse_address(address, network) {
280-
Ok(addr) => Ok(addr),
294+
match crate::utils::address::parse_address(address, faucet_info.network()) {
295+
Ok(addr) => {
296+
check_valid_address(addr, faucet_info)?;
297+
Ok(addr)
298+
}
281299
Err(e) => {
282-
log::error!("Invalid address: {}", e);
300+
log::error!("Invalid address - failed to parse: {}", e);
283301
set_response_status(StatusCode::BAD_REQUEST);
284302
Err(ServerFnError::ServerError(format!(
285-
"Invalid address: {}",
303+
"Invalid address - failed to parse: {}",
286304
e
287305
)))
288306
}
@@ -403,5 +421,81 @@ fn handle_faucet_error(err: FaucetError) -> ServerFnError {
403421

404422
#[cfg(feature = "ssr")]
405423
fn set_response_status(status: StatusCode) {
406-
leptos::prelude::expect_context::<ResponseOptions>().set_status(status);
424+
if let Some(res) = leptos::context::use_context::<ResponseOptions>() {
425+
res.set_status(status)
426+
}
427+
}
428+
429+
#[cfg(all(test, feature = "ssr"))]
430+
mod tests {
431+
use crate::faucet::server_api::*;
432+
433+
fn assert_valid_address(address: &str, faucet: FaucetInfo) {
434+
let network = faucet.network();
435+
let addr = crate::utils::address::parse_address(address, network).unwrap();
436+
assert!(check_valid_address(addr, faucet).is_ok());
437+
assert!(parse_and_validate_address(address, faucet).is_ok());
438+
}
439+
440+
fn assert_invalid_address(address: &str, faucet: FaucetInfo) {
441+
let network = faucet.network();
442+
let addr = crate::utils::address::parse_address(address, network).unwrap();
443+
assert!(check_valid_address(addr, faucet).is_err());
444+
assert!(parse_and_validate_address(address, faucet).is_err());
445+
}
446+
447+
#[test]
448+
fn test_check_valid_address_mainnet() {
449+
let addresses = [
450+
"f03603846",
451+
"f1rgci272nfk4k6cpyejepzv4xstpejjckldlzidy",
452+
"f2yjb6dq3jggychgnuhevcwe7ehv3ot2rkhkbk4qy",
453+
"f3s5kg6rehbbmgvngpec6b7m4uxmwbscdafn2pvtrrp65wbgjuymrr2z6qbkqiunkyjul6b62buqk76q47cjeq",
454+
"f410fv2oexfiizeuzm3xtoie3gnxfpfwwglg4q3dgxki",
455+
"0xff0000000000000000000000000000000036fd86",
456+
"0xAe9C4b9508c929966ef37209b336E5796D632CDc",
457+
];
458+
for addr in addresses.iter() {
459+
assert_valid_address(addr, FaucetInfo::MainnetFIL);
460+
}
461+
}
462+
463+
#[test]
464+
fn test_check_valid_address_calibnet() {
465+
let addresses = [
466+
"t03603846",
467+
"t1rgci272nfk4k6cpyejepzv4xstpejjckldlzidy",
468+
"t2yjb6dq3jggychgnuhevcwe7ehv3ot2rkhkbk4qy",
469+
"t3s5kg6rehbbmgvngpec6b7m4uxmwbscdafn2pvtrrp65wbgjuymrr2z6qbkqiunkyjul6b62buqk76q47cjeq",
470+
"t410fv2oexfiizeuzm3xtoie3gnxfpfwwglg4q3dgxki",
471+
"0xff0000000000000000000000000000000036f672",
472+
"0xAe9C4b9508c929966ef37209b336E5796D632CDc",
473+
];
474+
for addr in addresses.iter() {
475+
assert_valid_address(addr, FaucetInfo::CalibnetFIL);
476+
}
477+
}
478+
479+
#[test]
480+
fn test_check_valid_address_calibnet_usdfc() {
481+
let valid_addresses = [
482+
"0xAe9C4b9508c929966ef37209b336E5796D632CDc",
483+
"t410fv2oexfiizeuzm3xtoie3gnxfpfwwglg4q3dgxki",
484+
];
485+
let invalid_addresses = [
486+
"t03603846",
487+
"t1rgci272nfk4k6cpyejepzv4xstpejjckldlzidy",
488+
"t2yjb6dq3jggychgnuhevcwe7ehv3ot2rkhkbk4qy",
489+
"t3s5kg6rehbbmgvngpec6b7m4uxmwbscdafn2pvtrrp65wbgjuymrr2z6qbkqiunkyjul6b62buqk76q47cjeq",
490+
"0xff0000000000000000000000000000000036f672",
491+
];
492+
493+
for addr in valid_addresses.iter() {
494+
assert_valid_address(addr, FaucetInfo::CalibnetUSDFC);
495+
}
496+
497+
for addr in invalid_addresses.iter() {
498+
assert_invalid_address(addr, FaucetInfo::CalibnetUSDFC);
499+
}
500+
}
407501
}

src/utils/address.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ pub fn parse_address(raw: &str, n: Network) -> anyhow::Result<Address> {
6363
s.chars().skip(2).all(|c| c.is_ascii_hexdigit()),
6464
"Invalid characters in address"
6565
);
66-
if let Some(id) = s.strip_prefix("0xff") {
66+
if let Some(id) = s.strip_prefix("0xff0000000000000000000000") {
6767
let id = u64::from_str_radix(id, 16)?;
6868
Ok(Address::new_id(id))
6969
} else {

0 commit comments

Comments
 (0)