Rivellum

Rivellum Portal

Checking...
testnet

Contract IR Quick Reference

At a Glance

Status: āœ… Implemented and tested
VM Backend: NoopVm (placeholder for Move 2/WASM)
Breaking Changes: None
Tests Passing: 38/38

Quick Start

1. Deploy a Contract

use rivellum_types::{PlainPayload, Address};
use rivellum_intents::Intent;
use rivellum_crypto::generate_keypair;
use rivellum_execution::SimpleExecutor;
use rivellum_ledger::InMemoryState;

let keypair = generate_keypair();
let bytecode = vec![0xCA, 0xFE, 0xBA, 0xBE];

// Create deploy intent
let intent = Intent::new_plain(
    PlainPayload::DeployContract { code: bytecode.clone() },
    &keypair,
    1,
    1000,
);

// Execute deployment
let executor = SimpleExecutor::new(100);
let mut state = InMemoryState::new();
let contract_addr = executor.execute_deploy(
    bytecode,
    &intent.sender,
    intent.nonce,
    &mut state,
)?;

println!("Deployed at: {:?}", contract_addr);

2. Call a Contract

use rivellum_types::{PlainPayload, ContractAddress};

let contract = ContractAddress([0x12; 32]);

let intent = Intent::new_plain(
    PlainPayload::CallContract {
        contract,
        entrypoint: "transfer".to_string(),
        args: vec![vec![0x01], vec![0x64]],
    },
    &keypair,
    2,
    1000,
);

executor.execute_call(
    intent.sender,
    contract,
    "transfer",
    &[vec![0x01], vec![0x64]],
    1000,
    &state,
    &mut state,
)?;

3. Custom VM

use rivellum_execution::vm::{ContractVm, VmContext};
use rivellum_types::{ContractAddress, ContractCode, contract_address_from_sender};

struct MyVm;

impl ContractVm for MyVm {
    fn deploy(&self, code: Vec<u8>, deployer: &Address, nonce: u64, state: &mut dyn StateWrite) 
        -> anyhow::Result<ContractAddress> 
    {
        let addr = contract_address_from_sender(deployer, nonce);
        state.put_contract(addr, ContractCode::new(code));
        Ok(addr)
    }
    
    fn call(&self, ctx: &mut VmContext<'_>, entrypoint: &str, args: &[Vec<u8>]) 
        -> anyhow::Result<()> 
    {
        // Your VM logic here
        ctx.gas_meter.consume(100)?;
        Ok(())
    }
}

let executor = SimpleExecutor::with_vm(100, Box::new(MyVm));

Key Types

Contract Types

  • ContractAddress: 32-byte deterministic address
  • ContractCode: Code hash + bytecode
  • contract_address_from_sender(sender, nonce): Compute address

Payload Variants

PlainPayload::DeployContract { code: Vec<u8> }
PlainPayload::CallContract { contract, entrypoint, args }

VM Interface

trait ContractVm {
    fn deploy(...) -> Result<ContractAddress>;
    fn call(...) -> Result<()>;
}

State Methods

trait StateView {
    fn get_contract(&self, addr) -> Option<ContractCode>;
    fn get_contract_storage(&self, addr, key) -> Option<Vec<u8>>;
}

trait StateWrite {
    fn put_contract(&mut self, addr, code);
    fn put_contract_storage(&mut self, addr, key, value);
}

Testing

# Run all tests
cargo test --workspace --lib

# Run just VM tests
cargo test --package rivellum-execution --lib vm

# Check compilation
cargo build --workspace

Files Changed

Core Implementation:

  • crates/rivellum-types/src/lib.rs - Contract types
  • crates/rivellum-ledger/src/lib.rs - State storage
  • crates/rivellum-execution/src/lib.rs - Executor integration
  • crates/rivellum-execution/src/vm.rs - VM interface (NEW)

Documentation:

  • docs/CONTRACT_IR_LAYER.md - Full guide
  • docs/CONTRACT_IR_IMPLEMENTATION.md - Implementation summary
  • examples/deploy_contract.py - Python example

What Works Now

āœ… Contract deployment via intents
āœ… Contract calls via intents
āœ… Deterministic address computation
āœ… Contract code storage
āœ… Contract storage (key-value)
āœ… Gas metering (stub)
āœ… Execution traces with contract ops
āœ… NoopVm for testing
āœ… All existing transfer logic

What's Next

  1. Node Integration: Update apply_intent to call execute_deploy/execute_call
  2. RPC APIs: Add /contracts/:addr endpoints
  3. Explorer: Show deployed contracts
  4. Move 2: Integrate move-vm-runtime
  5. Gas: Replace stub with real gas accounting

Common Patterns

Check if Contract Exists

if let Some(code) = state.get_contract(&addr) {
    println!("Code hash: {:?}", code.code_hash);
}

Read Contract Storage

let key = b"balance";
if let Some(value) = state.get_contract_storage(&addr, key) {
    let balance = u128::from_le_bytes(value.try_into().unwrap());
}

Write Contract Storage

let key = b"balance";
let value = 100u128.to_le_bytes().to_vec();
state.put_contract_storage(&addr, key, value);

Troubleshooting

Q: Can I deploy real Move contracts now?
A: Not yet. Currently using NoopVm placeholder. Move 2 integration is next step.

Q: Will existing transfer intents break?
A: No. All existing functionality preserved. Transfers work exactly as before.

Q: How do I swap in a real VM?
A: Implement ContractVm trait, then use SimpleExecutor::with_vm(fee, Box::new(YourVm)).

Q: Where are contract addresses stored?
A: Computed deterministically via BLAKE3(sender || nonce). No address registry needed.

Q: Can contracts call other contracts?
A: Not yet. Inter-contract calls will be added when integrating real VM.

References

  • Full documentation: docs/CONTRACT_IR_LAYER.md
  • Implementation details: docs/CONTRACT_IR_IMPLEMENTATION.md
  • Python example: examples/deploy_contract.py
  • VM module: crates/rivellum-execution/src/vm.rs