-
Notifications
You must be signed in to change notification settings - Fork 322
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
CIP-???? | SpendMany script purpose (replace Spend) #858
Open
matteocoppola
wants to merge
7
commits into
cardano-foundation:master
Choose a base branch
from
matteocoppola:master
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
9b390d0
wip
matteocoppola ca5f4e8
added most of the info
matteocoppola b51c873
added most of the info
matteocoppola f719925
wip
matteocoppola 812a81d
Merge branch 'cardano-foundation:master' into master
matteocoppola 1147b1a
empty set syntax for implementors
rphair 202a961
initial PR as Discussion
rphair File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,115 @@ | ||||||
--- | ||||||
CIP: ???? | ||||||
Title: SpendMany script purpose | ||||||
Category: Plutus | ||||||
Status: Proposed | ||||||
Authors: | ||||||
- Matteo Coppola <[email protected]> | ||||||
Implementors: [] | ||||||
Discussions: | ||||||
- https://github.com/cardano-foundation/CIPs/pull/858 | ||||||
Created: 2024-07-13 | ||||||
License: Apache-2.0 | ||||||
--- | ||||||
|
||||||
## Abstract | ||||||
|
||||||
This CIP proposes to replace the current Spend purpose with another one called SpendMany, removing the need of computational waste and higher fees when a transaction validates multiple inputs coming from the same script. | ||||||
|
||||||
SpendMany is executed only once for the whole transaction and it comes with 2 parameters: | ||||||
- The current script hash | ||||||
- The list of the indexes of the inputs that are being spent from this script | ||||||
|
||||||
The second parameter is used to easily identify all the current script inputs in the script Context. | ||||||
|
||||||
As this change could take a while to be implemented, this CIP proposes also another quick workaround: | ||||||
we add in the Context a ```Map<Hash, List<Int>>``` where the keys are the transaction's script hashes and the values are lists containing the indexes of the Inputs that come from these scripts. | ||||||
|
||||||
## Motivation: why is this CIP necessary? | ||||||
|
||||||
After years of Cardano smart contract development and sharing the experience with different teams, we came to the conclusion that there's no smart contract - except for trivial cases - that needs to validate each script utxo separately. | ||||||
Even when a single utxo must be validated, all the recent smart contracts allow composability, permitting multiple inputs from the same script to be validated in the same transaction. When this is done for each utxo separately, developers need to add important redundant security checks to avoid double spending vulnerabilities. | ||||||
|
||||||
Additionally, most of the times, the smart contract should also validate conditions of the transaction as a whole, for example the validity range or the sum of the Values of all the Inputs. | ||||||
Validating N inputs separately means wasting a lot of resources (computational and transaction fees) because these conditions must be checked N times. | ||||||
To circumvent this and get more performant smart contracts, the development community came up with the "Withdraw Zero" trick, which consists in using a separated WithdrawFrom script that will be executed only once for the full transaction. While this is a dirty hack used to circumvent the current status of the validator specification, it also still requires to check that the WithdrawFrom script is present for each script Input, wasting resources. | ||||||
|
||||||
These problems have been briefly discussed in [CPS-0004](https://github.com/cardano-foundation/CIPs/pull/418). | ||||||
|
||||||
It is therefore necessary to replace the existing Spend purpose with a new one that I called SpendMany. | ||||||
The objective is to reduce the time complexity from O(n) to O(1) while keeping the development experience easy. | ||||||
Please note that the name doesn't necessarily need to be changed, but here we will use SpendMany here to refer to these CIP changes. | ||||||
|
||||||
The SpendMany purpose completely replaces the Spend purpose, because simply adding the new purpose would require extremely difficult changes in the ledger code and because, as discussed above, it covers 100% of the Spend validation use cases. | ||||||
|
||||||
By using SpendMany we officially get rid of the need of using dirty hacks and unnecessary computation. | ||||||
|
||||||
Even if it's not a dependency, this CIP works very well with the [CIP for Transaction Inputs as Unordered Set](https://github.com/cardano-foundation/CIPs/pull/758). | ||||||
|
||||||
Even if this CIP looks as an alternative to [CIP-112](https://github.com/cardano-foundation/CIPs/tree/master/CIP-0112), they can perfectly co-exist. Additionally this CIP drastically simplifies the development experience. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
just helps for those who struggle to remember every number 😆 |
||||||
|
||||||
If the changes formalized by this CIP can't be implemented within a reasonable timeframe, I at least suggest to implement the following change: adding in the script Context a new field called inputIndexesByScriptHashes of type ```Map<Hash, List<Int>>``` where the keys are the transaction's script hashes and the values are lists containing the indexes of the Inputs that come from these scripts. | ||||||
This change will allow to easily validate all the script inputs with either the CIP-112 or the already existing "Withdraw Zero" hack. | ||||||
|
||||||
## Specification | ||||||
|
||||||
The type signature of this script type will be the same of the usual Spend validator, except without any Datum, as it can be easily derived for each input, making the type signature the same of Minting and Staking validators: | ||||||
|
||||||
```Redeemer -> ScriptContext -> () ``` | ||||||
|
||||||
The type signature of the newly introduced Purpose will be: | ||||||
|
||||||
```SpendMany ByteArray List<Int>``` | ||||||
|
||||||
where: | ||||||
- ByteArray is the current script hash | ||||||
- List<Int> is the list of Input indexes that must be validated by this script. These are the usual inputs that would normally trigger the execution of Spend validation logic. | ||||||
|
||||||
### Alternative workaround: | ||||||
If SpendMany can't be implemented we should at least add the field inputIndexesByScriptHashes in the script Context : | ||||||
``` | ||||||
-- | TxInfo for PlutusV3 | ||||||
data TxInfo = TxInfo | ||||||
{ txInfoInputs :: [V2.TxInInfo] | ||||||
, txInfoReferenceInputs :: [V2.TxInInfo] | ||||||
, txInfoOutputs :: [V2.TxOut] | ||||||
, txInfoFee :: V2.Value | ||||||
, txInfoMint :: V2.Value | ||||||
, txInfoTxCerts :: [TxCert] | ||||||
, txInfoWdrl :: Map V2.Credential Haskell.Integer | ||||||
, txInfoValidRange :: V2.POSIXTimeRange | ||||||
, txInfoSignatories :: [V2.PubKeyHash] | ||||||
, txInfoRedeemers :: Map ScriptPurpose V2.Redeemer | ||||||
, txInfoData :: Map V2.DatumHash V2.Datum | ||||||
, txInfoId :: V2.TxId | ||||||
, txInfoVotingProcedures :: Map Voter (Map GovernanceActionId VotingProcedure) | ||||||
, txInfoProposalProcedures :: [ProposalProcedure] | ||||||
, txInfoCurrentTreasuryAmount :: Haskell.Maybe V2.Value | ||||||
, txInfoTreasuryDonation :: Haskell.Maybe V2.Value | ||||||
, inputIndexesByScriptHashes :: Map ScriptHash [Haskell.Integer] -- ^ newly introduced list of inputs for each script | ||||||
} | ||||||
``` | ||||||
|
||||||
## Rationale: how does this CIP achieve its goals? | ||||||
|
||||||
This CIP removes the need of unprofessional hacks to achieve whole-transaction validation while also achieving better computational efficiency. | ||||||
A simple chnage of the Spend purpose in SpendMany can simplify the developer experience, giving visibility of all the inputs to validate at once. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
typo! |
||||||
|
||||||
### Alternatives | ||||||
* We could decide to accept the withdraw-zero staking script trick as an adequate solution, and just preserve the nonsensical withdraw zero case in future language versions. | ||||||
* [CIP-112](https://github.com/cardano-foundation/CIPs/tree/master/CIP-0112) is a possible valid alternative, but makes the development experience more complicated and doesn't provide an easy list of all the inputs validated by the current validator. | ||||||
|
||||||
## Path to Active | ||||||
|
||||||
### Acceptance Criteria | ||||||
|
||||||
- [ ] These rules included within a official Plutus version, and released via a major hard fork. | ||||||
|
||||||
### Implementation Plan | ||||||
|
||||||
- [ ] Passes all requirements of both Plutus and Ledger teams as agreed to improve Plutus script efficiency and usability. | ||||||
|
||||||
|
||||||
## Copyright | ||||||
|
||||||
This CIP is licensed under [Apache-2.0](http://www.apache.org/licenses/LICENSE-2.0). |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some scripts allow composing with itself where inputs use different redeemers. It sounds like this CIP will require all inputs from the script in this transaction to use the same redeemer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, the current CIP version requires 1 redeemer for all the inputs from the smart contract.
Can't we just use a single redeemer that contains lists for each field, indexed by the order of the inputs?
We already do like that and it works smoothly: as we know the inputs and their order in advance, it's easy to setup such a redeemer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd rather not use the redeemer like this since it must always be validated at run-time to verify all of the inputs are properly accounted for. If an input is accidentally/maliciously omitted from the redeemer, the smart contract can return true without actually validating that input. In other words, the security of this approach depends on the DApp developer. It is possible for one DApp to be secure while another DApp is missing checks.
Instead, the ledger can do the checks before running the scripts if separate spending groups are used. One of the plutus/ledger devs can correct me if I am wrong, but if the rule is that all inputs in a spending group must use the same redeemer, wouldn't this be a very cheap check that can be part of phase 1 validation? You can just hash the redeemer used for each input in the group and then compare the hashes. If an input is not tagged by a redeemer, the ledger will immediately catch it and fail the transaction.
I have not used the redeemer index pattern very much, so if I am misunderstanding something please correct me.