Skip to content
Open
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
11 changes: 11 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
JULC compile code : ../julc

Yaci Devkit admin API documentation: http://localhost:10000/v3/api-docs
Use Yaci DevKit admin's reset endpoint to reset the devnet before running test

Cardano Foundation's cardano-template-and-ecosystem-monitoring repository: https://github.com/cardano-foundation/cardano-template-and-ecosystem-monitoring.git

For writing JuLC validator
- Always prefer to use high-level typed apis
- Try not to use low level builtins or PlutusData directly unless necessary.
- Follow julc-best-practices guide in docs folder for writing clean and maintainable code.
6 changes: 4 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
plugins {
id 'java'
id 'application'
id 'com.bloxbean.cardano.julc' version '0.1.0-pre15'
}

group = 'com.example'
version = '0.1.0'

ext {
julcVersion = '0.1.0-pre12'
julcVersion = '0.1.0-pre15'
cardanoClientLibVersion = '0.8.0-pre4'
}

Expand Down Expand Up @@ -68,7 +69,8 @@ dependencies {
testImplementation "com.bloxbean.cardano:julc-compiler:${julcVersion}"
testImplementation "com.bloxbean.cardano:julc-stdlib:${julcVersion}"
testRuntimeOnly "com.bloxbean.cardano:julc-vm-java:${julcVersion}"
testImplementation "com.bloxbean.cardano:julc-vm-truffle:${julcVersion}" // For JulcDebugger API
// testImplementation "com.bloxbean.cardano:julc-vm-truffle:${julcVersion}" // For JulcDebugger API
// testImplementation "com.bloxbean.cardano:julc-vm-scalus:${julcVersion}" // Scalus VM backend — cross-VM budget comparison
testImplementation 'net.jqwik:jqwik:1.9.2'
testImplementation platform('org.junit:junit-bom:5.11.4')
testImplementation 'org.junit.jupiter:junit-jupiter'
Expand Down
19 changes: 14 additions & 5 deletions docs/julc-best-practices.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ list.take(n) // first n elements
list.drop(n) // skip first n elements
JulcList.empty() // empty list
JulcList.of(a, b, c) // list from elements
JulcList.of(a, b, c).toPlutusData() // typed list -> ListData
```

### 3.6 JulcMap<K, V> — On-Chain Associative Map
Expand All @@ -218,6 +219,7 @@ map.insert(key, value) // new map with entry added
map.delete(key) // new map without key
map.keys() // all keys as JulcList
map.values() // all values as JulcList
map.toPlutusData() // typed map -> MapData
map.size() // number of entries
map.isEmpty() // check if empty
JulcMap.empty() // empty map
Expand Down Expand Up @@ -266,6 +268,7 @@ import com.bloxbean.cardano.julc.stdlib.lib.ByteStringLib;

// Basic operations
ByteStringLib.append(a, b) // concatenate two byte arrays
Builtins.concat(a, b, c, ...) // concatenate 2+ arrays
ByteStringLib.empty() // empty byte array
ByteStringLib.cons(byteVal, bs) // prepend a byte (0-255)
ByteStringLib.length(bs) // byte length
Expand Down Expand Up @@ -303,6 +306,8 @@ ValuesLib.assetOf(value, policyId, tokenName) // quantity of specific
ValuesLib.containsPolicy(value, policyId) // check if policy exists
ValuesLib.countTokensWithQty(mint, policyId, qty) // count tokens with exact qty
ValuesLib.findTokenName(mint, policyId, qty) // find token name with exact qty
ValuesLib.refBytes(seedRef) // txId ++ 2-byte BE index
ValuesLib.uniqueTokenName(seedRef) // blake2b_256(refBytes)
ValuesLib.flatten(value) // flatten to list of (policy, name, amount) triples

// Construction
Expand Down Expand Up @@ -940,13 +945,17 @@ if (a.equals(BigInteger.ZERO))
if (a.compareTo(BigInteger.ZERO) > 0)
```

### 10.3 JVM vs UPLC Behavior Difference
### 10.3 `integerToByteString` Bounds

`Builtins.integerToByteString(true, 0, 0)`:
- **JVM**: Returns `[0]` (1 byte) — BigInteger.ZERO.toByteArray() bug
- **UPLC**: Returns `[]` (empty)
The JVM and UPLC implementations now share the same contract:

**Fix in tests**: Manually handle zero: `if (n.signum() == 0) return new byte[0];`
- width must be between 0 and 8192 bytes;
- a positive width fails if the integer does not fit;
- width 0 uses the minimal representation and encodes zero as an empty byte string;
- even with width 0, the result cannot exceed 8192 bytes.

For deterministic `TxOutRef` seeds, prefer `ValuesLib.refBytes(ref)`, which uses
a fixed two-byte big-endian output index and fails if the index exceeds 65535.

### 10.4 `contains()` on ByteString Lists

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,6 @@ public static void main(String[] args) throws Exception {
.collateralPayer(seller.baseAddress())
.withRequiredSigners(sellerPkh)
.validTo(currentSlot + 10) // before expiration
.ignoreScriptCostEvaluationError(true)
.withTxEvaluator(YaciHelper.julcEvaluator(backend))
.complete();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,6 @@ public static void main(String[] args) throws Exception {
.collateralPayer(player1.baseAddress())
.withRequiredSigners(player1Pkh)
.validTo(currentSlot + 10) // upper bound before expiration
.ignoreScriptCostEvaluationError(true)
.withTxEvaluator(YaciHelper.julcEvaluator(backend))
.complete();

Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/example/offchain/MintingDemo.java
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public static void main(String[] args) throws Exception {
.withRequiredSigners(authorizerPkh)
.feePayer(authorizerAddr)
.collateralPayer(authorizerAddr)
.withTxEvaluator(YaciHelper.julcEvaluator(backend))
// .withTxEvaluator(YaciHelper.julcEvaluator(backend))
.complete();

if (!mintResult.isSuccessful()) {
Expand Down
18 changes: 15 additions & 3 deletions src/main/java/com/example/offchain/OneShotMintDemo.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.bloxbean.cardano.client.address.Address;
import com.bloxbean.cardano.client.api.model.Utxo;
import com.bloxbean.cardano.client.common.model.Networks;
import com.bloxbean.cardano.client.crypto.Blake2bUtil;
import com.bloxbean.cardano.client.function.helper.SignerProviders;
import com.bloxbean.cardano.client.plutus.spec.BigIntPlutusData;
import com.bloxbean.cardano.client.plutus.spec.BytesPlutusData;
Expand All @@ -12,6 +13,9 @@
import com.bloxbean.cardano.client.transaction.spec.Asset;
import com.bloxbean.cardano.client.util.HexUtil;
import com.bloxbean.cardano.julc.clientlib.JulcScriptLoader;
import com.bloxbean.cardano.julc.ledger.TxId;
import com.bloxbean.cardano.julc.ledger.TxOutRef;
import com.bloxbean.cardano.julc.stdlib.lib.ValuesLib;
import com.example.validators.OneShotMintPolicy;

import java.math.BigInteger;
Expand Down Expand Up @@ -60,9 +64,17 @@ public static void main(String[] args) throws Exception {
BigIntPlutusData.of(utxoIndex));
System.out.println("Policy loaded (parameterized)");

// 4. Mint 1 UniqueNFT token
System.out.println("\n--- Minting 1 UniqueNFT ---");
var asset = new Asset("UniqueNFT", BigInteger.ONE);
// 4. Derive the canonical 32-byte token name from the consumed seed.
// ValuesLib.refBytes is JVM/UPLC-identical; Blake2bUtil is the off-chain
// equivalent of the policy's ValuesLib.uniqueTokenName(seedRef).
var seedRef = new TxOutRef(new TxId(utxoTxId), utxoIndex);
byte[] tokenName = Blake2bUtil.blake2bHash256(ValuesLib.refBytes(seedRef));
String tokenNameHex = HexUtil.encodeHexString(tokenName);
System.out.println("Canonical token name: " + tokenNameHex);

// 5. Mint exactly one token with that canonical name.
System.out.println("\n--- Minting canonical one-shot NFT ---");
var asset = new Asset("0x" + tokenNameHex, BigInteger.ONE);
var redeemer = BigIntPlutusData.of(0); // unused redeemer

var mintTx = new ScriptTx()
Expand Down
10 changes: 3 additions & 7 deletions src/main/java/com/example/uverify/onchain/UVerifyFeePot.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.bloxbean.cardano.julc.core.PlutusData;
import com.bloxbean.cardano.julc.core.types.JulcList;
import com.bloxbean.cardano.julc.ledger.*;
import com.bloxbean.cardano.julc.stdlib.Builtins;
import com.bloxbean.cardano.julc.stdlib.annotation.Entrypoint;
import com.bloxbean.cardano.julc.stdlib.annotation.MultiValidator;
import com.bloxbean.cardano.julc.stdlib.annotation.Param;
Expand Down Expand Up @@ -111,13 +112,8 @@ static boolean handleOnBehalf(TxInfo txInfo, TxOutRef utxo, OnBehalf ob) {
static byte[] buildExpectedMessage(byte[] signerPkhHex, byte[] submitterKeyHashHex, BigInteger ttl) {
byte[] colon = ByteStringLib.cons(58, ByteStringLib.empty()); // ':'
byte[] ttlStr = ByteStringLib.intToDecimalString(ttl);
return ByteStringLib.append(
ByteStringLib.append(
ByteStringLib.append(
ByteStringLib.append(signerPkhHex, colon),
submitterKeyHashHex),
colon),
ttlStr);
return Builtins.concat(
signerPkhHex, colon, submitterKeyHashHex, colon, ttlStr);
}

static BigInteger sumLovelaceAtWhitelistedScripts(JulcList<TxOut> outputs) {
Expand Down
5 changes: 2 additions & 3 deletions src/main/java/com/example/uverify/onchain/UVerifyV1.java
Original file line number Diff line number Diff line change
Expand Up @@ -453,8 +453,7 @@ static boolean certificateIsValid(UVerifyCertificate cert, JulcList<PubKeyHash>
}

static byte[] certificateToByteArray(UVerifyCertificate cert) {
byte[] base = ByteStringLib.append(
ByteStringLib.append(cert.hash(), cert.algorithm()), cert.issuer());
byte[] base = Builtins.concat(cert.hash(), cert.algorithm(), cert.issuer());
byte[] result = base;
for (var extra : cert.extra()) {
result = ByteStringLib.append(result, extra);
Expand All @@ -467,4 +466,4 @@ static boolean oneOfAdminKeysSigned(JulcList<PubKeyHash> signatories) {
sig.hash().equals(adminKey1) || sig.hash().equals(adminKey2));
}

}
}
1 change: 0 additions & 1 deletion src/main/java/com/example/validators/AuctionValidator.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.example.validators;

import com.bloxbean.cardano.julc.stdlib.annotation.SpendingValidator;
import com.bloxbean.cardano.julc.stdlib.annotation.Validator;
import com.bloxbean.cardano.julc.stdlib.annotation.Entrypoint;
import com.bloxbean.cardano.julc.ledger.ScriptContext;
import com.bloxbean.cardano.julc.ledger.TxInfo;
Expand Down
1 change: 0 additions & 1 deletion src/main/java/com/example/validators/EscrowValidator.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.example.validators;

import com.bloxbean.cardano.julc.stdlib.annotation.SpendingValidator;
import com.bloxbean.cardano.julc.stdlib.annotation.Validator;
import com.bloxbean.cardano.julc.stdlib.annotation.Entrypoint;
import com.bloxbean.cardano.julc.ledger.ScriptContext;
import com.bloxbean.cardano.julc.ledger.TxInfo;
Expand Down
1 change: 0 additions & 1 deletion src/main/java/com/example/validators/MultiSigMinting.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.example.validators;

import com.bloxbean.cardano.julc.stdlib.annotation.MintingPolicy;
import com.bloxbean.cardano.julc.stdlib.annotation.Entrypoint;
import com.bloxbean.cardano.julc.stdlib.annotation.MintingValidator;
import com.bloxbean.cardano.julc.ledger.ScriptContext;
Expand Down
53 changes: 49 additions & 4 deletions src/main/java/com/example/validators/OneShotMintPolicy.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@
import com.bloxbean.cardano.julc.stdlib.annotation.MintingValidator;
import com.bloxbean.cardano.julc.stdlib.annotation.Param;
import com.bloxbean.cardano.julc.ledger.ScriptContext;
import com.bloxbean.cardano.julc.ledger.ScriptInfo;
import com.bloxbean.cardano.julc.ledger.TxInfo;
import com.bloxbean.cardano.julc.ledger.TxOutRef;
import com.bloxbean.cardano.julc.ledger.Value;
import com.bloxbean.cardano.julc.stdlib.lib.ContextsLib;
import com.bloxbean.cardano.julc.stdlib.lib.ValuesLib;
import com.bloxbean.cardano.julc.stdlib.Builtins;
import com.bloxbean.cardano.julc.core.PlutusData;

Expand All @@ -24,16 +27,58 @@ public class OneShotMintPolicy {
@Entrypoint
public static boolean validate(PlutusData redeemer, ScriptContext ctx) {
TxInfo txInfo = ctx.txInfo();
ScriptInfo.MintingScript minting = (ScriptInfo.MintingScript) ctx.scriptInfo();
byte[] ownPolicyId = Builtins.toByteString(minting.policyId());

ContextsLib.trace("Checking UTXO input");
boolean found = false;
boolean validMint = false;
for (var input : txInfo.inputs()) {
TxOutRef ref = input.outRef();
byte[] refTxIdBytes = Builtins.toByteString(ref.txId());
found = Builtins.equalsByteString(refTxIdBytes, utxoTxId) && ref.index().compareTo(utxoIndex) == 0;
if (found) {
boolean consumesSeed = Builtins.equalsByteString(refTxIdBytes, utxoTxId)
&& ref.index().compareTo(utxoIndex) == 0;
if (consumesSeed) {
byte[] expectedTokenName = ValuesLib.uniqueTokenName(ref);
validMint = mintsOnlyCanonicalToken(
txInfo.mint(), ownPolicyId, expectedTokenName);
break; // ← break is separate from assignment
}
}
return found;
return validMint;
}

/**
* Require exactly one asset under this policy: the canonical token derived
* from the consumed seed reference, with quantity one.
*/
static boolean mintsOnlyCanonicalToken(
Value mint, byte[] ownPolicyId, byte[] expectedTokenName) {
PlutusData policies = Builtins.unMapData(mint);
PlutusData ownPolicyData = Builtins.bData(ownPolicyId);
boolean validMint = false;

while (!Builtins.nullList(policies)) {
var policy = Builtins.headList(policies);
if (Builtins.equalsData(Builtins.fstPair(policy), ownPolicyData)) {
PlutusData assets = Builtins.unMapData(
(PlutusData.MapData) Builtins.sndPair(policy));

if (!Builtins.nullList(assets)) {
var asset = Builtins.headList(assets);
boolean exactlyOneAsset =
Builtins.nullList(Builtins.tailList(assets));
boolean expectedName = Builtins.equalsData(
Builtins.fstPair(asset), Builtins.bData(expectedTokenName));
boolean expectedQuantity = Builtins.unIData(
Builtins.sndPair(asset)).compareTo(BigInteger.ONE) == 0;

validMint = exactlyOneAsset && expectedName && expectedQuantity;
}
break;
}
policies = Builtins.tailList(policies);
}

return validMint;
}
}
2 changes: 0 additions & 2 deletions src/main/java/com/example/validators/VestingValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,11 @@

import com.bloxbean.cardano.julc.stdlib.annotation.Param;
import com.bloxbean.cardano.julc.stdlib.annotation.SpendingValidator;
import com.bloxbean.cardano.julc.stdlib.annotation.Validator;
import com.bloxbean.cardano.julc.stdlib.annotation.Entrypoint;
import com.bloxbean.cardano.julc.ledger.ScriptContext;
import com.bloxbean.cardano.julc.ledger.TxInfo;
import com.bloxbean.cardano.julc.ledger.PubKeyHash;
import com.bloxbean.cardano.julc.stdlib.lib.ContextsLib;
//import com.bloxbean.cardano.julc.stdlib.lib.ValuesLib;
import com.bloxbean.cardano.julc.stdlib.lib.ValuesLib;
import com.example.util.SumTest;

Expand Down
Loading
Loading