|
4 | 4 |
|
5 | 5 | use fendermint_actor_blobs_shared::{ |
6 | 6 | blobs::{ |
7 | | - AddBlobParams, Blob, BlobStatus, DeleteBlobParams, GetBlobParams, OverwriteBlobParams, |
8 | | - TrimBlobExpiriesParams, |
| 7 | + AddBlobParams, Blob, BlobStatus, DeleteBlobParams, FinalizeBlobParams, GetBlobParams, |
| 8 | + OverwriteBlobParams, SubscriptionId, TrimBlobExpiriesParams, |
9 | 9 | }, |
10 | 10 | bytes::B256, |
11 | 11 | execution::{ |
@@ -120,6 +120,8 @@ pub fn parse_input(input: &ipc_storage_actor_sdk::evm::InputData) -> Result<Call |
120 | 120 | pub const REGISTER_NODE_OPERATOR_SELECTOR: [u8; 4] = [0x71, 0x3b, 0x10, 0xcf]; |
121 | 121 | pub const GET_OPERATOR_INFO_SELECTOR: [u8; 4] = [0x27, 0xd9, 0xab, 0x5d]; |
122 | 122 | pub const GET_ACTIVE_OPERATORS_SELECTOR: [u8; 4] = [0x64, 0xbd, 0xc6, 0x7e]; |
| 123 | +/// keccak256("finalizeBlob(bytes32,address,bytes32,uint64,string,uint8,bytes,uint128)") |
| 124 | +pub const FINALIZE_BLOB_SELECTOR: [u8; 4] = [0xf6, 0x94, 0x17, 0x21]; |
123 | 125 |
|
124 | 126 | pub struct RegisterNodeOperatorInvokeCall { |
125 | 127 | pub bls_pubkey: Vec<u8>, |
@@ -183,6 +185,10 @@ pub fn is_fail_job_call(input: &ipc_storage_actor_sdk::evm::InputData) -> bool { |
183 | 185 | input.selector() == FAIL_JOB_SELECTOR |
184 | 186 | } |
185 | 187 |
|
| 188 | +pub fn is_finalize_blob_call(input: &ipc_storage_actor_sdk::evm::InputData) -> bool { |
| 189 | + input.selector() == FINALIZE_BLOB_SELECTOR |
| 190 | +} |
| 191 | + |
186 | 192 | pub fn parse_register_node_operator_input( |
187 | 193 | input: &ipc_storage_actor_sdk::evm::InputData, |
188 | 194 | ) -> Result<RegisterNodeOperatorInvokeCall, ActorError> { |
@@ -309,6 +315,54 @@ pub fn parse_fail_job_input( |
309 | 315 | }) |
310 | 316 | } |
311 | 317 |
|
| 318 | +/// Parses ABI-encoded calldata for `finalizeBlob(bytes32,address,bytes32,uint64,string,uint8,bytes,uint128)`. |
| 319 | +pub fn parse_finalize_blob_input( |
| 320 | + input: &ipc_storage_actor_sdk::evm::InputData, |
| 321 | + rt: &impl Runtime, |
| 322 | +) -> Result<FinalizeBlobParams, ActorError> { |
| 323 | + let calldata = input.calldata(); |
| 324 | + // 8 head slots: source(32) + subscriber(32) + blobHash(32) + size(32) |
| 325 | + // + string_offset(32) + status(32) + bytes_offset(32) + signerBitmap(32) |
| 326 | + if calldata.len() < 32 * 8 { |
| 327 | + return Err(actor_error!( |
| 328 | + illegal_argument, |
| 329 | + "invalid finalizeBlob call: input too short" |
| 330 | + )); |
| 331 | + } |
| 332 | + |
| 333 | + let source = decode_b256_word(calldata, 0)?; |
| 334 | + let subscriber_h160 = decode_address_word(calldata, 32)?; |
| 335 | + let subscriber: Address = subscriber_h160.into(); |
| 336 | + let subscriber = rt |
| 337 | + .resolve_address(&subscriber) |
| 338 | + .map(Address::new_id) |
| 339 | + .unwrap_or(subscriber); |
| 340 | + let hash = decode_b256_word(calldata, 64)?; |
| 341 | + let size = decode_u64_word(calldata, 96)?; |
| 342 | + let subscription_id_str = decode_dynamic_string(calldata, decode_offset(calldata, 128)?)?; |
| 343 | + let subscription_id: SubscriptionId = subscription_id_str.try_into().map_err(|e| { |
| 344 | + actor_error!( |
| 345 | + illegal_argument, |
| 346 | + format!("invalid finalizeBlob call: bad subscription id: {}", e) |
| 347 | + ) |
| 348 | + })?; |
| 349 | + let status_u8 = decode_u8_word(calldata, 160)?; |
| 350 | + let status = solidity_enum_to_blob_status(status_u8)?; |
| 351 | + let aggregated_signature = decode_dynamic_bytes(calldata, decode_offset(calldata, 192)?)?; |
| 352 | + let signer_bitmap = decode_u128_word(calldata, 224)?; |
| 353 | + |
| 354 | + Ok(FinalizeBlobParams { |
| 355 | + source, |
| 356 | + subscriber, |
| 357 | + hash, |
| 358 | + size, |
| 359 | + id: subscription_id, |
| 360 | + status, |
| 361 | + aggregated_signature, |
| 362 | + signer_bitmap, |
| 363 | + }) |
| 364 | +} |
| 365 | + |
312 | 366 | impl From<CreateJobInvokeCall> for CreateJobParams { |
313 | 367 | fn from(value: CreateJobInvokeCall) -> Self { |
314 | 368 | CreateJobParams { |
@@ -507,6 +561,69 @@ fn decode_b256_word(calldata: &[u8], at: usize) -> Result<B256, ActorError> { |
507 | 561 | Ok(B256(out)) |
508 | 562 | } |
509 | 563 |
|
| 564 | +fn decode_address_word(calldata: &[u8], at: usize) -> Result<H160, ActorError> { |
| 565 | + let end = at + 32; |
| 566 | + if end > calldata.len() { |
| 567 | + return Err(actor_error!( |
| 568 | + illegal_argument, |
| 569 | + "invalid call: malformed address word" |
| 570 | + )); |
| 571 | + } |
| 572 | + let word = &calldata[at..end]; |
| 573 | + if word[..12].iter().any(|b| *b != 0) { |
| 574 | + return Err(actor_error!( |
| 575 | + illegal_argument, |
| 576 | + "invalid call: malformed address" |
| 577 | + )); |
| 578 | + } |
| 579 | + Ok(H160::from_slice(&word[12..32])) |
| 580 | +} |
| 581 | + |
| 582 | +fn decode_u8_word(calldata: &[u8], at: usize) -> Result<u8, ActorError> { |
| 583 | + let end = at + 32; |
| 584 | + if end > calldata.len() { |
| 585 | + return Err(actor_error!(illegal_argument, "invalid call: malformed word")); |
| 586 | + } |
| 587 | + let word = &calldata[at..end]; |
| 588 | + if word[..31].iter().any(|b| *b != 0) { |
| 589 | + return Err(actor_error!( |
| 590 | + illegal_argument, |
| 591 | + "invalid call: uint8 value too large" |
| 592 | + )); |
| 593 | + } |
| 594 | + Ok(word[31]) |
| 595 | +} |
| 596 | + |
| 597 | +fn decode_u128_word(calldata: &[u8], at: usize) -> Result<u128, ActorError> { |
| 598 | + let end = at + 32; |
| 599 | + if end > calldata.len() { |
| 600 | + return Err(actor_error!(illegal_argument, "invalid call: malformed word")); |
| 601 | + } |
| 602 | + let word = &calldata[at..end]; |
| 603 | + if word[..16].iter().any(|b| *b != 0) { |
| 604 | + return Err(actor_error!( |
| 605 | + illegal_argument, |
| 606 | + "invalid call: uint128 value too large" |
| 607 | + )); |
| 608 | + } |
| 609 | + let mut n = [0u8; 16]; |
| 610 | + n.copy_from_slice(&word[16..32]); |
| 611 | + Ok(u128::from_be_bytes(n)) |
| 612 | +} |
| 613 | + |
| 614 | +fn solidity_enum_to_blob_status(value: u8) -> Result<BlobStatus, ActorError> { |
| 615 | + match value { |
| 616 | + 0 => Ok(BlobStatus::Added), |
| 617 | + 1 => Ok(BlobStatus::Pending), |
| 618 | + 2 => Ok(BlobStatus::Resolved), |
| 619 | + 3 => Ok(BlobStatus::Failed), |
| 620 | + _ => Err(actor_error!( |
| 621 | + illegal_argument, |
| 622 | + format!("invalid BlobStatus enum value: {}", value) |
| 623 | + )), |
| 624 | + } |
| 625 | +} |
| 626 | + |
510 | 627 | fn abi_word_from_usize(value: usize) -> [u8; 32] { |
511 | 628 | let mut word = [0u8; 32]; |
512 | 629 | word[24..32].copy_from_slice(&(value as u64).to_be_bytes()); |
|
0 commit comments