diff --git a/README.md b/README.md index 7516e50f..e959fd39 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # prototype-forwarder-contract +For the specialized forwarder contract, see [here](README_SPECIALIZED.md) + ## Building ```bash diff --git a/README_SPECIALIZED.md b/README_SPECIALIZED.md new file mode 100644 index 00000000..c67078a0 --- /dev/null +++ b/README_SPECIALIZED.md @@ -0,0 +1,238 @@ + +# Building + +To build, run: + +```bash +stack build +``` + +# Origination + +The CLI tool's `print-specialized-fa12` command can be used to print the contract: + +```bash +❯❯❯ stack exec -- prototype-forwarder-contract print-specialized-fa12 --help +Usage: prototype-forwarder-contract print-specialized-fa12 --central-wallet ADDRESS + --fa12-address ADDRESS + [-o|--output FILEPATH] + [--oneline] + Dump FA1.2 Token Forwarder contract, specialized to paricular addresses, in + the form of Michelson code + +Available options: + -h,--help Show this help text + --central-wallet ADDRESS Address of central wallet + --fa12-address ADDRESS Address of FA1.2 Token contract + -o,--output FILEPATH Output file + --oneline Force single line output +``` + + +# Usage + +## Overview + +The forwarder contract's only entrypoint allows any user to trigger a "flush" of +a given `nat`ural number of tokens from the forwarder to a "central wallet": + +``` ++-------------+ +| | +| Any user | +| | ++--+----------+ + | + | + | Call with: 42 + | + | ++--v----------+ +-------+ +| | Transfer 42 tokens | | +| Forwarder +--------------------------->+ FA1.2 | +| | From: Forwarder | | ++-------------+ To: Central Wallet +-------+ +``` + +The central wallet is fixed upon origination. + + +## Setup + +Originate a dummy FA1.2 that has the appropriate `transfer` +entrypoint and fails on all inputs: + +```bash +❯❯❯ tezos-client --wait none originate contract DummyManagedLedger \ + transferring 0 from $BOB_ADDRESS running \ + "$(cat dummy_fa12.tz | tr -d '\n')" \ + --burn-cap 0.378 + +Waiting for the node to be bootstrapped before injection... +Current head: BLYqnrwfpmKF (timestamp: 2020-05-28T18:51:47-00:00, validation: 2020-05-28T18:52:08-00:00) +Node is bootstrapped, ready for injecting operations. +Estimated gas: 12214 units (will add 100 for safety) +Estimated storage: 378 bytes added (will add 20 for safety) +Operation successfully injected in the node. +Operation hash is 'ong5uJLknnycztcM2VTZ8faeAGPJEhaAQbNo7fiLQKPGyAJ8qYZ' +NOT waiting for the operation to be included. +Use command + tezos-client wait for ong5uJLknnycztcM2VTZ8faeAGPJEhaAQbNo7fiLQKPGyAJ8qYZ to be included --confirmations 30 --branch BLYqnrwfpmKFscvGCqhC5SkZKbdLvNhpQ9MTVPat5q4UrP6Jp41 +and/or an external block explorer to make sure that it has been included. +This sequence of operations was run: + Manager signed operations: + From: tz1bDCu64RmcpWahdn9bWrDMi6cu7mXZynHm + Fee to the baker: ꜩ0.001582 + Expected counter: 623977 + Gas limit: 12314 + Storage limit: 398 bytes + Balance updates: + tz1bDCu64RmcpWahdn9bWrDMi6cu7mXZynHm ............. -ꜩ0.001582 + fees(tz1Ke2h7sDdakHJQh8WX4Z372du1KChsksyU,216) ... +ꜩ0.001582 + Origination: + From: tz1bDCu64RmcpWahdn9bWrDMi6cu7mXZynHm + Credit: ꜩ0 + Script: + { parameter + (or (pair %transfer (address :from) (pair (address :to) (nat :value))) + (pair %approve (address :spender) (nat :value))) ; + storage unit ; + code { FAILWITH } } + Initial storage: Unit + No delegate for this contract + This origination was successfully applied + Originated contracts: + KT1B73ePQbxBHFxKgKjHE46Y6WnabnNu5i7f + Storage size: 121 bytes + Paid storage size diff: 121 bytes + Consumed gas: 12214 + Balance updates: + tz1bDCu64RmcpWahdn9bWrDMi6cu7mXZynHm ... -ꜩ0.121 + tz1bDCu64RmcpWahdn9bWrDMi6cu7mXZynHm ... -ꜩ0.257 + +New contract KT1B73ePQbxBHFxKgKjHE46Y6WnabnNu5i7f originated. +Contract memorized as DummyManagedLedger. +``` + + +Originate a copy of the specialized forwarder that points to the dummy FA1.2 +and uses Alice's address as the central wallet: + +```bash +❯❯❯ tezos-client --wait none originate contract FA12Forwarder \ + transferring 0 from $BOB_ADDRESS running \ + "$(stack exec -- prototype-forwarder-contract print-specialized-fa12 \ + --central-wallet $ALICE_ADDRESS \ + --fa12-address KT1B73ePQbxBHFxKgKjHE46Y6WnabnNu5i7f)" \ + --burn-cap 0.493 +Waiting for the node to be bootstrapped before injection... +Current head: BL8qgEypo1DM (timestamp: 2020-05-28T19:04:27-00:00, validation: 2020-05-28T19:04:29-00:00) +Node is bootstrapped, ready for injecting operations. +Estimated gas: 15042 units (will add 100 for safety) +Estimated storage: 493 bytes added (will add 20 for safety) +Operation successfully injected in the node. +Operation hash is 'ooJYSs4R8dQkuf4mXumExcB57r4ZG6g5HWnHaveSqshJischitT' +NOT waiting for the operation to be included. +Use command + tezos-client wait for ooJYSs4R8dQkuf4mXumExcB57r4ZG6g5HWnHaveSqshJischitT to be included --confirmations 30 --branch BL8qgEypo1DMw7ngmXSVhsjkaamb4Yb4oQ34H7EJ5NLpeMqMvDc +and/or an external block explorer to make sure that it has been included. +This sequence of operations was run: + Manager signed operations: + From: tz1bDCu64RmcpWahdn9bWrDMi6cu7mXZynHm + Fee to the baker: ꜩ0.00198 + Expected counter: 623978 + Gas limit: 15142 + Storage limit: 513 bytes + Balance updates: + tz1bDCu64RmcpWahdn9bWrDMi6cu7mXZynHm ............. -ꜩ0.00198 + fees(tz1Ke2h7sDdakHJQh8WX4Z372du1KChsksyU,216) ... +ꜩ0.00198 + Origination: + From: tz1bDCu64RmcpWahdn9bWrDMi6cu7mXZynHm + Credit: ꜩ0 + Script: + { parameter nat ; + storage unit ; + code { CAR ; + PUSH address "tz1R3vJ5TV8Y5pVj8dicBR23Zv8JArusDkYr" ; + PAIR ; + SELF ; + ADDRESS ; + PAIR ; + DIP { PUSH address "KT1B73ePQbxBHFxKgKjHE46Y6WnabnNu5i7f" ; + CONTRACT %transfer (pair address (pair address nat)) ; + IF_NONE { PUSH string "Internal: not FA1.2" ; FAILWITH } { PUSH mutez 0 } } ; + TRANSFER_TOKENS ; + DIP { NIL operation } ; + CONS ; + DIP { UNIT } ; + PAIR } } + Initial storage: Unit + No delegate for this contract + This origination was successfully applied + Originated contracts: + KT1FgsP8zrKgG9pNW6vwK98dUB7BvjkQmqeD + Storage size: 236 bytes + Paid storage size diff: 236 bytes + Consumed gas: 15042 + Balance updates: + tz1bDCu64RmcpWahdn9bWrDMi6cu7mXZynHm ... -ꜩ0.236 + tz1bDCu64RmcpWahdn9bWrDMi6cu7mXZynHm ... -ꜩ0.257 + +New contract KT1FgsP8zrKgG9pNW6vwK98dUB7BvjkQmqeD originated. +Contract memorized as FA12Forwarder. +``` + + +Call the forwarder with the parameter `42`: + +```bash +❯❯❯ tezos-client --wait none transfer 0 from $BOB_ADDRESS to KT1FgsP8zrKgG9pNW6vwK98dUB7BvjkQmqeD --arg 42 + +Waiting for the node to be bootstrapped before injection... +Current head: BLKvZAnwhm9d (timestamp: 2020-05-28T19:05:27-00:00, validation: 2020-05-28T19:05:54-00:00) +Node is bootstrapped, ready for injecting operations. +This simulation failed: + Manager signed operations: + From: tz1bDCu64RmcpWahdn9bWrDMi6cu7mXZynHm + Fee to the baker: ꜩ0 + Expected counter: 623979 + Gas limit: 1040000 + Storage limit: 60000 bytes + Transaction: + Amount: ꜩ0 + From: tz1bDCu64RmcpWahdn9bWrDMi6cu7mXZynHm + To: KT1FgsP8zrKgG9pNW6vwK98dUB7BvjkQmqeD + Parameter: 42 + This transaction was BACKTRACKED, its expected effects (as follow) were NOT applied. + Updated storage: Unit + Storage size: 236 bytes + Consumed gas: 26359 + Internal operations: + Transaction: + Amount: ꜩ0 + From: KT1FgsP8zrKgG9pNW6vwK98dUB7BvjkQmqeD + To: KT1B73ePQbxBHFxKgKjHE46Y6WnabnNu5i7f + Entrypoint: transfer + Parameter: (Pair 0x014df03b5b4530dd0ac5ca1b2f4754ba4bd8e97b2100 + (Pair 0x00003b5d4596c032347b72fb51f688c45200d0cb50db 42)) + This operation FAILED. + +Runtime error in contract KT1B73ePQbxBHFxKgKjHE46Y6WnabnNu5i7f: + 1: { parameter + 2: (or (pair %transfer (address :from) (pair (address :to) (nat :value))) + 3: (pair %approve (address :spender) (nat :value))) ; + 4: storage unit ; + 5: code { FAILWITH } } +At line 5 characters 9 to 17, +script reached FAILWITH instruction +with + (Pair (Left (Pair 0x014df03b5b4530dd0ac5ca1b2f4754ba4bd8e97b2100 + (Pair 0x00003b5d4596c032347b72fb51f688c45200d0cb50db 42))) + Unit) +Fatal error: + transfer simulation failed +``` + +As expected, the dummy FA1.2 fails with a transfer of `42` tokens from the +forwarder contract to `$ALICE_ADDRESS`. + diff --git a/app/Main.hs b/app/Main.hs index 68931b6b..92cdffc4 100644 --- a/app/Main.hs +++ b/app/Main.hs @@ -196,7 +196,7 @@ argParser = Opt.subparser $ mconcat parseAddress "tokens-contract" "Address of FA1.2 token contract" <*> outputOption ) - "Parameter to flush spzialized any-token forwarder" + "Parameter to flush specialized any-token forwarder" outputOption = Opt.optional $ Opt.strOption $ mconcat [ Opt.short 'o' diff --git a/src/Lorentz/Contracts/Forwarder/Specialized.hs b/src/Lorentz/Contracts/Forwarder/Specialized.hs index 05f4bf9d..aa044b9a 100644 --- a/src/Lorentz/Contracts/Forwarder/Specialized.hs +++ b/src/Lorentz/Contracts/Forwarder/Specialized.hs @@ -45,6 +45,8 @@ type Parameter = Natural -- - The central wallet to transfer sub-tokens to type Storage = () +-- | Wrap a @to@ `Address` and number of tokens to transfer +-- in `TransferParams`, sending from `self` toTransferParameter :: forall s. Address & Natural & s :-> TransferParams & s toTransferParameter = do pair @@ -53,6 +55,8 @@ toTransferParameter = do pair forcedCoerce_ @(Address, (Address, Natural)) @TransferParams +-- | Run a transfer to the given central wallet `Address`, given the token +-- contract `Address` and the number of tokens to transfer runSpecializedTransfer :: Address -> Address -> (Natural & s) :-> (Operation & s) runSpecializedTransfer centralWalletAddr' contractAddr' = do push centralWalletAddr' @@ -76,6 +80,7 @@ specializedForwarderContract centralWalletAddr' contractAddr' = do dip unit pair +-- | `analyzeLorentz` specialized to the `specializedForwarderContract` analyzeSpecializedForwarder :: Address -> Address -> AnalyzerRes analyzeSpecializedForwarder centralWalletAddr' contractAddr' = analyzeLorentz $ specializedForwarderContract centralWalletAddr' contractAddr' diff --git a/src/Lorentz/Contracts/Forwarder/Specialized/FlushAny.hs b/src/Lorentz/Contracts/Forwarder/Specialized/FlushAny.hs index 080905a4..d779a9b1 100644 --- a/src/Lorentz/Contracts/Forwarder/Specialized/FlushAny.hs +++ b/src/Lorentz/Contracts/Forwarder/Specialized/FlushAny.hs @@ -40,7 +40,8 @@ import Prelude (Show(..), Enum(..), Eq(..), ($), String, show) import Michelson.Typed.Value.Orphans () --- | The number of sub-tokens to forward and which token to forward it on +-- | The number of sub-tokens to forward and the typed contract address of the +-- token to forward it on data Parameter = Parameter { amountToFlush :: !Natural , tokenContract :: !(ContractRef TransferParams) @@ -54,6 +55,7 @@ instance HasTypeAnn Parameter instance ParameterHasEntryPoints Parameter where type ParameterEntryPointsDerivation Parameter = EpdNone +-- | Unwrap a `Parameter` unParameter :: Parameter & s :-> (Natural, ContractRef TransferParams) & s unParameter = forcedCoerce_ @@ -92,6 +94,8 @@ mkParameter amountToFlush' tokenContract' = -- - The central wallet to transfer sub-tokens to type Storage = () +-- | Wrap a @to@ `Address` and number of tokens to transfer +-- in `TransferParams`, sending from `self` toTransferParameter :: forall s. Address & Natural & s :-> TransferParams & s toTransferParameter = do pair @@ -100,6 +104,8 @@ toTransferParameter = do pair forcedCoerce_ @(Address, (Address, Natural)) @TransferParams +-- | Run a transfer to the given central wallet `Address`, given the token +-- contract `Address` and the number of tokens to transfer runSpecializedAnyTransfer :: Address -> (Natural & ContractRef TransferParams & s) :-> (Operation & s) runSpecializedAnyTransfer centralWalletAddr' = do push centralWalletAddr' @@ -120,6 +126,7 @@ specializedAnyForwarderContract centralWalletAddr' = do dip unit pair +-- | `analyzeLorentz` specialized to the `specializedAnyForwarderContract` analyzeSpecializedAnyForwarder :: Address -> AnalyzerRes analyzeSpecializedAnyForwarder centralWalletAddr' = analyzeLorentz $ specializedAnyForwarderContract centralWalletAddr' diff --git a/src/Lorentz/Contracts/Forwarder/Specialized/FlushAny/ForwardAnyFA12.hs b/src/Lorentz/Contracts/Forwarder/Specialized/FlushAny/ForwardAnyFA12.hs index ae459d46..b43d59eb 100644 --- a/src/Lorentz/Contracts/Forwarder/Specialized/FlushAny/ForwardAnyFA12.hs +++ b/src/Lorentz/Contracts/Forwarder/Specialized/FlushAny/ForwardAnyFA12.hs @@ -52,6 +52,10 @@ instance ParameterHasEntryPoints Parameter where instance (SingI t) => ParameterHasEntryPoints (Value t) where type ParameterEntryPointsDerivation (Value t) = EpdNone +-- | Run a transfer to the given central wallet `Address`, given `FlushAny.Parameter`. +-- If no `FlushAny.Parameter` is provided, skip forwarding contract tokens. +-- +-- Always attempt to forward any Tez in `balance`. runSpecializedAnyTezTransfer :: Address -> (Maybe FlushAny.Parameter & s) :-> ([Operation] & s) runSpecializedAnyTezTransfer centralWalletAddr' = do dip $ push centralWalletAddr' @@ -95,6 +99,7 @@ specializedAnyFA12ForwarderContract centralWalletAddr' = do dip unit pair +-- | `analyzeLorentz` specialized to the `specializedAnyFA12ForwarderContract` analyzeSpecializedAnyTezForwarder :: Address -> AnalyzerRes analyzeSpecializedAnyTezForwarder centralWalletAddr' = analyzeLorentz $ specializedAnyFA12ForwarderContract centralWalletAddr' @@ -123,3 +128,4 @@ verifyForwarderContract centralWalletAddr' (SomeContract (contract' :: ContractC forceOneline (specializedAnyFA12ForwarderContract centralWalletAddr') + diff --git a/src/Lorentz/Contracts/Forwarder/Specialized/FlushAny/Tez.hs b/src/Lorentz/Contracts/Forwarder/Specialized/FlushAny/Tez.hs index ea45f238..733d5aad 100644 --- a/src/Lorentz/Contracts/Forwarder/Specialized/FlushAny/Tez.hs +++ b/src/Lorentz/Contracts/Forwarder/Specialized/FlushAny/Tez.hs @@ -43,6 +43,10 @@ import Michelson.Typed.Value.Orphans () -- - The central wallet to transfer sub-tokens to type Storage = () +-- | Run a transfer to the given central wallet `Address`, given the number of +-- tokens to forward and the token contract reference. +-- +-- Always attempt to forward any Tez in `balance`. runSpecializedAnyTezTransfer :: Address -> (Natural & ContractRef TransferParams & s) :-> ([Operation] & s) runSpecializedAnyTezTransfer centralWalletAddr' = do push centralWalletAddr' @@ -82,6 +86,7 @@ specializedAnyTezForwarderContract centralWalletAddr' = do dip unit pair +-- | `analyzeLorentz` specialized to the `specializedAnyTezForwarderContract` analyzeSpecializedAnyTezForwarder :: Address -> AnalyzerRes analyzeSpecializedAnyTezForwarder centralWalletAddr' = analyzeLorentz $ specializedAnyTezForwarderContract centralWalletAddr' @@ -110,3 +115,4 @@ verifyForwarderContract centralWalletAddr' (SomeContract (contract' :: ContractC forceOneline (specializedAnyTezForwarderContract centralWalletAddr') +