123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778(*****************************************************************************)(* *)(* Open Source License *)(* Copyright (c) 2022 Marigold <contact@marigold.dev> *)(* Copyright (c) 2022 Nomadic Labs <contact@nomadic-labs.com> *)(* Copyright (c) 2022 Oxhead Alpha <info@oxheadalpha.com> *)(* *)(* Permission is hereby granted, free of charge, to any person obtaining a *)(* copy of this software and associated documentation files (the "Software"),*)(* to deal in the Software without restriction, including without limitation *)(* the rights to use, copy, modify, merge, publish, distribute, sublicense, *)(* and/or sell copies of the Software, and to permit persons to whom the *)(* Software is furnished to do so, subject to the following conditions: *)(* *)(* The above copyright notice and this permission notice shall be included *)(* in all copies or substantial portions of the Software. *)(* *)(* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR*)(* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *)(* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *)(* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER*)(* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING *)(* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER *)(* DEALINGS IN THE SOFTWARE. *)(* *)(*****************************************************************************)openAlpha_contextopenTx_rollup_l2_context_sigopenTx_rollup_l2_batchtypeerror+=|Counter_mismatchof{account:Tx_rollup_l2_address.t;expected:int64;provided:int64;}|Incorrect_aggregated_signature|Unallocated_metadataofint32|Multiple_operations_for_signerofBls.Public_key.t|Invalid_transaction_encoding|Invalid_batch_encoding|Unexpectedly_indexed_ticket|Missing_ticketofTicket_hash.t|Unknown_addressofTx_rollup_l2_address.t|Invalid_self_transfer|Invalid_zero_transfer|Maximum_withdraws_per_message_exceededof{current:int;maximum:int}let()=letopenData_encodingin(* Counter mismatch *)register_error_kind`Branch~id:"tx_rollup_operation_counter_mismatch"~title:"Operation counter mismatch"~description:"A transaction rollup operation has been submitted with an incorrect \
counter"(obj3(req"account"Tx_rollup_l2_address.encoding)(req"expected"int64)(req"provided"int64))(function|Counter_mismatch{account;expected;provided}->Some(account,expected,provided)|_->None)(fun(account,expected,provided)->Counter_mismatch{account;expected;provided});(* Incorrect aggregated signature *)register_error_kind`Permanent~id:"tx_rollup_incorrect_aggregated_signature"~title:"Incorrect aggregated signature"~description:"The aggregated signature is incorrect"empty(functionIncorrect_aggregated_signature->Some()|_->None)(function()->Incorrect_aggregated_signature);(* Unallocated metadata *)register_error_kind`Branch~id:"tx_rollup_unknown_metadata"~title:"Unknown metadata"~description:"A public key index was provided but the account information for this \
index is not present in the context."(obj1(req"idx"int32))(functionUnallocated_metadatai->Somei|_->None)(functioni->Unallocated_metadatai);(* Invalid transaction *)register_error_kind`Permanent~id:"tx_rollup_invalid_transaction"~title:"Invalid transaction"~description:"The signer signed multiple operations in the same transaction. He must \
gather all the contents in a single operation"(obj1(req"pk"Bls.Public_key.encoding))(functionMultiple_operations_for_signeridx->Someidx|_->None)(functionidx->Multiple_operations_for_signeridx);(* Invalid transaction encoding *)register_error_kind`Permanent~id:"tx_rollup_invalid_transaction_encoding"~title:"Invalid transaction encoding"~description:"The transaction could not be decoded from bytes"empty(functionInvalid_transaction_encoding->Some()|_->None)(function()->Invalid_transaction_encoding);(* Invalid batch encoding *)register_error_kind`Permanent~id:"tx_rollup_invalid_batch_encoding"~title:"Invalid batch encoding"~description:"The batch could not be decoded from bytes"empty(functionInvalid_batch_encoding->Some()|_->None)(function()->Invalid_batch_encoding);(* Unexpectedly indexed ticket *)register_error_kind`Permanent~id:"tx_rollup_unexpectedly_indexed_ticket"~title:"Unexpected indexed ticket in deposit or transfer"~description:"Tickets in layer2-to-layer1 transfers must be referenced by value."empty(functionUnexpectedly_indexed_ticket->Some()|_->None)(function()->Unexpectedly_indexed_ticket);(* Missing ticket *)register_error_kind`Temporary~id:"tx_rollup_missing_ticket"~title:"Attempted to withdraw from a ticket missing in the rollup"~description:"A withdrawal must reference a ticket that already exists in the rollup."(obj1(req"ticket_hash"Ticket_hash.encoding))(functionMissing_ticketticket_hash->Someticket_hash|_->None)(functionticket_hash->Missing_ticketticket_hash);(* Unknown address *)register_error_kind`Temporary~id:"tx_rollup_unknown_address"~title:"Attempted to sign a transfer with an unknown address"~description:"The address must exist in the context when signing a transfer with it."(obj1(req"address"Tx_rollup_l2_address.encoding))(functionUnknown_addressaddr->Someaddr|_->None)(functionaddr->Unknown_addressaddr);(* Invalid self transfer *)register_error_kind`Temporary~id:"tx_rollup_invalid_self_transfer"~title:"Attempted to transfer ticket to self"~description:"The index for the destination is the same as the sender"empty(functionInvalid_self_transfer->Some()|_->None)(function()->Invalid_self_transfer);(* Invalid zero transfer *)register_error_kind`Permanent~id:"tx_rollup_invalid_zero_transfer"~title:"Attempted to transfer zero ticket"~description:"A transfer's amount must be greater than zero."empty(functionInvalid_zero_transfer->Some()|_->None)(function()->Invalid_zero_transfer);(* Maximum_withdraws_per_message_exceeded *)register_error_kind`Permanent~id:"tx_rollup_maximum_withdraws_per_message_exceeded"~title:"Maximum tx-rollup withdraws per message exceeded"~description:"The maximum number of withdraws allowed per tx-rollup message exceeded"(obj2(req"current"int31)(req"limit"int31))(function|Maximum_withdraws_per_message_exceeded{current;maximum}->Some(current,maximum)|_->None)(fun(current,maximum)->Maximum_withdraws_per_message_exceeded{current;maximum})typeindexes={address_indexes:(Tx_rollup_l2_address.t*Tx_rollup_l2_address.Indexable.index)list;ticket_indexes:(Ticket_hash.t*Ticket_indexable.index)list;}letencoding_indexes:indexesData_encoding.t=letopenData_encodinginconv(fun{address_indexes;ticket_indexes}->(address_indexes,ticket_indexes))(fun(address_indexes,ticket_indexes)->{address_indexes;ticket_indexes})@@obj2(req"address_indexes"(list(tup2Tx_rollup_l2_address.encodingTx_rollup_l2_address.Indexable.index_encoding)))(req"ticket_indexes"(list(tup2Ticket_hash.encodingTicket_indexable.index_encoding)))moduleMessage_result=structtypetransaction_result=|Transaction_success|Transaction_failureof{index:int;reason:error}typedeposit_result=Deposit_successofindexes|Deposit_failureoferrorletencoding_transaction_result=letopenData_encodinginunion[(letkind="transaction_success"incase~title:kind(Tag0)(constantkind)(functionTransaction_success->Some()|_->None)(fun()->Transaction_success));(letkind="transaction_failure"incase~title:kind(Tag1)(obj1(reqkind(obj2(req"transaction_index"Data_encoding.int31)(req"reason"Error_monad.error_encoding))))(function|Transaction_failure{index;reason}->Some(index,reason)|_->None)(fun(index,reason)->Transaction_failure{index;reason}));]letencoding_deposit_result=letopenData_encodinginunion[(letkind="deposit_success"incase~title:kind(Tag0)(obj1(reqkindencoding_indexes))(functionDeposit_successindexes->Someindexes|_->None)(funindexes->Deposit_successindexes));(letkind="deposit_failure"incase~title:kind(Tag1)(obj1(reqkind(obj1(req"reason"Error_monad.error_encoding))))(functionDeposit_failurereason->Somereason|_->None)(funreason->Deposit_failurereason));]moduleBatch_V1=structtypet=|Batch_resultof{results:((Indexable.index_only,Indexable.unknown)V1.transaction*transaction_result)list;indexes:indexes;}letencoding=letopenData_encodinginconv(fun(Batch_result{results;indexes})->(results,indexes))(fun(results,indexes)->Batch_result{results;indexes})(obj2(req"results"@@list(Data_encoding.tup2(Compact.make~tag_size:`Uint8V1.compact_transaction_signer_index)encoding_transaction_result))(req"allocated_indexes"encoding_indexes))endtypemessage_result=|Deposit_resultofdeposit_result|Batch_V1_resultofBatch_V1.tletmessage_result_encoding=letopenData_encodinginunion[(letkind="deposit_result"incase~title:kind(Tag0)(obj1(reqkindencoding_deposit_result))(functionDeposit_resultresult->Someresult|_->None)(funresult->Deposit_resultresult));(letkind="batch_v1_result"incase~title:kind(Tag1)(obj1(reqkindBatch_V1.encoding))(functionBatch_V1_resultresult->Someresult|_->None)(funresult->Batch_V1_resultresult));]typet=message_result*Tx_rollup_withdraw.tlistletencoding=Data_encoding.(tup2message_result_encoding(listTx_rollup_withdraw.encoding))endtypeparameters={(* Maximum number of allowed L2-to-L1 withdraws per batch *)tx_rollup_max_withdrawals_per_batch:int;}moduleMake(Context:CONTEXT)=structopenContextopenSyntaxopenMessage_resulttypectxt=Context.t(** {3. Indexes. } *)(** The application of a message can (and is supposed to) use and
create several indexes during the application of a {Tx_rollup_message.t}.
*)letindexget_or_associate_indexadd_indexctxtindexesindexable=letopenIndexableinmatchdestructindexablewith|Rightv->(let+ctxt,created,idx=get_or_associate_indexctxtvinmatchcreatedwith|`Existed->(ctxt,indexes,idx)|`Created->(ctxt,add_indexindexes(v,idx),idx))|Lefti->return(ctxt,indexes,i)letaddress_indexctxtindexesindexable=letget_or_associate_index=Address_index.get_or_associate_indexinletadd_indexindexesx={indexeswithaddress_indexes=x::indexes.address_indexes}inindexget_or_associate_indexadd_indexctxtindexesindexableletticket_indexctxtindexesindexable=letget_or_associate_index=Ticket_index.get_or_associate_indexinletadd_indexindexesx={indexeswithticket_indexes=x::indexes.ticket_indexes}inindexget_or_associate_indexadd_indexctxtindexesindexableletaddress_of_signer_index:Signer_indexable.index->Tx_rollup_l2_address.Indexable.index=funidx->Indexable.(index_exn(to_int32idx))letsigner_of_address_index:Tx_rollup_l2_address.Indexable.index->Signer_indexable.index=funidx->Indexable.(index_exn(to_int32idx))letempty_indexes={address_indexes=[];ticket_indexes=[]}letassert_non_zero_quantityqty=fail_whenTx_rollup_l2_qty.(qty=zero)Invalid_zero_transfer(** {2. Counter } *)(** [get_metadata ctxt idx] returns the metadata associated to [idx] in
[ctxt]. It must have an associated metadata in the context, otherwise,
something went wrong in {!check_signature}. *)letget_metadata:ctxt->address_index->metadatam=functxtidx->letopenAddress_metadatainlet*metadata=getctxtidxinmatchmetadatawith|None->fail(Unallocated_metadata(Indexable.to_int32idx))|Somemetadata->returnmetadata(** [get_metadata_signer] gets the metadata for a signer using {!get_metadata}.
It transforms a signer index to an address one. *)letget_metadata_signer:ctxt->Signer_indexable.index->metadatam=functxtsigner_idx->get_metadatactxt(address_of_signer_indexsigner_idx)(** [transfers ctxt source_idx destination_idx tidx amount] transfers [amount]
from [source_idx] to [destination_idx] of [tidx]. *)lettransferctxtsource_idxdestination_idxtidxamount=let*()=fail_unlessCompare.Int.(Indexable.compare_indexessource_idxdestination_idx<>0)Invalid_self_transferinlet*()=assert_non_zero_quantityamountinlet*ctxt=Ticket_ledger.spendctxttidxsource_idxamountinTicket_ledger.creditctxttidxdestination_idxamount(** [deposit ctxt aidx tidx amount] credits [amount] of [tidx] to [aidx].
They are deposited from the layer1 and created in the layer2 context, but,
we only handle the creation part (i.e. in the layer2) in this module. *)letdepositctxtaidxtidxamount=Ticket_ledger.creditctxttidxaidxamountmoduleBatch_V1=structopenTx_rollup_l2_batch.V1(** [operation_with_signer_index ctxt indexes op] takes an operation
and performs multiple get/sets on the context to return an operation
where the signer is replaced by its index.
It performs on the [ctxt]:
{ul {li If the signer is an index, we read the public key from the
[ctxt].}
{li If the signer is a public key, we associate a new index to
it in the [ctxt]. The public key is also added to the metadata
if not already present.}}
{b Note:} If the context already contains all the required information,
we only read from it. *)letoperation_with_signer_index:ctxt->indexes->('signer,'content)operation->(ctxt*indexes*(Indexable.index_only,'content)operation*Bls.Public_key.t)m=functxtindexesop->let*ctxt,indexes,pk,idx=matchIndexable.destructop.signerwith|Leftsigner_index->(* Get the public key from the index. *)letaddress_index=address_of_signer_indexsigner_indexinlet*metadata=get_metadatactxtaddress_indexinletpk=metadata.public_keyinreturn(ctxt,indexes,pk,address_index)|Right(Bls_pksigner_pk)->((* Initialize the ctxt with public_key if it's necessary. *)letaddr=Bls.Public_key.hashsigner_pkinlet*ctxt,created,idx=Address_index.get_or_associate_indexctxtaddrin(* If the address is created, we add it to [indexes]. *)matchcreatedwith|`Existed->(* If the public key existed in the context, it should not
be added in [indexes]. However, the metadata might not
have been initialized for the public key. Especially during
a deposit, the deposit destination is a layer2 address and
it contains no information about the public key.
*)let*ctxt=let*metadata=Address_metadata.getctxtidxinmatchmetadatawith|Some_->(* If the metadata exists, then the public key necessarily
exists, we do not need to change the context. *)returnctxt|None->Address_metadata.init_with_public_keyctxtidxsigner_pkinreturn(ctxt,indexes,signer_pk,idx)|`Created->(* If the index is created, we need to add to indexes and
initialize the metadata. *)letindexes={indexeswithaddress_indexes=(addr,idx)::indexes.address_indexes;}inlet*ctxt=Address_metadata.init_with_public_keyctxtidxsigner_pkinreturn(ctxt,indexes,signer_pk,idx))|Right(L2_addrsigner_addr)->((* In order to get the public key associated to [signer_addr], there
needs to be both an index associated to it, and a metadata for this
index. *)let*idx=Address_index.getctxtsigner_addrinmatchidxwith|None->fail(Unknown_addresssigner_addr)|Someidx->let*metadata=get_metadatactxtidxinreturn(ctxt,indexes,metadata.public_key,idx))inletop:(Indexable.index_only,'content)operation={opwithsigner=signer_of_address_indexidx}inreturn(ctxt,indexes,op,pk)(** [check_transaction ctxt indexes transmitted transaction] performs an
*active* check of an operation.
We consider this as an *active* check because the function is likely to
write in the [ctxt], since it replaces the signer's public key
(if provided) by its index in {!operation_with_signer_index}.
Outside of the active preprocessing, we check that a signer signs
at most one operation in the [transaction].
It also associates the signer to the bytes representation of a
transaction in [transmitted], which is used to check the aggregated
signature.
*)letcheck_transactionctxtindexestransmittedtransaction=let*buf=matchData_encoding.Binary.to_bytes_opt(Data_encoding.Compact.make~tag_size:`Uint8compact_transaction)transactionwith|Somebuf->returnbuf|None->failInvalid_transaction_encodinginlet*ctxt,indexes,transmitted,_,rev_ops=list_fold_left_m(fun(ctxt,indexes,transmitted,signers,ops)op->let*ctxt,indexes,op,pk=operation_with_signer_indexctxtindexesopinifList.mem~equal:Bls.Public_key.equalpksignersthenfail(Multiple_operations_for_signerpk)elsereturn(ctxt,indexes,(pk,buf)::transmitted,pk::signers,op::ops))(ctxt,indexes,transmitted,[],[])transactioninreturn(ctxt,indexes,transmitted,List.revrev_ops)letcheck_signature:ctxt->('signer,'content)t->(ctxt*indexes*(Indexable.index_only,'content)t)m=functxt({contents=transactions;aggregated_signature}asbatch)->let*ctxt,indexes,transmitted,rev_new_transactions=list_fold_left_m(fun(ctxt,indexes,transmitted,new_transactions)transaction->(* To check the signature, we need the list of [buf] each signer
signed. That is, the [buf] is the binary encoding of the
[transaction]. *)let*ctxt,indexes,transmitted,transaction=check_transactionctxtindexestransmittedtransactioninreturn(ctxt,indexes,transmitted,transaction::new_transactions))(ctxt,empty_indexes,[],[])transactionsin(* Once we collected the public keys for each signer and the buffers
they signed, we can check the signature. *)let*b=bls_verifytransmittedaggregated_signatureinlet*()=fail_unlessbIncorrect_aggregated_signatureinletbatch={batchwithcontents=List.revrev_new_transactions}inreturn(ctxt,indexes,batch)(** {2. Apply } *)(** [apply_operation_content ctxt source content] performs the transfer
on the [ctxt]. The validity of the transfer is checked in
the context itself, e.g. for an invalid balance.
It returns the potential created indexes:
{ul {li The destination address index.}
{li The ticket exchanged index.}}
*)letapply_operation_content:ctxt->indexes->Signer_indexable.index->'contentoperation_content->(ctxt*indexes*Tx_rollup_withdraw.toption)m=functxtindexessource_idxop_content->matchop_contentwith|Withdraw{destination=claimer;ticket_hash;qty=amount}->(* To withdraw, the ticket must already exist in the
rollup and be indexed (the ticket must have already been
assigned an index in the content: otherwise the ticket has
not been seen before and we can't withdraw from it). *)let*tidx_opt=Ticket_index.getctxtticket_hashinlet*?tidx=Option.value_e~error:(Missing_ticketticket_hash)tidx_optinletsource_idx=address_of_signer_indexsource_idxin(* spend the ticket -- this is responsible for checking that
the source has the required balance *)let*()=assert_non_zero_quantityamountinlet*ctxt=Ticket_ledger.spendctxttidxsource_idxamountinletwithdrawal=Tx_rollup_withdraw.{claimer;ticket_hash;amount}inreturn(ctxt,indexes,Somewithdrawal)|Transfer{destination;ticket_hash;qty}->let*ctxt,indexes,dest_idx=address_indexctxtindexesdestinationinlet*ctxt,indexes,tidx=ticket_indexctxtindexesticket_hashinletsource_idx=address_of_signer_indexsource_idxinlet*ctxt=transferctxtsource_idxdest_idxtidxqtyinreturn(ctxt,indexes,None)(** [check_counter ctxt signer counter] asserts that the provided [counter] is the
successor of the one associated to the [signer] in the [ctxt]. *)letcheck_counter:ctxt->Indexable.index_onlySigner_indexable.t->int64->unitm=functxtsignercounter->let*metadata=get_metadata_signerctxtsignerinfail_unlessCompare.Int64.(counter=Int64.succmetadata.counter)(Counter_mismatch{account=Bls.Public_key.hashmetadata.public_key;expected=Int64.succmetadata.counter;provided=counter;})(** [apply_operation ctxt indexes op] checks the counter validity for the [op.signer] with
{!check_counter}, and then calls {!apply_operation_content} for each content in [op]. *)letapply_operation:ctxt->indexes->(Indexable.index_only,Indexable.unknown)operation->(ctxt*indexes*Tx_rollup_withdraw.tlist)m=functxtindexes{signer;counter;contents}->(* Before applying any operation, we check the counter *)let*()=check_counterctxtsignercounterinlet*ctxt,indexes,rev_withdrawals=list_fold_left_m(fun(ctxt,indexes,withdrawals)content->let*ctxt,indexes,withdrawal_opt=apply_operation_contentctxtindexessignercontentinreturn(ctxt,indexes,Option.to_listwithdrawal_opt@withdrawals))(ctxt,indexes,[])contentsinreturn(ctxt,indexes,rev_withdrawals|>List.rev)(** [apply_transaction ctxt indexes transaction] applies each operation in
the [transaction]. It returns a {!transaction_result}, i.e. either
every operation in the [transaction] succedeed and the [ctxt] is
modified, or the [transaction] is a failure and the context
is left untouched.
*)letapply_transaction:ctxt->indexes->(Indexable.index_only,Indexable.unknown)transaction->(ctxt*indexes*transaction_result*Tx_rollup_withdraw.tlist)m=funinitial_ctxtinitial_indexestransaction->letrecfold(ctxt,prev_indexes,withdrawals)indexops=matchopswith|[]->return(ctxt,prev_indexes,Transaction_success,withdrawals)|op::rst->let*ctxt,indexes,status,withdrawals=catch(apply_operationctxtprev_indexesop)(fun(ctxt,indexes,op_withdrawals)->fold(ctxt,indexes,withdrawals@op_withdrawals)(index+1)rst)(funreason->return(initial_ctxt,initial_indexes,Transaction_failure{index;reason},[]))inreturn(ctxt,indexes,status,withdrawals)infold(initial_ctxt,initial_indexes,[])0transaction(** [update_counters ctxt status transaction] updates the counters for
the signers of operations in [transaction]. If the [transaction]
failed because of a [Counter_mismatch] the counters are left
untouched.
*)letupdate_countersctxtstatustransaction=matchstatuswith|Transaction_failure{reason=Counter_mismatch_;_}->returnctxt|Transaction_failure_|Transaction_success->list_fold_left_m(functxt(op:(Indexable.index_only,_)operation)->Address_metadata.incr_counterctxt@@address_of_signer_indexop.signer)ctxttransactionletapply_batch:ctxt->parameters->(Indexable.unknown,Indexable.unknown)t->(ctxt*Message_result.Batch_V1.t*Tx_rollup_withdraw.tlist)m=functxtparametersbatch->let*ctxt,indexes,batch=check_signaturectxtbatchinlet{contents;_}=batchinlet*ctxt,indexes,rev_results,withdrawals=list_fold_left_m(fun(prev_ctxt,prev_indexes,results,withdrawals)transaction->let*new_ctxt,new_indexes,status,transaction_withdrawals=apply_transactionprev_ctxtprev_indexestransactioninlet*new_ctxt=update_countersnew_ctxtstatustransactioninreturn(new_ctxt,new_indexes,(transaction,status)::results,withdrawals@transaction_withdrawals))(ctxt,indexes,[],[])contentsinletlimit=parameters.tx_rollup_max_withdrawals_per_batchinifCompare.List_length_with.(withdrawals>limit)thenfail(Maximum_withdraws_per_message_exceeded{current=List.lengthwithdrawals;maximum=limit})elseletresults=List.revrev_resultsinreturn(ctxt,Message_result.Batch_V1.Batch_result{results;indexes},withdrawals)endletapply_deposit:ctxt->Tx_rollup_message.deposit->(ctxt*deposit_result*Tx_rollup_withdraw.toption)m=funinitial_ctxtTx_rollup_message.{sender;destination;ticket_hash;amount}->letapply_deposit()=let*ctxt,indexes,aidx=address_indexinitial_ctxtempty_indexesdestinationinlet*ctxt,indexes,tidx=ticket_indexctxtindexesIndexable.(valueticket_hash)inlet*ctxt=depositctxtaidxtidxamountinreturn(ctxt,indexes)incatch(apply_deposit())(fun(ctxt,indexes)->return(ctxt,Deposit_successindexes,None))(funreason->(* Should there be an error during the deposit, then return
the full [amount] to [sender] in the form of a
withdrawal. *)letwithdrawal=Tx_rollup_withdraw.{claimer=sender;ticket_hash;amount}inreturn(initial_ctxt,Deposit_failurereason,Somewithdrawal))letapply_message:ctxt->parameters->Tx_rollup_message.t->(ctxt*Message_result.t)m=functxtparametersmsg->letopenTx_rollup_messageinmatchmsgwith|Depositdeposit->let*ctxt,result,withdrawl_opt=apply_depositctxtdepositinreturn(ctxt,(Deposit_resultresult,Option.to_listwithdrawl_opt))|Batchstr->(letbatch=Data_encoding.Binary.of_string_optTx_rollup_l2_batch.encodingstrinmatchbatchwith|Some(V1batch)->let*ctxt,result,withdrawals=Batch_V1.apply_batchctxtparametersbatchinreturn(ctxt,(Batch_V1_resultresult,withdrawals))|None->failInvalid_batch_encoding)end