Fix error handling in morley-client
Description
In morley-client, when doing a batch operation, runOperation
will return a RunOperationResult
.
This RunOperationResult
may contain multiple OperationContent
, each of which may contain multiple OperationResult
.
In handleOperationResult
, we inspect these results and, if any of them is a failure, we combine all the failures and throw them all aggregated in one exception.
However, there is a bug in handleOperationResult
.
As soon as it finds an OperationContent
with an OperationFailed :: OperationResult
, it throws immediately with all the errors from OperationContent
. If there are any more OperationContent
s with errors, those will be swallowed and won't be thrown.
Steps to reproduce
Paste this in a repl session (cabal repl lib:morley-client
):
import Morley.Client.Action.Common
import Morley.Tezos.Core
import Morley.Util.Named
import Morley.Client.RPC.Types
:{
let res = RunOperationResult
{ rrOperationContents =
OperationContent
(RunMetadata
{ rmOperationResult = OperationFailed []
, rmInternalOperationResults =
[ InternalOperation {unInternalOperation = OperationFailed []}
, InternalOperation {unInternalOperation = OperationFailed []}
]
})
:|
[ OperationContent
(RunMetadata
{ rmOperationResult = OperationFailed
[ CantPayStorageFee
, BalanceTooLow (#balance :! UnsafeMutez {unMutez = 149380}) (#required :! UnsafeMutez {unMutez = 355000})
]
, rmInternalOperationResults =
[ InternalOperation {unInternalOperation = OperationFailed []}
, InternalOperation {unInternalOperation = OperationFailed []}
]
})
, OperationContent (RunMetadata {rmOperationResult = OperationFailed [], rmInternalOperationResults = []})
]
}
:}
Note: this is a real network response extracted while debugging a failing test, see Background.
λ> handleOperationResult res 3
*** Exception: Preapply failed due to the following errors:
Expected behaviour
Expected the exception thrown to contain a list with the reasons why the operation failed, i.e. CantPayStorageFee
and BalanceTooLow
.
Actual behaviour
As can be seen above, handleOperationResult
detected an error had occurred, but the exception thrown contains an empty list of errors.
This is because it only looked at the OperationFailed []
in the first OperationContent
. The real reason for the failure (CantPayStorageFee
and BalanceTooLow
) was nested inside the second OperationContent
.
Environment
-
production
branch, a726d42f
Background
Some background: this bug was first discovered while working on the segmented-cfmm project.
Here's the original repro and output, using commit d6cbb3e4e1c90e5996bc416b77c5875140f2e156
:
nettestScenarioCaps "balance_of can handle multiple owners and positions" do
owner1 <- newAddress "new-owner"
cfmm <- fst <$> prepareSomeSegCFMM [owner1] defaultTokenTypes def
withSender owner1 $ inBatch do
setPosition cfmm liquidity (-20, -15)
setPosition cfmm liquidity (-10, 1)
setPosition cfmm liquidity (6, 17)
pure ()
┏━━ test/Test/FA2/BalanceOf.hs ━━━
80 ┃ -- nonOwner1 <- newAddress auto
81 ┃ -- nonOwner2 <- newAddress auto
82 ┃ -- transferMoney owner1 10_e6
83 ┃ -- cfmm <- fst <$> prepareSomeSegCFMM [owner1, owner2, owner3, nonOwner1] defaultTokenTypes def
84 ┃ cfmm <- fst <$> prepareSomeSegCFMM [owner1] defaultTokenTypes def
85 ┃ withSender owner1 $ inBatch do
86 ┃ setPosition cfmm liquidity (-20, -15) -- TokenId 0
87 ┃ setPosition cfmm liquidity (-10, 1) -- TokenId 1
88 ┃ setPosition cfmm liquidity (6, 17) -- TokenId 2
89 ┃ pure ()
┃ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
┃ | Preapply failed due to the following errors:
90 ┃ -- withSender owner2 do
91 ┃ -- setPosition cfmm liquidity (-10, 15) -- TokenId 3
92 ┃ -- withSender owner3 $ inBatch do
93 ┃ -- setPosition cfmm liquidity (-10, 15) -- TokenId 4
94 ┃ -- setPosition cfmm liquidity (-1, 8) -- TokenId 5
CallStack (from HasCallStack):
addCallStack, called at src/Morley/Nettest/Client.hs:97:51 in cleveland-0.1.0-Bp91jlrmqMcJf5chwWljdz:Morley.Nettest.Client
f, called at src/Morley/Nettest/Abstract.hs:770:45 in cleveland-0.1.0-Bp91jlrmqMcJf5chwWljdz:Morley.Nettest.Abstract
f, called at src/Morley/Nettest/Abstract.hs:734:37 in cleveland-0.1.0-Bp91jlrmqMcJf5chwWljdz:Morley.Nettest.Abstract
noiRunOperationBatch, called at src/Morley/Nettest/Abstract.hs:237:5 in cleveland-0.1.0-Bp91jlrmqMcJf5chwWljdz:Morley.Nettest.Abstract
noiRunOperationBatch, called at src/Morley/Nettest/Abstract.hs:237:5 in cleveland-0.1.0-Bp91jlrmqMcJf5chwWljdz:Morley.Nettest.Abstract
runOperationBatchM, called at src/Morley/Nettest/Abstract.hs:609:3 in cleveland-0.1.0-Bp91jlrmqMcJf5chwWljdz:Morley.Nettest.Abstract
runBatched, called at src/Morley/Nettest/Caps.hs:358:3 in cleveland-0.1.0-Bp91jlrmqMcJf5chwWljdz:Morley.Nettest.Caps
inBatch, called at test/Test/FA2/BalanceOf.hs:85:25 in main:Test.FA2.BalanceOf