diff --git a/etherlink/tezos_contracts/README.md b/etherlink/tezos_contracts/README.md index 4eac5e7e45296e079386c538c0f79502cbf5c15d..ebc42c4eb338f4865fef6b27e0db752044f55b3f 100644 --- a/etherlink/tezos_contracts/README.md +++ b/etherlink/tezos_contracts/README.md @@ -17,3 +17,14 @@ $ ./octez-client originate contract evm-bridge transferring 0 from running ``` $ ./octez-client transfer 0 from to evm-bridge --entrypoint "set" --arg '""' --burn-cap 0.00675 ``` + +## Fast Withdrawals Workflow + +**NB**: Some interactions were intentionally omitted to facilitate overall comprehension. +The sequencer is omitted here as it is irrelevant to the core mechanism of the +fast withdrawal feature for instance. + +![Fast Withdrawals Workflow 0](fastwithdrawals_inanutshell/fastwithdrawals_inanutshell_0.png) +![Fast Withdrawals Workflow 1](fastwithdrawals_inanutshell/fastwithdrawals_inanutshell_1.png) +![Fast Withdrawals Workflow 2](fastwithdrawals_inanutshell/fastwithdrawals_inanutshell_2.png) +![Fast Withdrawals Workflow 3](fastwithdrawals_inanutshell/fastwithdrawals_inanutshell_3.png) diff --git a/etherlink/tezos_contracts/fast_withdrawal_mockup.mligo b/etherlink/tezos_contracts/fast_withdrawal_mockup.mligo new file mode 100644 index 0000000000000000000000000000000000000000..7912c338dab214ab14630006f9238f7791202f24 --- /dev/null +++ b/etherlink/tezos_contracts/fast_withdrawal_mockup.mligo @@ -0,0 +1,68 @@ +(* SPDX-CopyrightText 2025 Functori *) +(* SPDX-CopyrightText 2025 Nomadic Labs *) + +#include "./ticket_type.mligo" + +type storage = { + exchanger: address; (* the address of the ticketer *) + withdrawals : (nat, (nat * timestamp * address * bytes)) big_map; (* stores (quantity, timestamp, payer_address, payload) for each withdrawal id *) +} + +type withdrawal_entry = { + withdrawal_id : nat; + ticket : tez_ticket; + timestamp : timestamp; + base_withdrawer: address; + payload: bytes; +} + +type payout_entry = { + withdrawal_id : nat; + ticket : tez_ticket; + target : address; + timestamp : timestamp; + service_provider : address; + payload: bytes; +} + +type return = operation list * storage + +[@entry] +let payout_withdrawal ({withdrawal_id; ticket; target; timestamp; service_provider; payload} : payout_entry) (storage: storage) : return = + let is_in_storage = Option.is_some (Big_map.find_opt withdrawal_id storage.withdrawals) in + (* Ensure that the fast withdrawal was not already payed. *) + if not is_in_storage then + (* Update storage to record prepayment. *) + let (_, (_, amount)), ticket = Tezos.Next.Ticket.read ticket in + let updated_withdrawals = Big_map.add withdrawal_id (amount, timestamp, service_provider, payload) storage.withdrawals in + let storage = { storage with withdrawals = updated_withdrawals } in + (match Tezos.get_entrypoint_opt "%burn" storage.exchanger with + | None -> failwith "Invalid tez ticket contract" + | Some contract -> + [Tezos.Next.Operation.transaction (target, ticket) 0mutez contract], storage) + else + failwith "The fast withdrawal was already payed" + +[@entry] +let default ({ withdrawal_id; ticket; timestamp; base_withdrawer; payload} : withdrawal_entry) (storage: storage) : return = + match Big_map.find_opt withdrawal_id storage.withdrawals with + | None -> + (* No advance payment found, send to the withdrawer. *) + (match Tezos.get_entrypoint_opt "%burn" storage.exchanger with + | None -> failwith "Invalid tez ticket contract" + | Some contract -> + [Tezos.Next.Operation.transaction (base_withdrawer, ticket) 0mutez contract], storage) + | Some (prepaid_quantity, prepaid_timestamp, payer, stored_payload) -> + (* Check if the provider has prepaid. *) + let (_, (_, amount)), ticket = Tezos.Next.Ticket.read ticket in + if prepaid_timestamp = timestamp && prepaid_quantity = amount && stored_payload = payload then + (* Everything matches, the withdrawal was payed, we send the amount + to the payer. *) + let updated_withdrawals = Big_map.remove withdrawal_id storage.withdrawals in + let storage = { storage with withdrawals = updated_withdrawals } in + (match Tezos.get_entrypoint_opt "%burn" storage.exchanger with + | None -> failwith "Invalid tez ticket contract" + | Some contract -> + [Tezos.Next.Operation.transaction (payer, ticket) 0mutez contract], storage) + else + failwith "Unexpected behavior" diff --git a/etherlink/tezos_contracts/fast_withdrawal_mockup.tz b/etherlink/tezos_contracts/fast_withdrawal_mockup.tz new file mode 100644 index 0000000000000000000000000000000000000000..35f9c19e8f945441a660df70a031380108e51a79 --- /dev/null +++ b/etherlink/tezos_contracts/fast_withdrawal_mockup.tz @@ -0,0 +1,127 @@ +{ parameter + (or (pair %default + (nat %withdrawal_id) + (ticket %ticket (pair nat (option bytes))) + (timestamp %timestamp) + (address %base_withdrawer) + (bytes %payload)) + (pair %payout_withdrawal + (nat %withdrawal_id) + (ticket %ticket (pair nat (option bytes))) + (address %target) + (timestamp %timestamp) + (address %service_provider) + (bytes %payload))) ; + storage + (pair (address %exchanger) + (big_map %withdrawals nat (pair nat timestamp address bytes))) ; + code { UNPAIR ; + IF_LEFT + { UNPAIR 5 ; + DUP 6 ; + CDR ; + DUP 2 ; + GET ; + IF_NONE + { DIG 2 ; + DIG 4 ; + DROP 3 ; + DUP 3 ; + CAR ; + CONTRACT %burn (pair address (ticket (pair nat (option bytes)))) ; + IF_NONE + { DROP 3 ; PUSH string "Invalid tez ticket contract" ; FAILWITH } + { DIG 3 ; + NIL operation ; + DIG 2 ; + PUSH mutez 0 ; + DIG 4 ; + DIG 5 ; + PAIR ; + TRANSFER_TOKENS ; + CONS ; + PAIR } } + { DIG 4 ; + DROP ; + UNPAIR 4 ; + DIG 5 ; + READ_TICKET ; + CDR ; + CDR ; + DIG 8 ; + DIG 6 ; + COMPARE ; + EQ ; + SWAP ; + DIG 3 ; + COMPARE ; + EQ ; + DIG 6 ; + DIG 4 ; + COMPARE ; + EQ ; + AND ; + AND ; + IF { DUP 4 ; + DIG 4 ; + CDR ; + NONE (pair nat timestamp address bytes) ; + DIG 5 ; + UPDATE ; + UPDATE 2 ; + DUP ; + CAR ; + CONTRACT %burn (pair address (ticket (pair nat (option bytes)))) ; + IF_NONE + { DROP 3 ; PUSH string "Invalid tez ticket contract" ; FAILWITH } + { SWAP ; + NIL operation ; + DIG 2 ; + PUSH mutez 0 ; + DIG 4 ; + DIG 5 ; + PAIR ; + TRANSFER_TOKENS ; + CONS ; + PAIR } } + { DROP 4 ; PUSH string "Unexpected behavior" ; FAILWITH } } } + { UNPAIR 6 ; + DUP 7 ; + CDR ; + DUP 2 ; + GET ; + IF_NONE { PUSH bool False } { DROP ; PUSH bool True } ; + NOT ; + IF { SWAP ; + READ_TICKET ; + CDR ; + CDR ; + DUP 8 ; + DIG 8 ; + CDR ; + DIG 8 ; + DIG 8 ; + DIG 8 ; + DIG 5 ; + PAIR 4 ; + SOME ; + DIG 4 ; + UPDATE ; + UPDATE 2 ; + DUP ; + CAR ; + CONTRACT %burn (pair address (ticket (pair nat (option bytes)))) ; + IF_NONE + { DROP 3 ; PUSH string "Invalid tez ticket contract" ; FAILWITH } + { SWAP ; + NIL operation ; + DIG 2 ; + PUSH mutez 0 ; + DIG 4 ; + DIG 5 ; + PAIR ; + TRANSFER_TOKENS ; + CONS ; + PAIR } } + { DROP 7 ; PUSH string "The fast withdrawal was already payed" ; FAILWITH } } } } + diff --git a/etherlink/tezos_contracts/fastwithdrawals_inanutshell/fastwithdrawals_inanutshell_0.png b/etherlink/tezos_contracts/fastwithdrawals_inanutshell/fastwithdrawals_inanutshell_0.png new file mode 100644 index 0000000000000000000000000000000000000000..e8d2bd45d31e4776475cd95bfec6ec6835743297 Binary files /dev/null and b/etherlink/tezos_contracts/fastwithdrawals_inanutshell/fastwithdrawals_inanutshell_0.png differ diff --git a/etherlink/tezos_contracts/fastwithdrawals_inanutshell/fastwithdrawals_inanutshell_1.png b/etherlink/tezos_contracts/fastwithdrawals_inanutshell/fastwithdrawals_inanutshell_1.png new file mode 100644 index 0000000000000000000000000000000000000000..0686e42c229737099a477407e795856e55582027 Binary files /dev/null and b/etherlink/tezos_contracts/fastwithdrawals_inanutshell/fastwithdrawals_inanutshell_1.png differ diff --git a/etherlink/tezos_contracts/fastwithdrawals_inanutshell/fastwithdrawals_inanutshell_2.png b/etherlink/tezos_contracts/fastwithdrawals_inanutshell/fastwithdrawals_inanutshell_2.png new file mode 100644 index 0000000000000000000000000000000000000000..c6982ca38c23d0563cca17479be7e60e9b7e3004 Binary files /dev/null and b/etherlink/tezos_contracts/fastwithdrawals_inanutshell/fastwithdrawals_inanutshell_2.png differ diff --git a/etherlink/tezos_contracts/fastwithdrawals_inanutshell/fastwithdrawals_inanutshell_3.png b/etherlink/tezos_contracts/fastwithdrawals_inanutshell/fastwithdrawals_inanutshell_3.png new file mode 100644 index 0000000000000000000000000000000000000000..d8829cf0676ca8930b71d4efad986154c2711f1d Binary files /dev/null and b/etherlink/tezos_contracts/fastwithdrawals_inanutshell/fastwithdrawals_inanutshell_3.png differ diff --git a/etherlink/tezos_contracts/service_provider.mligo b/etherlink/tezos_contracts/service_provider.mligo new file mode 100644 index 0000000000000000000000000000000000000000..2c84f3e42c40d80e42f7f74b85cc4d7f4544ea81 --- /dev/null +++ b/etherlink/tezos_contracts/service_provider.mligo @@ -0,0 +1,55 @@ +(* SPDX-CopyrightText 2025 Functori *) +(* SPDX-CopyrightText 2025 Nomadic Labs *) + +#include "./ticket_type.mligo" + +type storage = { + fast_withdrawal_contract: address; + exchanger : address; + withdrawal_id : nat; + target : address; + timestamp : timestamp; + service_provider : address; + payload : bytes; +} + +type payout_entry = { + fast_withdrawal_contract: address; + withdrawal_id : nat; + ticket : tez_ticket; + target : address; + timestamp : timestamp; + service_provider : address; + payload: bytes; +} + +type payout_entry_proxy = { + fast_withdrawal_contract: address; + exchanger : address; + withdrawal_id : nat; + target : address; + timestamp : timestamp; + service_provider : address; + payload: bytes; +} + +type return = operation list * storage + +[@entry] +let payout_proxy ({fast_withdrawal_contract; exchanger; withdrawal_id; target; timestamp; service_provider; payload} : payout_entry_proxy) (_storage: storage) : return = + let amount = Tezos.get_amount () in + let payout = Tezos.address (Tezos.self("%payout"): tez_ticket contract) in + match Tezos.get_entrypoint_opt "%mint" exchanger with + | None -> failwith "Invalid tez ticket contract" + | Some contract -> + let mint = Tezos.Next.Operation.transaction payout amount contract in + let payout_storage = {fast_withdrawal_contract; exchanger; withdrawal_id; target; timestamp; service_provider; payload} in + [mint], payout_storage + +[@entry] +let payout (ticket: tez_ticket) ({fast_withdrawal_contract; exchanger; withdrawal_id; target; timestamp; service_provider; payload}: storage) : return = + match Tezos.get_entrypoint_opt "%payout_withdrawal" fast_withdrawal_contract with + | None -> failwith "Invalid entrypoint" + | Some contract -> + let full_payload = (withdrawal_id, (ticket, (target, (timestamp, (service_provider, payload))))) in + [Tezos.Next.Operation.transaction full_payload 0mutez contract], {fast_withdrawal_contract; exchanger; withdrawal_id; target; timestamp; service_provider; payload} diff --git a/etherlink/tezos_contracts/service_provider.tz b/etherlink/tezos_contracts/service_provider.tz new file mode 100644 index 0000000000000000000000000000000000000000..2ff300b4af093d54d2c55a0689299bc5e334fc51 --- /dev/null +++ b/etherlink/tezos_contracts/service_provider.tz @@ -0,0 +1,71 @@ +{ parameter + (or (ticket %payout (pair nat (option bytes))) + (pair %payout_proxy + (address %fast_withdrawal_contract) + (address %exchanger) + (nat %withdrawal_id) + (address %target) + (timestamp %timestamp) + (address %service_provider) + (bytes %payload))) ; + storage + (pair (address %fast_withdrawal_contract) + (address %exchanger) + (nat %withdrawal_id) + (address %target) + (timestamp %timestamp) + (address %service_provider) + (bytes %payload)) ; + code { UNPAIR ; + IF_LEFT + { SWAP ; + UNPAIR 7 ; + DUP ; + CONTRACT %payout_withdrawal + (pair nat (ticket (pair nat (option bytes))) address timestamp address bytes) ; + IF_NONE + { DROP 8 ; PUSH string "Invalid entrypoint" ; FAILWITH } + { DUP 8 ; + DUP 8 ; + DUP 8 ; + DUP 8 ; + DUP 8 ; + DIG 7 ; + DIG 7 ; + PAIR 7 ; + NIL operation ; + DIG 2 ; + PUSH mutez 0 ; + DIG 8 ; + DIG 8 ; + PAIR ; + DIG 7 ; + PAIR ; + DIG 6 ; + PAIR ; + DIG 6 ; + PAIR ; + DIG 5 ; + PAIR ; + TRANSFER_TOKENS ; + CONS ; + PAIR } } + { SWAP ; + DROP ; + UNPAIR 7 ; + AMOUNT ; + SELF %payout ; + ADDRESS ; + DUP 4 ; + CONTRACT %mint address ; + IF_NONE + { DROP 9 ; PUSH string "Invalid tez ticket contract" ; FAILWITH } + { DUG 2 ; + TRANSFER_TOKENS ; + DUG 7 ; + PAIR 7 ; + NIL operation ; + DIG 2 ; + CONS ; + PAIR } } } } +