Complete documentation page mirrored and translated for learning. Attribution is shown at the bottom of each article.

阅读中文版

appdevmodulesm2-smart-contract-paradigm

Smart Contract Paradigm Shift

Documentation Index

Fetch the complete documentation index at: https://docs.canton.network/llms.txt Use this file to discover all available pages before exploring further.

Smart Contract Paradigm Shift

Understanding the fundamental differences between Solidity and Daml programming models

Moving from Solidity to Daml requires a significant mental shift. This page explains the paradigm differences and how to adapt your thinking.

Programming Model Comparison

AspectSolidityDaml
ParadigmImperative, object-orientedFunctional, declarative
StateMutable storageImmutable contracts
ExecutionSequential operationsTransaction trees
TypesStatic with dynamic callsStrongly typed, ADTs
Side effectsUnlimitedControlled via monads

State Model: Mutable vs. Immutable

Solidity: Mutable State

In Solidity, contracts have mutable storage that you modify directly:

contract Token {
    mapping(address => uint256) public balances;

    function transfer(address to, uint256 amount) public {
        require(balances[msg.sender] >= amount, "Insufficient balance");

        // Mutate state in place
        balances[msg.sender] -= amount;
        balances[to] += amount;

        emit Transfer(msg.sender, to, amount);
    }
}

Mental model: The contract is a persistent object with state you modify.

Daml: Immutable Contracts

In Daml, contracts are immutable data. State changes create new contracts or archive existing contracts:

template Token
  with
    owner : Party
    issuer : Party
    amount : Decimal
  where
    signatory issuer
    observer owner

    choice Transfer : ContractId Token
      with
        newOwner : Party
        transferAmount : Decimal
      controller owner
      do
        -- This contract will be archived
        -- Create new contracts for the split
        create Token with owner = newOwner, issuer, amount = transferAmount
        create this with amount = amount - transferAmount

Mental model: Contracts are facts. Exercise archives the fact and creates new facts.

UTXO vs. Account Model

Ethereum: Account Model

  • State is a global mapping of accounts to balances
  • Transfers modify account entries
  • Easy to query total balance
  • Contention on popular accounts

Canton: Extended UTXO Model

  • State is a set of contracts (like unspent outputs)
  • Transfers archive existing contracts, create new ones
  • Balance is sum of owned contracts
  • Better parallelism, explicit data flow
-- Canton: Holdings are individual contracts
-- Alice's total balance = sum of all Token contracts where owner = Alice

-- Query: Find all my tokens
myTokens <- queryContractKey @Token myParty
totalBalance <- pure $ sum [amount | Token{amount} <- myTokens]

Language Comparison

Type System

FeatureSolidityDaml
Type safetyModerateStrong
Null handlingImplicit (0/empty)Explicit (Optional)
Custom typesStructs, enumsADTs, records
GenericsLimitedFull parametric polymorphism

Solidity Types

struct Asset {
    address owner;
    uint256 value;
    bool isLocked;
}

enum State { Pending, Active, Completed }

Daml Types

-- Record type (like struct)
data Asset = Asset with
  owner : Party
  value : Decimal
  isLocked : Bool

-- Sum type (algebraic data type)
data AssetState
  = Pending
  | Active with activatedAt : Time
  | Completed with result : Text

-- Optional (explicit null handling)
data MaybeApprover = Some Party | None

Solidity Control Flow

function process(uint256[] memory items) public {
    for (uint i = 0; i < items.length; i++) {
        if (items[i] > threshold) {
            revert("Over threshold");
        }
        results[i] = items[i] * 2;
    }
}

Daml Control Flow

process : [Decimal] -> Update [Decimal]
process items = do
  forA items \item -> do
    assertMsg "Over threshold" (item <= threshold)
    pure (item * 2.0)

Authorization Model

Solidity: Runtime Authorization

contract Ownable {
    address public owner;

    modifier onlyOwner() {
        require(msg.sender == owner, "Not owner");
        _;
    }

    function sensitiveAction() public onlyOwner {
        // Only owner can call
    }
}

Issues:

  • Authorization checked at runtime
  • Easy to forget checks
  • Anyone can attempt the call
  • Authorization mixed with logic

Daml: Declarative Authorization

template OwnedAsset
  with
    owner : Party
    data : Text
  where
    signatory owner  -- owner must authorize creation

    choice SensitiveAction : ()
      controller owner  -- only owner can exercise
      do
        -- Protocol enforces: only owner can reach here
        pure ()

Benefits:

  • Authorization declared, not coded
  • Impossible to forget (compiler enforces)
  • Only authorized parties can attempt
  • Clear separation of concerns

Multi-Party Coordination

Solidity: Manual Multi-Sig

contract MultiSig {
    mapping(address => bool) public approved;
    uint256 public approvalCount;
    uint256 public requiredApprovals;

    function approve() public {
        require(!approved[msg.sender], "Already approved");
        approved[msg.sender] = true;
        approvalCount++;
    }

    function execute() public {
        require(approvalCount >= requiredApprovals, "Not enough approvals");
        // Execute action
    }
}

Daml: Native Multi-Party

template Agreement
  with
    partyA : Party
    partyB : Party
    terms : Text
  where
    signatory partyA, partyB  -- Both must sign to create

-- Proposal pattern for gathering signatures
template AgreementProposal
  with
    proposer : Party
    counterparty : Party
    terms : Text
  where
    signatory proposer
    observer counterparty

    choice Accept : ContractId Agreement
      controller counterparty
      do create Agreement with
           partyA = proposer
           partyB = counterparty
           terms

Common Patterns Translated

Solidity PatternDaml Equivalent
OwnableSignatory declaration
PausableContract archival + recreation
ERC-20Token Standard (CIP-0056)
Proxy/UpgradeableSmart Contract Upgrade (SCU)
Pull paymentPropose/accept pattern
FactoryTemplate + create
RegistryContract keys (when available)

What to Unlearn

Solidity HabitDaml Reality
Mutate state in placeArchive + create new contracts
Runtime msg.sender checksCompile-time controller declarations
Public functions anyone can callOnly controllers can exercise
Global contract addressContract IDs change on every update
Loops for iterationUse forA, mapA, fold patterns
Try/catch everywhereAssertions for validation; exceptions are deprecated

Next Steps

Compare network architecture and topology. Start writing Daml smart contracts.

Mirrored from Canton Network official documentation (CC-BY-4.0) by CC Privacy Club for learning purposes.