cardano-serialization-lib icon indicating copy to clipboard operation
cardano-serialization-lib copied to clipboard

What does redeemer index mean

Open nmaddp1995 opened this issue 2 years ago • 10 comments

When I pass index of redeemer is 0, sometimes it works, and sometimes I have to change it to 2 to make it works (like the code below) So what does that mean and how could I know what should I put to index param

 const redeemer = CardanoWasm.Redeemer.new(
      CardanoWasm.RedeemerTag.new_spend(),
      CardanoWasm.BigNum.from_str('2'),
      redeemerData,
      CardanoWasm.ExUnits.new(
        CardanoWasm.BigNum.from_str(BUY_CARDANO_MEM.toString()),
        CardanoWasm.BigNum.from_str(BUY_CARDANO_CPU.toString()),
      ),
    );

nmaddp1995 avatar Aug 17 '22 10:08 nmaddp1995

@nmaddp1995 , the redeemer index is pointing to the input the specific redeemer is for in your input list in the transaction. For example, if you add inputs like: plutus input, some other input, some other input - the index must be zero because the plutus input is the first one in the list; but if you add inputs like: some regular input, plutus input, some other regular input - then the index must be one, and so forth.

In case you have multiple plutus inputs each redeemer must have a correct index pointing to the correct corresponding input. If you were to use the new TxInputsBuilder.add_plutus_script_input functions, they would set the proper index automatically when you build the transaction, because the inputs also must be canonically sorted by the referenced transaction ID, so it might be tricky to get right. Using the new APIs to add script inputs is the best option. Check the release notes for versions 10.1.0 and 10.2.0 here - https://github.com/Emurgo/cardano-serialization-lib/releases

vsubhuman avatar Aug 17 '22 11:08 vsubhuman

@vsubhuman I make buy function with txin is tx put on sale and utxo of buyer

txBuilder.add_input(ScriptAddress, txPutOnSale, assetValPutOnsale);
for (let i = 0; i < hexInputUtxos.length; i++) {
 const wasmUtxo = CardanoWasm.TransactionUnspentOutput.from_bytes(hexToBytes(hexInputUtxos[i]));
 txBuilder.add_input(wasmUtxo.output().address(), wasmUtxo.input(), wasmUtxo.output().amount());
}

And I use set_pluts_scripts for txWitness:

    const transactionWitnessSet = CardanoWasm.TransactionWitnessSet.new();
    transactionWitnessSet.set_plutus_scripts(scripts);
    transactionWitnessSet.set_plutus_data(datums);
    transactionWitnessSet.set_redeemers(redeemers);

Also I set script datahash for txBody

    const scriptDataHash = CardanoWasm.hash_script_data(redeemers, costModels, datums);
    txBody.set_script_data_hash(scriptDataHash);

So I am not sure how could I get the right index of redeemer

nmaddp1995 avatar Aug 18 '22 01:08 nmaddp1995

txBuilder.add_input(ScriptAddress, txPutOnSale, assetValPutOnsale);

@nmaddp1995 , replace this with this:

const plutusWitness = CardanoWasm.PlutusWitness.new(plutusScript, plutusDatum, plutusRedeemer);
txBuilder.add_plutus_script_input(plutusWitness, txPutOnSale, assetValPutOnsale);

Then replace txBody.set_script_data_hash with auto-calculation using this:

txBuilder.calc_script_data_hash(costModels);

(And make sure to call it before you are calling the build function, of course)

Then make sure to use the proper function .build_tx() instead of the deprecated .build(), it will return you the full transaction which includes the script, the data, and the redeemers, so you don't need to put them into the witness set by yourself:

// This transaction already contains the plutus witness data for the script input
// Only have to add vkey witness signatures for regular inputs
const transaction = txBuilder.build_tx();

In the resulting transaction the redeemer IS already present in the witness set and also it has the correct index that matches the position of the input after the canonical sorting.

vsubhuman avatar Aug 18 '22 09:08 vsubhuman

Hi @nmaddp1995 here is a reference implementation that works for most cases https://github.com/MartifyLabs/mesh/blob/smart-contracts/packages/module/src/transaction/transaction.service.ts I hope that help 😉

abdelkrimdev avatar Aug 18 '22 10:08 abdelkrimdev

Tks, I got the index of Plutus transaction by this code

 const allInputs = txInputsBuilder.inputs();
    const numberOfInput = allInputs.len();
    let indexRedeemer = 0;
    for (let i = 0; i < numberOfInput; i++) {
      const curInput = allInputs.get(i);
      const txId = bytesToHex(curInput.transaction_id().to_bytes());
      if (txId?.toString() === resellOrderData.txIdPutOnSale?.toString()) {
        indexRedeemer = i;
      }
    }

BTW, I am making the buy NFT function but I have to define the gas fee to calculate the res coin for buyer and make tx out for that. Is that the right approach for that? Or is there any solution to calculate the exact fee for a transaction or solution to not have to fixed fee

nmaddp1995 avatar Aug 18 '22 10:08 nmaddp1995

is there any solution to calculate the exact fee for a transaction

@nmaddp1995 , if you use the txBuilder.add_change_if_needed(changeAddress) function it will calculate the required fee and create the change outputs automatically, but in order for that to work you need to use the APIs the way I have described them here: https://github.com/Emurgo/cardano-serialization-lib/issues/499#issuecomment-1219239234

Because the builder then must know all the information about your inputs and which witnesses they will require, before you try estimating the fee. If you keep adding the scripts, datums, and redeemers manually the fee estimation will not work correctly.

vsubhuman avatar Aug 18 '22 10:08 vsubhuman

@vsubhuman @abdelkrimdev Hi, I change my function to the same as your purchase function

` const txPutOnSale = CardanoWasm.TransactionInput.new( CardanoWasm.TransactionHash.from_bytes(Buffer.from(resellOrderData.txIdPutOnSale, 'hex')), get(resellOrderData, 'metadata.output_index'), );

const serviceFee = resellOrderData.fee;

const { priceServiceFee, priceRoyaltyFee, priceForSeller } = getPriceData({
  sellOrderPrice: resellOrderData.price,
  coinDecimal: TOKEN_ADA_DECIMAL,
  serviceFee,
  royaltyFee: nftData.royaltyFee,
});

const datum = getDatumFromData();
const datums = CardanoWasm.PlutusList.new();
const outputs = CardanoWasm.TransactionOutputs.new();
datums.add(datum);
const hexInputUtxos = await cardanoApi.getUtxos();
const utxos = hexInputUtxos.map((item: any) => CardanoWasm.TransactionUnspentOutput.from_bytes(hexToBytes(item)));

const assetValPutOnsale = CardanoWasm.Value.new(CardanoWasm.BigNum.from_str(LOCK_AMOUNT_PUT_ON_SALE.toString()));
const assetMAPutOnsale = CardanoWasm.MultiAsset.new();
const assetPutOnsale = CardanoWasm.Assets.new();

assetPutOnsale.insert(
  CardanoWasm.AssetName.new(hexToBytes(convertStringToHex(tokenId))),
  CardanoWasm.BigNum.from_str('1'),
);
assetMAPutOnsale.insert(CardanoWasm.ScriptHash.from_bytes(hexToBytes(nftPolicy)), assetPutOnsale);
assetValPutOnsale.set_multiasset(assetMAPutOnsale);

// nft to buyer
outputs.add(CardanoWasm.TransactionOutput.new(shelleyChangeAddress, assetValPutOnsale));

// service fee
outputs.add(
  CardanoWasm.TransactionOutput.new(
    walletAdminAddress,
    CardanoWasm.Value.new(CardanoWasm.BigNum.from_str(priceServiceFee.toString())),
  ),
);

// money to seller
outputs.add(
  CardanoWasm.TransactionOutput.new(
    sellerAddress,
    CardanoWasm.Value.new(CardanoWasm.BigNum.from_str(priceForSeller)),
  ),
);

// money to creator (royalty)
outputs.add(
  CardanoWasm.TransactionOutput.new(
    creatorAddress,
    CardanoWasm.Value.new(CardanoWasm.BigNum.from_str(priceRoyaltyFee)),
  ),
);

const transactionWitnessSet = CardanoWasm.TransactionWitnessSet.new();

CoinSelection.setProtocolParameters(
  Parameters.coinsPerUtxoWord,
  Parameters.linearFee.minFeeA,
  Parameters.linearFee.minFeeB,
  Parameters.maxTxSize.toString(),
);

const inputs = [...utxos];
const inputPutOnSale = CardanoWasm.TransactionUnspentOutput.new(
  txPutOnSale,
  CardanoWasm.TransactionOutput.new(ScriptAddress, assetValPutOnsale),
);
inputs.push(inputPutOnSale);
console.log('inputs', inputs);
console.log('outputs', outputs);
const { input, change } = CoinSelection.randomImprove(inputs, outputs, 16);
input.forEach((utxo: any) => {
  txBuilder.add_input(utxo.output().address(), utxo.input(), utxo.output().amount());
});

for (let i = 0; i < outputs.len(); i++) {
  txBuilder.add_output(outputs.get(i));
}

const redeemers = CardanoWasm.Redeemers.new();
const indexRedeemer = 0;
const plutusListRedeemer = CardanoWasm.PlutusList.new();
plutusListRedeemer.add(CardanoWasm.PlutusData.new_bytes(hexToBytes(resellOrderData._id))); // sell order id

const redeemerData = CardanoWasm.PlutusData.new_constr_plutus_data(
  CardanoWasm.ConstrPlutusData.new(CardanoWasm.BigNum.from_str(CONTRACT_CARDANO_EVENT.BUY), plutusListRedeemer),
);
const redeemer = CardanoWasm.Redeemer.new(
  CardanoWasm.RedeemerTag.new_spend(),
  CardanoWasm.BigNum.from_str(indexRedeemer.toString()),
  redeemerData,
  CardanoWasm.ExUnits.new(
    CardanoWasm.BigNum.from_str(BUY_CARDANO_MEM.toString()),
    CardanoWasm.BigNum.from_str(BUY_CARDANO_CPU.toString()),
  ),
);
redeemers.add(redeemer);
const scripts = CardanoWasm.PlutusScripts.new();
scripts.add(CardanoWasm.PlutusScript.from_bytes(Buffer.from(cborHexContract, 'hex'))); //from cbor of plutus script

transactionWitnessSet.set_redeemers(redeemers);
transactionWitnessSet.set_plutus_scripts(scripts);
transactionWitnessSet.set_plutus_data(datums);

const inputsCollateral = CardanoWasm.TxInputsBuilder.new();
const hexCollateralUtxos = await window.cardano.getCollateral(COLLATERAL_NAMI_AMOUNT);
for (let i = 0; i < hexCollateralUtxos.length; i++) {
  const wasmUtxo = CardanoWasm.TransactionUnspentOutput.from_bytes(hexToBytes(hexCollateralUtxos[i]));
  inputsCollateral.add_input(wasmUtxo.output().address(), wasmUtxo.input(), wasmUtxo.output().amount());
}
txBuilder.set_collateral(inputsCollateral);

// const plutusWitness = CardanoWasm.PlutusWitness.new(
//   CardanoWasm.PlutusScript.from_bytes(Buffer.from(cborHexContract, 'hex')),
//   datum,
//   redeemer,
// );
// txBuilder.add_plutus_script_input(plutusWitness, txPutOnSale, assetValPutOnsale);
// console.log('5');

const changeMultiAssets = change.multiasset();
console.log('changeMultiAssets', changeMultiAssets);

// const cost_model_vals = COST_MODEL_VAL;

// const costModel = CardanoWasm.CostModel.new();
// cost_model_vals.forEach((x, i) => costModel.set(i, CardanoWasm.Int.new_i32(x)));
// const costModels = CardanoWasm.Costmdls.new();
// costModels.insert(CardanoWasm.Language.new_plutus_v1(), costModel);

// txBuilder.calc_script_data_hash(costModels);

txBuilder.add_change_if_needed(shelleyChangeAddress);
const txBody = txBuilder.build();

const tx = CardanoWasm.Transaction.new(
  txBody,
  CardanoWasm.TransactionWitnessSet.from_bytes(transactionWitnessSet.to_bytes()),
);
// const tx = txBuilder.build_tx();

const size = tx.to_bytes().length * 2;
console.log('size', size);
// if (size > Parameters.maxTxSize) throw new Error(ErrorTypes.MAX_SIZE_REACHED);

let txVkeyWitnesses = await cardanoApi.signTx(bytesToHex(tx.to_bytes()), true);
txVkeyWitnesses = CardanoWasm.TransactionWitnessSet.from_bytes(hexToBytes(txVkeyWitnesses));

transactionWitnessSet.set_vkeys(txVkeyWitnesses.vkeys());

const signedTx = CardanoWasm.Transaction.new(tx.body(), transactionWitnessSet, tx.auxiliary_data());

const txHash = await cardanoApi.submitTx(bytesToHex(signedTx.to_bytes()));

`

But this auto calculate fee is 0.2 ADA, it return error UtxowFailure (FromAlonzoUtxowFail (PPViewHashesDontMatch SNothing (SJust (SafeHash \"abebcdc89a8c403d4a5eac1e9cea6a0722aac60fdaacb2f8f9b89d507868caa7\")))),UtxowFailure (UtxoFailure (FromAlonzoUtxoFail (FeeTooSmallUTxO (Coin 1098996) (Coin 191813))))])

If I use txBuilder.add_plutus_script_input and txBuilder.calc_script_data_hash, it return error when call add_change_if_needed missing input or output for some native asset

Do you have any idea about that

nmaddp1995 avatar Sep 15 '22 04:09 nmaddp1995

Hi, @nmaddp1995 we managed to solve all the issues related to locking and unlocking an asset from a Smart Contract. and we create a package called Mesh I highly encourage you to check it out at https://mesh.martify.io/ it's under active development and we are adding new features constantly.

The fee calculation is wrong because you are adding the plutusWitness after add_change_if_needed

abdelkrimdev avatar Sep 16 '22 13:09 abdelkrimdev

Hi, @nmaddp1995 we managed to solve all the issues related to locking and unlocking an asset from a Smart Contract. and we create a package called Mesh I highly encourage you to check it out at https://mesh.martify.io/ it's under active development and we are adding new features constantly.

The fee calculation is wrong because you are adding the plutusWitness after add_change_if_needed

@abdelkrimdev Hi, about the adding the plutusWitness, can you clarify for me how to add plutusWitness in the correct way to add_change_if_needed. If I use txBuilder.add_plutus_script_input, it return the error missing input or output for some native asset I am in the way to release app to product so I can't change the lib to Mesh on a sort time

nmaddp1995 avatar Sep 19 '22 07:09 nmaddp1995

Hi @nmaddp1995. Actually the error missing input or output for some native asset can be connected with adding plutusWitness, because each script witness can increase tx fee. You can read about reasons of the error in this issue https://github.com/Emurgo/cardano-serialization-lib/issues/285#issuecomment-124361685 , probably you don't have enough ada or assets in the tx_builder's inputs to cover amount of all outputs + fee. And anyway I strongly recommend to use way that @vsubhuman offered to you https://github.com/Emurgo/cardano-serialization-lib/issues/499#issuecomment-1219239234 .

const tx = CardanoWasm.Transaction.new( txBody, CardanoWasm.TransactionWitnessSet.from_bytes(transactionWitnessSet.to_bytes()), );

The way that you use to create the tx is wrong because you get TransactionWitnessSet not from tx_builder and tx_builder doesn't know about your scripts, that can affect correct fee and change calculation. If you choose the build_tx() instead build() you can get TransactionWitnessSet from tx by tx.witness_set() it can help you with signed tx creation. But all scripts must be in the tx_builder before calling the add_change_if_needed.

lisicky avatar Sep 19 '22 13:09 lisicky

More about redeemer index propose you can find in the Alonzo spec https://github.com/input-output-hk/cardano-ledger

lisicky avatar Jun 20 '23 22:06 lisicky