Skip to content
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

[docs] More Smart Contract Interaction Examples #398

Merged
merged 7 commits into from
Nov 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/hip-guests-relax.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"docs": major
---

Redeemer Builder
142 changes: 138 additions & 4 deletions docs/pages/documentation/deep-dives/smart-contract-interactions.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -75,20 +75,154 @@ const tx = await lucid

</Steps>

## Apply parameters
## Redeemer Builder

The [Redeemer Indexing design pattern](https://github.com/Anastasia-Labs/design-patterns/blob/main/utxo-indexers/UTXO-INDEXERS.md)
leverages the deterministic script evaluation property of the Cardano ledger to achieve substantial performance gains in onchain code.

In scenarios where the protocol necessitates spending from the script back to a specific output—such as returning funds from the script to the same script,
directing them to another script, or transferring assets to a wallet, it is imperative to ensure that each script input is uniquely associated with an output.

This preventive measure is essential for mitigating the risk of
[Double Satisfaction Attack](https://github.com/Anastasia-Labs/design-patterns/blob/main/stake-validator/STAKE-VALIDATOR.md#protect-against-double-satisfaction-exploit).

You can use a redeemer containing one-to-one correlation between script input UTxOs and output UTxOs.
This is provided via ordered lists of input/output indices of inputs/ouputs present in the Script Context.

For e.g.
```
Inputs : [scriptInputA, scriptInputB, randomInput1, scriptInputC, randomInput2, randomInput3] // random inputs are not the concerned script inputs
Outputs : [outputA, outputB, outputC, randomOuput1, randomOutput2, randomOutput3]
InputIdxs : [0, 1, 3]
OutputIdxs : [0, 1, 2]

where type Redeemer = List<(inputIdx, outputIdx)>
```

Luckily [lucid-evolution](https://github.com/Anastasia-Labs/lucid-evolution)
provides a high-level interface that abstracts all the complexity away and makes writing offchain code for this design pattern extremely simple
by using the `RedeemerBuilder` to help building your redeemer.

```typescript
const scriptInputs: UTxO[] = [
{ txHash: "a", outputIndex: 1, address: "scriptAddress", assets: { lovelace: 10_000000n } },
{ txHash: "b", outputIndex: 2, address: "scriptAddress", assets: { lovelace: 20_000000n } },
{ txHash: "d", outputIndex: 4, address: "scriptAddress", assets: { lovelace: 40_000000n } },
];
const randomInputs: UTxO[] = [
{ txHash: "c", outputIndex: 3, address: "randomAddress", assets: { lovelace: 30_000000n } },
{ txHash: "e", outputIndex: 5, address: "randomAddress", assets: { lovelace: 50_000000n } },
{ txHash: "f", outputIndex: 6, address: "randomAddress", assets: { lovelace: 60_000000n } },
];

const redeemer: RedeemerBuilder = {
kind: "selected",
makeRedeemer: (inputIdxs: bigint[]) => {
const tupleList = inputIdxs.map((inputIdx, i) => [inputIdx, BigInt(i)]); // List<(inputIdx, outputIdx)>
return Data.to(tupleList);
},
inputs: scriptInputs,
};

const tx = await lucid
.newTx()
.collectFrom([...scriptInputs, ...randomInputs], redeemer)
.attach.SpendingValidator(script)
.pay.ToContract("scriptAddress", datum, { lovelace: 10_000000n })
.pay.ToContract("scriptAddress", datum, { lovelace: 20_000000n })
.pay.ToContract("scriptAddress", datum, { lovelace: 40_000000n })
.pay.ToAddress("randomAddress", { lovelace: 30_000000n })
.pay.ToAddress("randomAddress", { lovelace: 50_000000n })
.pay.ToAddress("randomAddress", { lovelace: 60_000000n })
.addSigner("randomAddress")
.complete();
```

Some validators are parameterized, you can apply parameters dynamically
## Apply parameters

Some validators are parameterized, you can apply parameters dynamically.
Below example is for your validator when it expects 1 integer argument, eg. `validator minting_policy(first_param: Int) { .. }`
```typescript
const mintingPolicy = {
type: "PlutusV2",
type: "PlutusV3",
script: applyParamsToScript(
"5907945907910100...",
applyDoubleCborEncoding("5907945907910100..."),
[10n] // Parameters
),
};
```

Next example is for your validator when it expects 2 arguments, eg. a ByteArray and a Boolean.
For example, `validator spending_validator(pkh: VerificationKeyHash, yes_no: Bool) { .. }`
```typescript
const pkh = paymentCredentialOf(address).hash;
const yes = new Constr(1, []);

const spendingValidator = {
type: "PlutusV3",
script: applyParamsToScript(
applyDoubleCborEncoding("5907945907910101..."),
[pkh, yes] // Parameters
),
};
```

The following example is for your validator when it expects a more complex type, for example:
```gleam
type OutputReference {
transaction_id: ByteArray,
output_index: Int,
}

validator your_validator(o_ref: OutputReference) { .. }
```
```typescript
const oRef = new Constr(0, [String(utxo.txHash), BigInt(utxo.outputIndex)]);

const yourValidator = {
type: "PlutusV3",
script: applyParamsToScript(
applyDoubleCborEncoding("5907945907910102..."),
[oRef] // Parameters
),
};
```
Note that this OutputReference format is Aiken's, for Plutus format,
```haskell
data TxOutRef = TxOutRef
{ txOutRefId :: TxId
, txOutRefIdx :: Integer
}
```
where TxId itself is another type like `newtype TxId = TxId { getTxId :: PlutusTx.BuiltinByteString }`,
this would translate to the following Aiken code:
```gleam
type TransactionId {
tx_id: ByteArray
}

type OutputReference {
transaction_id: TransactionId,
output_index: Int,
}

validator your_validator(o_ref: OutputReference) { .. }
```
In this case, you can provide the parameter for your validator like:
```typescript
const txID = new Constr(0, [String(utxo.txHash)]);
const txIdx = BigInt(utxo.outputIndex)
const oRef = new Constr(0, [txID, txIdx]);

const yourValidator = {
type: "PlutusV3",
script: applyParamsToScript(
applyDoubleCborEncoding("5907945907910103..."),
[oRef] // Parameters
),
};
```

---

## Plutus script purposes
Expand Down
Loading