@@ -8,6 +8,7 @@ use fendermint_actor_blobs_shared::{
88 TrimBlobExpiriesParams ,
99 } ,
1010 bytes:: B256 ,
11+ operators:: OperatorInfo ,
1112 GetStatsReturn ,
1213} ;
1314use fil_actors_runtime:: { actor_error, runtime:: Runtime , ActorError } ;
@@ -112,6 +113,247 @@ pub fn parse_input(input: &ipc_storage_actor_sdk::evm::InputData) -> Result<Call
112113 . map_err ( |e| actor_error ! ( illegal_argument, format!( "invalid call: {}" , e) ) )
113114}
114115
116+ pub const REGISTER_NODE_OPERATOR_SELECTOR : [ u8 ; 4 ] = [ 0x71 , 0x3b , 0x10 , 0xcf ] ;
117+ pub const GET_OPERATOR_INFO_SELECTOR : [ u8 ; 4 ] = [ 0x27 , 0xd9 , 0xab , 0x5d ] ;
118+ pub const GET_ACTIVE_OPERATORS_SELECTOR : [ u8 ; 4 ] = [ 0x64 , 0xbd , 0xc6 , 0x7e ] ;
119+
120+ pub struct RegisterNodeOperatorInvokeCall {
121+ pub bls_pubkey : Vec < u8 > ,
122+ pub rpc_url : String ,
123+ }
124+
125+ pub struct GetOperatorInfoInvokeCall {
126+ pub address : Address ,
127+ }
128+
129+ pub fn is_register_node_operator_call ( input : & ipc_storage_actor_sdk:: evm:: InputData ) -> bool {
130+ input. selector ( ) == REGISTER_NODE_OPERATOR_SELECTOR
131+ }
132+
133+ pub fn is_get_operator_info_call ( input : & ipc_storage_actor_sdk:: evm:: InputData ) -> bool {
134+ input. selector ( ) == GET_OPERATOR_INFO_SELECTOR
135+ }
136+
137+ pub fn is_get_active_operators_call ( input : & ipc_storage_actor_sdk:: evm:: InputData ) -> bool {
138+ input. selector ( ) == GET_ACTIVE_OPERATORS_SELECTOR
139+ }
140+
141+ pub fn parse_register_node_operator_input (
142+ input : & ipc_storage_actor_sdk:: evm:: InputData ,
143+ ) -> Result < RegisterNodeOperatorInvokeCall , ActorError > {
144+ let calldata = input. calldata ( ) ;
145+ if calldata. len ( ) < 64 {
146+ return Err ( actor_error ! ( illegal_argument, "invalid call: input too short" ) ) ;
147+ }
148+
149+ let bls_offset = decode_offset ( calldata, 0 ) ?;
150+ let rpc_offset = decode_offset ( calldata, 32 ) ?;
151+
152+ let bls_pubkey = decode_dynamic_bytes ( calldata, bls_offset) ?;
153+ let rpc_bytes = decode_dynamic_bytes ( calldata, rpc_offset) ?;
154+ let rpc_url = String :: from_utf8 ( rpc_bytes)
155+ . map_err ( |e| actor_error ! ( illegal_argument, format!( "invalid call: bad UTF-8: {}" , e) ) ) ?;
156+
157+ Ok ( RegisterNodeOperatorInvokeCall {
158+ bls_pubkey,
159+ rpc_url,
160+ } )
161+ }
162+
163+ pub fn parse_get_operator_info_input (
164+ input : & ipc_storage_actor_sdk:: evm:: InputData ,
165+ ) -> Result < GetOperatorInfoInvokeCall , ActorError > {
166+ let calldata = input. calldata ( ) ;
167+ if calldata. len ( ) < 32 {
168+ return Err ( actor_error ! ( illegal_argument, "invalid call: input too short" ) ) ;
169+ }
170+ let word = & calldata[ 0 ..32 ] ;
171+ if word[ ..12 ] . iter ( ) . any ( |b| * b != 0 ) {
172+ return Err ( actor_error ! (
173+ illegal_argument,
174+ "invalid call: malformed address"
175+ ) ) ;
176+ }
177+ let address: Address = H160 :: from_slice ( & word[ 12 ..32 ] ) . into ( ) ;
178+ Ok ( GetOperatorInfoInvokeCall { address } )
179+ }
180+
181+ pub fn encode_get_operator_info_output ( info : Option < OperatorInfo > ) -> Result < Vec < u8 > , ActorError > {
182+ let ( bls_pubkey, rpc_url, active) = if let Some ( info) = info {
183+ ( info. bls_pubkey , info. rpc_url . into_bytes ( ) , info. active )
184+ } else {
185+ ( Vec :: new ( ) , Vec :: new ( ) , false )
186+ } ;
187+
188+ let bls_section = encode_dynamic_bytes ( & bls_pubkey) ;
189+ let rpc_section = encode_dynamic_bytes ( & rpc_url) ;
190+
191+ let head_size = 32 * 3 ;
192+ let bls_offset = head_size;
193+ let rpc_offset = head_size + bls_section. len ( ) ;
194+
195+ let mut output = Vec :: with_capacity ( head_size + bls_section. len ( ) + rpc_section. len ( ) ) ;
196+ output. extend_from_slice ( & abi_word_from_usize ( bls_offset) ) ;
197+ output. extend_from_slice ( & abi_word_from_usize ( rpc_offset) ) ;
198+ output. extend_from_slice ( & abi_word_from_bool ( active) ) ;
199+ output. extend_from_slice ( & bls_section) ;
200+ output. extend_from_slice ( & rpc_section) ;
201+ Ok ( output)
202+ }
203+
204+ pub fn encode_get_active_operators_output ( operators : Vec < Address > ) -> Result < Vec < u8 > , ActorError > {
205+ let mut operators_section = Vec :: with_capacity ( 32 + operators. len ( ) * 32 ) ;
206+ operators_section. extend_from_slice ( & abi_word_from_usize ( operators. len ( ) ) ) ;
207+ for operator in operators {
208+ let h160 = H160 :: try_from ( operator) . map_err ( |e| {
209+ actor_error ! (
210+ illegal_argument,
211+ format!( "failed to encode operator address: {}" , e)
212+ )
213+ } ) ?;
214+ operators_section. extend_from_slice ( & abi_word_from_address ( h160) ) ;
215+ }
216+
217+ let mut output = Vec :: with_capacity ( 32 + operators_section. len ( ) ) ;
218+ output. extend_from_slice ( & abi_word_from_usize ( 32 ) ) ;
219+ output. extend_from_slice ( & operators_section) ;
220+ Ok ( output)
221+ }
222+
223+ fn decode_offset ( calldata : & [ u8 ] , at : usize ) -> Result < usize , ActorError > {
224+ let end = at + 32 ;
225+ if end > calldata. len ( ) {
226+ return Err ( actor_error ! ( illegal_argument, "invalid call: malformed offset" ) ) ;
227+ }
228+ let word = & calldata[ at..end] ;
229+ if word[ ..24 ] . iter ( ) . any ( |b| * b != 0 ) {
230+ return Err ( actor_error ! (
231+ illegal_argument,
232+ "invalid call: offset too large"
233+ ) ) ;
234+ }
235+ let mut n = [ 0u8 ; 8 ] ;
236+ n. copy_from_slice ( & word[ 24 ..32 ] ) ;
237+ Ok ( u64:: from_be_bytes ( n) as usize )
238+ }
239+
240+ fn decode_dynamic_bytes ( calldata : & [ u8 ] , offset : usize ) -> Result < Vec < u8 > , ActorError > {
241+ if offset + 32 > calldata. len ( ) {
242+ return Err ( actor_error ! (
243+ illegal_argument,
244+ "invalid call: dynamic offset out of bounds"
245+ ) ) ;
246+ }
247+
248+ let len = decode_offset ( calldata, offset) ?;
249+ let start = offset + 32 ;
250+ let end = start
251+ . checked_add ( len)
252+ . ok_or_else ( || actor_error ! ( illegal_argument, "invalid call: overflow" ) ) ?;
253+
254+ if end > calldata. len ( ) {
255+ return Err ( actor_error ! (
256+ illegal_argument,
257+ "invalid call: dynamic value out of bounds"
258+ ) ) ;
259+ }
260+
261+ Ok ( calldata[ start..end] . to_vec ( ) )
262+ }
263+
264+ fn abi_word_from_usize ( value : usize ) -> [ u8 ; 32 ] {
265+ let mut word = [ 0u8 ; 32 ] ;
266+ word[ 24 ..32 ] . copy_from_slice ( & ( value as u64 ) . to_be_bytes ( ) ) ;
267+ word
268+ }
269+
270+ fn abi_word_from_bool ( value : bool ) -> [ u8 ; 32 ] {
271+ let mut word = [ 0u8 ; 32 ] ;
272+ word[ 31 ] = u8:: from ( value) ;
273+ word
274+ }
275+
276+ fn abi_word_from_address ( value : H160 ) -> [ u8 ; 32 ] {
277+ let mut word = [ 0u8 ; 32 ] ;
278+ word[ 12 ..32 ] . copy_from_slice ( & value. to_fixed_bytes ( ) ) ;
279+ word
280+ }
281+
282+ fn encode_dynamic_bytes ( value : & [ u8 ] ) -> Vec < u8 > {
283+ let mut out = Vec :: with_capacity ( 32 + padded_32_len ( value. len ( ) ) ) ;
284+ out. extend_from_slice ( & abi_word_from_usize ( value. len ( ) ) ) ;
285+ out. extend_from_slice ( value) ;
286+ let padding = padded_32_len ( value. len ( ) ) - value. len ( ) ;
287+ out. extend ( std:: iter:: repeat ( 0u8 ) . take ( padding) ) ;
288+ out
289+ }
290+
291+ fn padded_32_len ( size : usize ) -> usize {
292+ if size == 0 { 0 } else { size. div_ceil ( 32 ) * 32 }
293+ }
294+
295+ #[ cfg( test) ]
296+ mod tests {
297+ use super :: * ;
298+
299+ fn address_word ( bytes20 : [ u8 ; 20 ] ) -> [ u8 ; 32 ] {
300+ let mut word = [ 0u8 ; 32 ] ;
301+ word[ 12 ..32 ] . copy_from_slice ( & bytes20) ;
302+ word
303+ }
304+
305+ #[ test]
306+ fn parses_get_operator_info_input_address ( ) {
307+ let addr = [ 0x11u8 ; 20 ] ;
308+ let mut input = Vec :: new ( ) ;
309+ input. extend_from_slice ( & GET_OPERATOR_INFO_SELECTOR ) ;
310+ input. extend_from_slice ( & address_word ( addr) ) ;
311+ let input =
312+ ipc_storage_actor_sdk:: evm:: InputData :: try_from ( ipc_storage_actor_sdk:: evm:: InvokeContractParams {
313+ input_data : input,
314+ } )
315+ . expect ( "valid input" ) ;
316+
317+ let parsed = parse_get_operator_info_input ( & input) . expect ( "parse succeeds" ) ;
318+ let expected = Address :: new_delegated ( 10 , & addr) . expect ( "delegated" ) ;
319+ assert_eq ! ( parsed. address, expected) ;
320+ }
321+
322+ #[ test]
323+ fn encodes_get_active_operators_output_as_address_array ( ) {
324+ let id = Address :: new_id ( 66 ) ;
325+ let delegated = Address :: new_delegated ( 10 , & [ 0x22 ; 20 ] ) . expect ( "delegated" ) ;
326+
327+ let encoded = encode_get_active_operators_output ( vec ! [ id, delegated] ) . expect ( "encode" ) ;
328+
329+ assert_eq ! ( & encoded[ 0 ..32 ] , & abi_word_from_usize( 32 ) ) ;
330+ assert_eq ! ( & encoded[ 32 ..64 ] , & abi_word_from_usize( 2 ) ) ;
331+
332+ let id_h160 = H160 :: try_from ( id) . expect ( "id to h160" ) ;
333+ let delegated_h160 = H160 :: try_from ( delegated) . expect ( "delegated to h160" ) ;
334+ assert_eq ! ( & encoded[ 64 ..96 ] , & abi_word_from_address( id_h160) ) ;
335+ assert_eq ! ( & encoded[ 96 ..128 ] , & abi_word_from_address( delegated_h160) ) ;
336+ }
337+
338+ #[ test]
339+ fn encodes_get_operator_info_output_tuple ( ) {
340+ let info = OperatorInfo {
341+ bls_pubkey : vec ! [ 1 , 2 , 3 , 4 ] ,
342+ rpc_url : "http://127.0.0.1:8081" . to_string ( ) ,
343+ active : true ,
344+ } ;
345+ let encoded = encode_get_operator_info_output ( Some ( info) ) . expect ( "encode" ) ;
346+
347+ assert_eq ! ( & encoded[ 0 ..32 ] , & abi_word_from_usize( 96 ) ) ;
348+ let bls_section_len = 32 + 32 ; // len + padded data for 4 bytes
349+ assert_eq ! (
350+ & encoded[ 32 ..64 ] ,
351+ & abi_word_from_usize( 96 + bls_section_len)
352+ ) ;
353+ assert_eq ! ( & encoded[ 64 ..96 ] , & abi_word_from_bool( true ) ) ;
354+ }
355+ }
356+
115357fn blob_status_as_solidity_enum ( blob_status : BlobStatus ) -> u8 {
116358 match blob_status {
117359 BlobStatus :: Added => 0 ,
0 commit comments