Skip to content
This repository was archived by the owner on Dec 2, 2024. It is now read-only.

SCP-2880: Transaction outputs should contain a minimum of 2 Ada. #76

Merged
merged 1 commit into from
Nov 12, 2021

Conversation

koslambrou
Copy link
Contributor

Enforce constraint from the real Cardano network where each transaction output should contain a minimum amount (here 2) of Ada.

  • Changed the transaction validation code in Ledger.Index to check this minimum deposit.

  • Changed the transaction construction code in Ledger.Constraints.OffChain to add enough Ada for the minimum deposit to all transaction outputs of the unbalanced tx.

  • Changed the emulator Wallet handler to correctly handle this constraints when balancing a transaction.

  • Adapted the contracts in plutus-use-cases to work with this new constraint.

  • The command cabal run plutus-use-cases-scripts – ./tmp transactions -p ./plutus-use-cases/scripts/protocol-parameters.json does not produce warnings anymore.

Pre-submit checklist:

  • Branch
    • Tests are provided (if possible)
    • Commit sequence broadly makes sense
    • Key commits have useful messages
    • Relevant tickets are mentioned in commit messages
    • Formatting, materialized Nix files, PNG optimization, etc. are updated
  • PR
    • Self-reviewed the diff
    • Useful pull request description
    • Reviewer requested

Comment on lines +405 to +407
maxFee :: Ada
maxFee = Ada.lovelaceOf 1_000_000
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is currently used for testings purposes in plutus-use-cases

Comment on lines -609 to -621
testAccounts :: FutureAccounts
testAccounts =
let con = setupTokens @() @FutureSchema @FutureError
fld = Folds.instanceOutcome con (Trace.walletInstanceTag (Wallet.knownWallet 1))
getOutcome (Folds.Done a) = a
getOutcome e = Haskell.error $ "not finished: " <> Haskell.show e
in
either (Haskell.error . Haskell.show) (getOutcome . S.fst')
$ Freer.run
$ Freer.runError @Folds.EmulatorFoldErr
$ Stream.foldEmulatorStreamM fld
$ Stream.takeUntilSlot 10
$ Trace.runEmulatorStream def setupTokensTrace

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was moved to the test file.

@koslambrou koslambrou force-pushed the scp-2880-emulator-txout-min-1ada branch from fd85a6e to 0d5b1a4 Compare November 1, 2021 09:48
@@ -40,10 +41,12 @@ checkOwnOutputConstraint
checkOwnOutputConstraint ctx@ScriptContext{scriptContextTxInfo} OutputConstraint{ocDatum, ocValue} =
let hsh = V.findDatumHash (Datum $ toBuiltinData ocDatum) scriptContextTxInfo
checkOutput TxOut{txOutValue, txOutDatumHash=Just svh} =
txOutValue == ocValue && hsh == Just svh
Ada.fromValue txOutValue >= Ada.fromValue ocValue
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Revert to old implementation when adjusting constraints using manually function as said in another comment.

Copy link
Contributor Author

@koslambrou koslambrou Nov 3, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added another condition below to make it more precise. However, I can't revert it to the old implementation because of the way I currently adjusted the StateMachine (the new output constraint in mkStep). Here's why. If 10 Ada was locked in the script address, the user creates a tx to redeem 9 Ada (uses mustPayToTheScript of 1Ada), and calls adjustUnbalancedTx manually to adjust the tx outputs, then there's a discrepancy between the output constraint value and the tx output value.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What I meant during the review was a function that adjusts the OutputConstraints, instead of adjusting the unbalanced tx. I'm not sure if that's still a useful thing to do.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahhh ok. Mmm... I'll check that out. Might be better this way than to adjust unbalanced tx

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tried to write a function which adjusts the TxConstraints instead of UnbalancedTx, but it doesn't seem possible. I had the constraint mustSpendAtLeast <Guess token> in the GameStateMachine contract. In this scenario, there's a tx output which contains the Guess token, but I couldn't modify the TxConstraints such that a 2Ada would be associated with the token.

@koslambrou koslambrou force-pushed the scp-2880-emulator-txout-min-1ada branch 2 times, most recently from 5a1da0b to 3a456aa Compare November 3, 2021 09:26
Comment on lines +135 to +137
, stateValue = Value.noAdaValue oldStateValue
<> Ada.toValue (Ada.fromValue oldStateValue - highestBid)
<> Ada.toValue newBid -- and lock the new bid in the script output
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This new stateValue calculation is more precise, and works better with the StateMachine now that it adjustes the Ada per tx output.

@koslambrou koslambrou force-pushed the scp-2880-emulator-txout-min-1ada branch from 3a456aa to dd30452 Compare November 3, 2021 11:57
Comment on lines +437 to +434
logWarn @Text $ "Plutus.Contract.StateMachine.runInitialise: "
<> "Found a transaction output value with less than the minimum amount of Ada. Adjusting ..."
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure about that warning

@koslambrou koslambrou marked this pull request as ready for review November 3, 2021 12:22
@koslambrou
Copy link
Contributor Author

koslambrou commented Nov 3, 2021

@sjoerdvisscher @j-mueller What's mostly left is maybe reorganize some of the functions in Plutus.Contract.Request since user can now manually adjust an UnbalancedTx. For now, only mkTxConstraints return an UnbalancedTx so we can balance it afterwards. Any suggestions are welcome.

@koslambrou koslambrou force-pushed the scp-2880-emulator-txout-min-1ada branch 4 times, most recently from fcb9d52 to d535fdc Compare November 3, 2021 15:50
[ OutputConstraint
{ ocDatum = newData
-- Check that the thread token value is still there
, ocValue = newValue <> threadTokenValueInner threadToken (ownHash ptx)
}
]
| not (isZero newValue)]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain this one?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was needed because of the GameStateMachine contract.

Here's an use case example which explains the mod:

  • Wallet1 locks 8 Ada to the script address
  • Wallet2 call the guess endpoint with the correct secret and wants the full 8 Ada.
  • In GameStateMachine.transition, the (Locked, Guess) transition will generate a new stateValue of mempty (8 locked Ada - 8 requested Ada).
  • Then, StateMachine.runStep for the (Locked, Guess) transition is executed and it creates an unbalanced tx containing an output of mempty Value to the script address.
  • Wallet.Emulator.Wallet will validate the balanced transaction and report the error GameSMError (SMCContractError (WalletError (ValidationError (ScriptFailure (EvaluationError ["L1","Ld","S5","PT5"] "CekEvaluationFailure")))))

The validation that is used is part of StateMachine.OnChain.mkValidator that I modified above. It will call Ledger.Constraints.OnChain.checkOwnOutputConstraint. Since the new stateValue is mempty, getContinuingOutputs ctx return an empty, list therefore checkOwnOutputConstraint returns False (because of List.any, thus the validation fails.

Therefore, my mod is simply to not create an output constraint is the new value is empty.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But now it doesn't check for the thread token anymore. And what happens if the value is larger than zero but less than 2 Ada?

Copy link
Contributor Author

@koslambrou koslambrou Nov 4, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But now it doesn't check for the thread token anymore.

You're right. I should replace it with not (isZero (newValue <> threadTokenValueInner thread (ownHash ptx)). I think this is valid. Note that the runStep and mkStep function will adjust the new state value to contain min of 2Ada is it's not empty. Also note, that I adjusted the mkStep function as well. Great job find that.

And what happens if the value is larger than zero but less than 2 Ada?

So if Wallet1 locks 8 Ada, and Wallet2 correctly guesses the secret and want 7Ada, then the new state value will be 1Ada. mkStep will generate an adjusted contraint which adds the missing ada (output of 2Ada + thread token), and runStep will adjust the outputs of created unbalanced tx.
Makes sense?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright, I managed to find a way to revert this change. What I needed to do was adapt the GameStateMachine contract such that when the locked funds are 0, then we change the state to final, so there will be no more txs.

@koslambrou koslambrou force-pushed the scp-2880-emulator-txout-min-1ada branch 2 times, most recently from 7196573 to c261532 Compare November 4, 2021 17:24
-- Add the thread token value back to the output
, ocValue = newStateValue <> Ada.toValue missingLovelace
}
| not isFinal && not (Value.isZero newStateValue) ]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, this is what I was missing. I don't think we want to throw away the outputconstraint when the value is zero, since we still want to propagate the ocDatum.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then what do we do if the output value is zero? Simply adjusting it to 2 Ada doesn't make much sense, since that would mean that I could never use all funds in a script address.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And similarly it would not be possible to use all the funds but 1 Ada in a script address right? Maybe we need to discuss this with people who have thought about this more...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So currently, we need to adapt contracts like GameStateMachine such that if the locked funds are 0, then we need to transition of a final state. Of course, if want all the funds - 1, then the adjusting function will change that to funds - 2.

@koslambrou koslambrou force-pushed the scp-2880-emulator-txout-min-1ada branch 4 times, most recently from 7a31ce9 to ef1d4b0 Compare November 10, 2021 19:22
Copy link
Contributor

@sjoerdvisscher sjoerdvisscher left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good and and impressive job! (Although I have a nagging feeling this won't be the last of it, since it is all quite fiddly)

@koslambrou
Copy link
Contributor Author

Looks good and and impressive job! (Although I have a nagging feeling this won't be the last of it, since it is all quite fiddly)

Thanks :)

True, it certainly feels fiddly.

@koslambrou koslambrou force-pushed the scp-2880-emulator-txout-min-1ada branch from ef1d4b0 to 4fb2048 Compare November 11, 2021 19:48
… transaction outputs should contain a minimum amount (here 2) of Ada.

* Changed the transaction validation code in Ledger.Index to check this minimum deposit.

* Changed the transaction construction code in Ledger.Constraints.OffChain to add enough Ada for the minimum deposit to all transaction outputs of the unbalanced tx.

* Changed the emulator Wallet handler to correctly handle this constraints when balancing a transaction.

* Adapted the contracts in plutus-use-cases to work with this new constraint.

* The command `cabal run plutus-use-cases-scripts – ./tmp transactions -p ./plutus-use-cases/scripts/protocol-parameters.json` does not produce warnings anymore.
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants