V2 Hard Fork (Radiant Core 2.1, Block 410,000): Six new/re-enabled opcodes are available post-fork: OP_BLAKE3 (
0xee), OP_K12 (0xef) for on-chain hash verification; OP_LSHIFT (0x98), OP_RSHIFT (0x99) for bitwise shifts; OP_2MUL (0x8d), OP_2DIV (0x8e) for numeric scaling. Use these for on-chain PoW validation (dMint v2) and ASERT-lite DAA computation. Contracts using these opcodes must only be deployed after the activation height.
// ✅ GOOD: Simple, focused contract
contract SimpleTransfer(bytes20 OWNER_PKH)
function (sig s, pubkey pk) {
require(hash160(pk) == OWNER_PKH);
require(checkSig(s, pk));
}
// ❌ BAD: Overly complex single contract
contract DoEverything(...)
function (...) {
// 500 lines of logic — hard to audit
}
| Contract Type | Responsibility |
|---|---|
| Token Contract | Hold and transfer value |
| Minting Contract | Create new tokens |
| Swap Contract | Exchange tokens |
| Governance Contract | Voting and proposals |
// ✅ GOOD: Explicit checks
function transfer(sig s, pubkey pk, int amount) {
require(amount > 0);
require(amount <= tx.inputs[this.activeInputIndex].value);
require(hash160(pk) == OWNER_PKH);
require(checkSig(s, pk));
}
// ❌ BAD: Relying on implicit behavior
function transfer(sig s, pubkey pk, int amount) {
require(checkSig(s, pk)); // Assumes amount is valid
}
function (sig s, pubkey pk) {
require(hash160(pk) == OWNER_PKH);
require(checkSig(s, pk));
}
// 2-of-3 multisig pattern
contract MultiSig(pubkey PK1, pubkey PK2, pubkey PK3)
function (sig s1, sig s2) {
int valid = 0;
if (checkSig(s1, PK1)) valid = valid + 1;
if (checkSig(s2, PK2)) valid = valid + 1;
if (checkSig(s1, PK3) || checkSig(s2, PK3)) valid = valid + 1;
require(valid >= 2);
}
contract TimeLock(bytes20 OWNER_PKH, int UNLOCK_TIME)
function (sig s, pubkey pk) {
require(tx.time >= UNLOCK_TIME);
require(hash160(pk) == OWNER_PKH);
require(checkSig(s, pk));
}
// ✅ GOOD: Explicit conservation check
contract FungibleToken(bytes36 REF, bytes20 OWNER_PKH)
function (sig s, pubkey pk) {
require(hash160(pk) == OWNER_PKH);
require(checkSig(s, pk));
stateSeparator;
bytes36 ref = pushInputRef(REF);
bytes32 csh = hash256(tx.inputs[this.activeInputIndex].codeScript);
// CONSERVATION: inputs >= outputs
int inputSum = tx.inputs.codeScriptValueSum(csh);
int outputSum = tx.outputs.codeScriptValueSum(csh);
require(inputSum >= outputSum);
}
Verify that the parent UTXO used the same contract code. Since the parent also verified its parent, this creates an inductive chain guaranteeing all ancestors followed the same rules:
// ✅ GOOD: Code-continuity induction step
bytes32 csh = hash256(tx.inputs[this.activeInputIndex].codeScript);
require(tx.outputs.codeScriptCount(csh) >= 1);
require(tx.inputs.codeScriptValueSum(csh) >= tx.outputs.codeScriptValueSum(csh));
// ✅ GOOD: Prevent excessive fee extraction
int fee = tx.state.inputSum - tx.state.outputSum;
require(fee >= 0);
require(fee <= maxFee);
// ✅ SAFE: Pre-check for overflow
function safeCalculation(int a, int b) {
require(a >= 0 && b >= 0);
require(a <= 9223372036854775807 / b); // MAX_INT64 / b
int result = a * b;
}
contract FungibleToken(bytes36 REF, bytes20 OWNER_PKH)
function (sig s, pubkey pk) {
require(hash160(pk) == OWNER_PKH);
require(checkSig(s, pk));
stateSeparator;
bytes36 ref = pushInputRef(REF);
bytes32 csh = hash256(tx.inputs[this.activeInputIndex].codeScript);
require(tx.inputs.codeScriptValueSum(csh) >= tx.outputs.codeScriptValueSum(csh));
require(tx.outputs.refOutputCount(ref) == tx.outputs.codeScriptOutputCount(csh));
}
contract NFT(bytes36 REF, bytes20 OWNER_PKH)
function (sig s, pubkey pk) {
require(hash160(pk) == OWNER_PKH);
require(checkSig(s, pk));
stateSeparator;
bytes36 ref = pushInputRefSingleton(REF);
require(tx.outputs.refOutputCount(ref) == 1);
}
contract RoyaltyNFT(
bytes36 REF, bytes20 OWNER_PKH,
bytes20 CREATOR_PKH, int ROYALTY_BPS
)
function (sig s, pubkey pk) {
require(hash160(pk) == OWNER_PKH);
require(checkSig(s, pk));
stateSeparator;
bytes36 ref = pushInputRefSingleton(REF);
require(tx.outputs.refOutputCount(ref) == 1);
int inputValue = tx.inputs[this.activeInputIndex].value;
int outputValue = tx.outputs[0].value;
if (outputValue < inputValue) {
int saleProceeds = inputValue - outputValue;
int royalty = saleProceeds * ROYALTY_BPS / 10000;
// Verify royalty payment to creator...
}
}
describe('FungibleToken', () => {
it('should allow owner to transfer', async () => {
const tx = await contract.functions.transfer(sig, pk, amount);
expect(tx).to.exist;
});
it('should reject non-owner transfer', async () => {
await expect(contract.functions.transfer(wrongSig, wrongPk, amount)).to.be.rejected;
});
it('should enforce conservation', async () => {
await expect(contract.functions.transfer(sig, pk, inputAmount + 1)).to.be.rejected;
});
});
# Debug a specific transaction
rxdeb --tx=<spending_tx_hex> --txin=<input_tx_hex>
# Debug RadiantScript artifact
rxdeb --artifact=MyContract.json --function=transfer --args='["sig", "pk"]'
# Step through execution
rxdeb> step
rxdeb> stack
rxdeb> source
| Limit | Value | Notes |
|---|---|---|
| Max script size | 32 MB | Consensus limit |
| Recommended max | 10 KB | Reasonable fees |
| Optimal | < 1 KB | Most efficient |
// ✅ GOOD: Reuse computed values
bytes32 csh = hash256(tx.inputs[this.activeInputIndex].codeScript);
int inputSum = tx.inputs.codeScriptValueSum(csh);
int outputSum = tx.outputs.codeScriptValueSum(csh);
// ❌ BAD: Recompute same values
require(tx.inputs.codeScriptValueSum(hash256(...)) >= ...);
require(tx.outputs.codeScriptValueSum(hash256(...)) <= ...);
// ❌ VULNERABLE: Attacker can create infinite tokens!
contract BadToken(bytes36 REF, bytes20 OWNER_PKH)
function (sig s, pubkey pk) {
require(checkSig(s, pk));
stateSeparator;
pushInputRef(REF);
// Missing: inputSum >= outputSum check
}
// ❌ VULNERABLE: Using normal ref for NFT allows duplication
pushInputRef(REF); // Should be pushInputRefSingleton!
// ❌ VULNERABLE: No overflow check
int result = a * b; // Can overflow!
// ✅ SAFE: Pre-check
require(a <= 9223372036854775807 / b);
int result = a * b;
// ❌ VULNERABLE: Accepts any outputs — attacker can drain funds
// ✅ SAFE: Validate outputs explicitly
require(tx.outputs[0].value >= expectedOutput);
require(tx.outputs[0].lockingBytecode == expectedScript);
# 1. Compile with all checks
npx rxdc MyContract.rxd -o MyContract.json --debug
# 2. Run full test suite
npm test
# 3. Audit the compiled output
rxdeb --artifact=MyContract.json --decode
# 4. Check script size
npx rxdc MyContract.rxd --size
Do's ✅
Don'ts ❌
For debugging help, see the rxdeb documentation. For security audits, contact the Radiant community.