Here is some code to illustrate my point …
The contract uses a very simple counter as Datum. On every grab the validator checks these simple conditions …
A client app would first try to increment the counter and grab the 100 ADA award, if that fails it would send another Tx that just increments the counter. Like so …

All goes well and on the fifth grab, the payout to the caller actually happens …
Slot 00001: *** CONTRACT LOG: "Give Tx"
Slot 00001: *** CONTRACT LOG: "-------"
Slot 00001: *** CONTRACT LOG: " TxIn mytxRef = TxOutRef {txOutRefId = af5e6d25b5ecb26185289a03d50786b7ac4425b21849143ed7e18bcd70dc4db8, txOutRefIdx = 6}"
Slot 00001: *** CONTRACT LOG: " -> Nothing"
Slot 00001: *** CONTRACT LOG: " TxOut {mytxAddress = PubKeyCredential 21fe31dfa154a261626bf854046fd2271b7bed4b6abe45aa58877ef47f9721b9, mytxValue = [(,\"\",99999490)], mytxDatum = Nothing}"
Slot 00001: *** CONTRACT LOG: " TxOut {mytxAddress = ScriptCredential f891e2dfe14f184559e4c3027cff54f3f1d956df7317ee6a5c15b035b4a6e69f, mytxValue = [(,\"\",500)], mytxDatum = Just (MyDatum {mdCount = 0})}"
Slot 00001: TxnValidate 22ff289f00153aa0e4554d470e8f6aeff5f7438715cb40e553a7c7fb1139659d
Slot 00001: SlotAdd Slot 2
Slot 00002: *** CONTRACT LOG: "Grab Tx"
Slot 00002: *** CONTRACT LOG: "-------"
Slot 00002: *** CONTRACT LOG: " TxIn mytxRef = TxOutRef {txOutRefId = 22ff289f00153aa0e4554d470e8f6aeff5f7438715cb40e553a7c7fb1139659d, txOutRefIdx = 0}"
Slot 00002: *** CONTRACT LOG: " -> Nothing"
Slot 00002: *** CONTRACT LOG: " TxIn mytxRef = TxOutRef {txOutRefId = 22ff289f00153aa0e4554d470e8f6aeff5f7438715cb40e553a7c7fb1139659d, txOutRefIdx = 1}"
Slot 00002: *** CONTRACT LOG: " -> Just TxOut {mytxAddress = ScriptCredential f891e2dfe14f184559e4c3027cff54f3f1d956df7317ee6a5c15b035b4a6e69f, mytxValue = [(,\"\",500)], mytxDatum = Just (MyDatum {mdCount = 0})}"
Slot 00002: *** CONTRACT LOG: " TxOut {mytxAddress = PubKeyCredential 21fe31dfa154a261626bf854046fd2271b7bed4b6abe45aa58877ef47f9721b9, mytxValue = [(,\"\",99999480)], mytxDatum = Nothing}"
Slot 00002: *** CONTRACT LOG: " TxOut {mytxAddress = ScriptCredential f891e2dfe14f184559e4c3027cff54f3f1d956df7317ee6a5c15b035b4a6e69f, mytxValue = [(,\"\",500)], mytxDatum = Just (MyDatum {mdCount = 1})}"
Slot 00002: TxnValidate 85066a73a065ae019ba87cc9da1058249191232241b032f93005b9818df6aeb2
Slot 00002: SlotAdd Slot 3
Slot 00003: *** CONTRACT LOG: "Grab Tx"
Slot 00003: *** CONTRACT LOG: "-------"
Slot 00003: *** CONTRACT LOG: " TxIn mytxRef = TxOutRef {txOutRefId = 85066a73a065ae019ba87cc9da1058249191232241b032f93005b9818df6aeb2, txOutRefIdx = 0}"
Slot 00003: *** CONTRACT LOG: " -> Nothing"
Slot 00003: *** CONTRACT LOG: " TxIn mytxRef = TxOutRef {txOutRefId = 85066a73a065ae019ba87cc9da1058249191232241b032f93005b9818df6aeb2, txOutRefIdx = 1}"
Slot 00003: *** CONTRACT LOG: " -> Just TxOut {mytxAddress = ScriptCredential f891e2dfe14f184559e4c3027cff54f3f1d956df7317ee6a5c15b035b4a6e69f, mytxValue = [(,\"\",500)], mytxDatum = Just (MyDatum {mdCount = 1})}"
Slot 00003: *** CONTRACT LOG: " TxOut {mytxAddress = PubKeyCredential 21fe31dfa154a261626bf854046fd2271b7bed4b6abe45aa58877ef47f9721b9, mytxValue = [(,\"\",99999470)], mytxDatum = Nothing}"
Slot 00003: *** CONTRACT LOG: " TxOut {mytxAddress = ScriptCredential f891e2dfe14f184559e4c3027cff54f3f1d956df7317ee6a5c15b035b4a6e69f, mytxValue = [(,\"\",500)], mytxDatum = Just (MyDatum {mdCount = 2})}"
Slot 00003: TxnValidate 4a102d725c6ac48e4fe4c903b93cd140015d6de92c60ee32a345c085f22e153f
Slot 00003: SlotAdd Slot 4
Slot 00004: *** CONTRACT LOG: "Grab Tx"
Slot 00004: *** CONTRACT LOG: "-------"
Slot 00004: *** CONTRACT LOG: " TxIn mytxRef = TxOutRef {txOutRefId = 4a102d725c6ac48e4fe4c903b93cd140015d6de92c60ee32a345c085f22e153f, txOutRefIdx = 0}"
Slot 00004: *** CONTRACT LOG: " -> Nothing"
Slot 00004: *** CONTRACT LOG: " TxIn mytxRef = TxOutRef {txOutRefId = 4a102d725c6ac48e4fe4c903b93cd140015d6de92c60ee32a345c085f22e153f, txOutRefIdx = 1}"
Slot 00004: *** CONTRACT LOG: " -> Just TxOut {mytxAddress = ScriptCredential f891e2dfe14f184559e4c3027cff54f3f1d956df7317ee6a5c15b035b4a6e69f, mytxValue = [(,\"\",500)], mytxDatum = Just (MyDatum {mdCount = 2})}"
Slot 00004: *** CONTRACT LOG: " TxOut {mytxAddress = PubKeyCredential 21fe31dfa154a261626bf854046fd2271b7bed4b6abe45aa58877ef47f9721b9, mytxValue = [(,\"\",99999460)], mytxDatum = Nothing}"
Slot 00004: *** CONTRACT LOG: " TxOut {mytxAddress = ScriptCredential f891e2dfe14f184559e4c3027cff54f3f1d956df7317ee6a5c15b035b4a6e69f, mytxValue = [(,\"\",500)], mytxDatum = Just (MyDatum {mdCount = 3})}"
Slot 00004: TxnValidate 72e645800e499d09aea88b6b2f38e92fe90b93c415aabfb70abda193933863ec
Slot 00004: SlotAdd Slot 5
Slot 00005: *** CONTRACT LOG: "Grab Tx"
Slot 00005: *** CONTRACT LOG: "-------"
Slot 00005: *** CONTRACT LOG: " TxIn mytxRef = TxOutRef {txOutRefId = 72e645800e499d09aea88b6b2f38e92fe90b93c415aabfb70abda193933863ec, txOutRefIdx = 0}"
Slot 00005: *** CONTRACT LOG: " -> Nothing"
Slot 00005: *** CONTRACT LOG: " TxIn mytxRef = TxOutRef {txOutRefId = 72e645800e499d09aea88b6b2f38e92fe90b93c415aabfb70abda193933863ec, txOutRefIdx = 1}"
Slot 00005: *** CONTRACT LOG: " -> Just TxOut {mytxAddress = ScriptCredential f891e2dfe14f184559e4c3027cff54f3f1d956df7317ee6a5c15b035b4a6e69f, mytxValue = [(,\"\",500)], mytxDatum = Just (MyDatum {mdCount = 3})}"
Slot 00005: *** CONTRACT LOG: " TxOut {mytxAddress = PubKeyCredential 21fe31dfa154a261626bf854046fd2271b7bed4b6abe45aa58877ef47f9721b9, mytxValue = [(,\"\",99999450)], mytxDatum = Nothing}"
Slot 00005: *** CONTRACT LOG: " TxOut {mytxAddress = ScriptCredential f891e2dfe14f184559e4c3027cff54f3f1d956df7317ee6a5c15b035b4a6e69f, mytxValue = [(,\"\",500)], mytxDatum = Just (MyDatum {mdCount = 4})}"
Slot 00005: TxnValidate 0d240247c0b04ff9e620f35ecef5a54bc0478b48977b6394e46ed039c863840b
Slot 00005: SlotAdd Slot 6
Slot 00006: *** CONTRACT LOG: "Grab Tx"
Slot 00006: *** CONTRACT LOG: "-------"
Slot 00006: *** CONTRACT LOG: " TxIn mytxRef = TxOutRef {txOutRefId = 0d240247c0b04ff9e620f35ecef5a54bc0478b48977b6394e46ed039c863840b, txOutRefIdx = 0}"
Slot 00006: *** CONTRACT LOG: " -> Nothing"
Slot 00006: *** CONTRACT LOG: " TxIn mytxRef = TxOutRef {txOutRefId = 0d240247c0b04ff9e620f35ecef5a54bc0478b48977b6394e46ed039c863840b, txOutRefIdx = 1}"
Slot 00006: *** CONTRACT LOG: " -> Just TxOut {mytxAddress = ScriptCredential f891e2dfe14f184559e4c3027cff54f3f1d956df7317ee6a5c15b035b4a6e69f, mytxValue = [(,\"\",500)], mytxDatum = Just (MyDatum {mdCount = 4})}"
Slot 00006: *** CONTRACT LOG: " TxOut {mytxAddress = PubKeyCredential 21fe31dfa154a261626bf854046fd2271b7bed4b6abe45aa58877ef47f9721b9, mytxValue = [(,\"\",99999440)], mytxDatum = Nothing}"
Slot 00006: *** CONTRACT LOG: " TxOut {mytxAddress = ScriptCredential f891e2dfe14f184559e4c3027cff54f3f1d956df7317ee6a5c15b035b4a6e69f, mytxValue = [(,\"\",400)], mytxDatum = Just (MyDatum {mdCount = 5})}"
Slot 00006: *** CONTRACT LOG: " TxOut {mytxAddress = PubKeyCredential 21fe31dfa154a261626bf854046fd2271b7bed4b6abe45aa58877ef47f9721b9, mytxValue = [(,\"\",100)], mytxDatum = Nothing}"
Slot 00006: TxnValidate 5c24fb1425988011dc83bf0bb642d5262071fc3e4496c9c3e7f925b431ec645c
Slot 00006: SlotAdd Slot 7
Slot 00007: *** USER LOG: Reached slot Slot {getSlot = 7}
Slot 00007: *** CONTRACT LOG: "Grab with payout: True"
Now, some piece of code that I do not control plays an attack on the contract like this …
i.e. It does not consume one of the script’s UTxO. Instead it places a new UTxO with a fabricated Datum that skips the winning count. The validator will not see this Tx. In a trace it looks like this …

The attack is successful because the winning condition (i.e. count mod 5 == 0
) is skipped and the payout never happens …
Slot 00001: *** CONTRACT LOG: "Give Tx"
Slot 00001: *** CONTRACT LOG: "-------"
Slot 00001: *** CONTRACT LOG: " TxIn mytxRef = TxOutRef {txOutRefId = af5e6d25b5ecb26185289a03d50786b7ac4425b21849143ed7e18bcd70dc4db8, txOutRefIdx = 6}"
Slot 00001: *** CONTRACT LOG: " -> Nothing"
Slot 00001: *** CONTRACT LOG: " TxOut {mytxAddress = PubKeyCredential 21fe31dfa154a261626bf854046fd2271b7bed4b6abe45aa58877ef47f9721b9, mytxValue = [(,\"\",99999490)], mytxDatum = Nothing}"
Slot 00001: *** CONTRACT LOG: " TxOut {mytxAddress = ScriptCredential f891e2dfe14f184559e4c3027cff54f3f1d956df7317ee6a5c15b035b4a6e69f, mytxValue = [(,\"\",500)], mytxDatum = Just (MyDatum {mdCount = 0})}"
Slot 00001: TxnValidate 22ff289f00153aa0e4554d470e8f6aeff5f7438715cb40e553a7c7fb1139659d
Slot 00001: SlotAdd Slot 2
Slot 00005: *** CONTRACT LOG: "Grab Tx"
Slot 00005: *** CONTRACT LOG: "-------"
Slot 00005: *** CONTRACT LOG: " TxIn mytxRef = TxOutRef {txOutRefId = 72e645800e499d09aea88b6b2f38e92fe90b93c415aabfb70abda193933863ec, txOutRefIdx = 0}"
Slot 00005: *** CONTRACT LOG: " -> Nothing"
Slot 00005: *** CONTRACT LOG: " TxIn mytxRef = TxOutRef {txOutRefId = 72e645800e499d09aea88b6b2f38e92fe90b93c415aabfb70abda193933863ec, txOutRefIdx = 1}"
Slot 00005: *** CONTRACT LOG: " -> Just TxOut {mytxAddress = ScriptCredential f891e2dfe14f184559e4c3027cff54f3f1d956df7317ee6a5c15b035b4a6e69f, mytxValue = [(,\"\",500)], mytxDatum = Just (MyDatum {mdCount = 3})}"
Slot 00005: *** CONTRACT LOG: " TxOut {mytxAddress = PubKeyCredential 21fe31dfa154a261626bf854046fd2271b7bed4b6abe45aa58877ef47f9721b9, mytxValue = [(,\"\",99999450)], mytxDatum = Nothing}"
Slot 00005: *** CONTRACT LOG: " TxOut {mytxAddress = ScriptCredential f891e2dfe14f184559e4c3027cff54f3f1d956df7317ee6a5c15b035b4a6e69f, mytxValue = [(,\"\",500)], mytxDatum = Just (MyDatum {mdCount = 4})}"
Slot 00005: TxnValidate 0d240247c0b04ff9e620f35ecef5a54bc0478b48977b6394e46ed039c863840b
Slot 00005: SlotAdd Slot 6
Slot 00006: *** CONTRACT LOG: "Grab with payout: False"
Slot 00006: *** CONTRACT LOG: "Attack Tx"
Slot 00006: *** CONTRACT LOG: "---------"
Slot 00006: *** CONTRACT LOG: " TxIn mytxRef = TxOutRef {txOutRefId = af5e6d25b5ecb26185289a03d50786b7ac4425b21849143ed7e18bcd70dc4db8, txOutRefIdx = 1}"
Slot 00006: *** CONTRACT LOG: " -> Nothing"
Slot 00006: *** CONTRACT LOG: " TxOut {mytxAddress = PubKeyCredential 39f713d0a644253f04529421b9f51b9b08979d08295959c4f3990ee617f5139f, mytxValue = [(,\"\",99999989)], mytxDatum = Nothing}"
Slot 00006: *** CONTRACT LOG: " TxOut {mytxAddress = ScriptCredential f891e2dfe14f184559e4c3027cff54f3f1d956df7317ee6a5c15b035b4a6e69f, mytxValue = [(,\"\",1)], mytxDatum = Just (MyDatum {mdCount = 6})}"
Slot 00006: TxnValidate 493badaa5c2694e5944c7cd1a8295f9e0c1a61139e0de4e99143aae1813cef9e
Slot 00006: SlotAdd Slot 7
Slot 00007: *** CONTRACT LOG: "Attack datum placed: MyDatum {mdCount = 6}"
Slot 00007: *** CONTRACT LOG: "Grab Tx"
Slot 00007: *** CONTRACT LOG: "-------"
Slot 00007: *** CONTRACT LOG: " TxIn mytxRef = TxOutRef {txOutRefId = 0d240247c0b04ff9e620f35ecef5a54bc0478b48977b6394e46ed039c863840b, txOutRefIdx = 0}"
Slot 00007: *** CONTRACT LOG: " -> Nothing"
Slot 00007: *** CONTRACT LOG: " TxIn mytxRef = TxOutRef {txOutRefId = 493badaa5c2694e5944c7cd1a8295f9e0c1a61139e0de4e99143aae1813cef9e, txOutRefIdx = 1}"
Slot 00007: *** CONTRACT LOG: " -> Just TxOut {mytxAddress = ScriptCredential f891e2dfe14f184559e4c3027cff54f3f1d956df7317ee6a5c15b035b4a6e69f, mytxValue = [(,\"\",1)], mytxDatum = Just (MyDatum {mdCount = 6})}"
Slot 00007: *** CONTRACT LOG: " TxOut {mytxAddress = PubKeyCredential 21fe31dfa154a261626bf854046fd2271b7bed4b6abe45aa58877ef47f9721b9, mytxValue = [(,\"\",99999440)], mytxDatum = Nothing}"
Slot 00007: *** CONTRACT LOG: " TxOut {mytxAddress = ScriptCredential f891e2dfe14f184559e4c3027cff54f3f1d956df7317ee6a5c15b035b4a6e69f, mytxValue = [(,\"\",1)], mytxDatum = Just (MyDatum {mdCount = 7})}"
Slot 00007: TxnValidate 6279ee3d8261eb67dea206407490ec97bd277a0d9f9177e26b20420f648cdb2a
Slot 00007: SlotAdd Slot 8
Slot 00008: *** CONTRACT LOG: "Grab with payout: False"
Slot 00008: *** USER LOG: Reached slot Slot {getSlot = 8}
In this particular example, the attack is successful because the off-chain grab endpoint always selects the UTxO with the highest count. To fix this, the author of the contract would first have to be aware of these kind of attacks and then proactively take countermeasures. For example, it could reject UTxO that have a count > 0 that were created by a Tx that did not consume a script UTxO. When we require to consume a UTxO, the validator kicks in and we could have rejected the attacker’s Tx.
In essence, the script assumes that there is only ever one UTxO with the counter Datum. It is conceptually a singleton, which must be enforced for example by an NFT that is attached to the UTxO that carries the singleton Datum.
Without the NFT enforcer, the attack could also be worse. For example, if the contract had decoupled UTxO that hold the payout value from a “Data UTxO” that holds the Datum but no value. The attacker could have played an UTxO with the winning count and sucessfully grabed the reward (every time until the script address runs dry).
Bottom line: there seems to be a lot of burden placed on the script author and the validator implementation. The validator impl has to account for all sorts of UTxO that could never have been created by “normal” endpoint operations.
Perhaps over time we’ll see more high level constrains, for example for “singleton datum” that somehow automatically does the NFT thing or similar.
Also, it ocurred to me that these kind of attacks would be a lot harder if every Tx related to a script address would be validated and not just those which consume UTxO already locked by the script. There may be good reason not to validate the pay-to-script case, but currently I can’t think of it.
Cheers - May this help to write better contracts