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
| Aspect | Solidity | Daml |
|---|---|---|
| Paradigm | Imperative, object-oriented | Functional, declarative |
| State | Mutable storage | Immutable contracts |
| Execution | Sequential operations | Transaction trees |
| Types | Static with dynamic calls | Strongly typed, ADTs |
| Side effects | Unlimited | Controlled 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
| Feature | Solidity | Daml |
|---|---|---|
| Type safety | Moderate | Strong |
| Null handling | Implicit (0/empty) | Explicit (Optional) |
| Custom types | Structs, enums | ADTs, records |
| Generics | Limited | Full 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 Pattern | Daml Equivalent |
|---|---|
| Ownable | Signatory declaration |
| Pausable | Contract archival + recreation |
| ERC-20 | Token Standard (CIP-0056) |
| Proxy/Upgradeable | Smart Contract Upgrade (SCU) |
| Pull payment | Propose/accept pattern |
| Factory | Template + create |
| Registry | Contract keys (when available) |
What to Unlearn
| Solidity Habit | Daml Reality |
|---|---|
| Mutate state in place | Archive + create new contracts |
Runtime msg.sender checks | Compile-time controller declarations |
| Public functions anyone can call | Only controllers can exercise |
| Global contract address | Contract IDs change on every update |
| Loops for iteration | Use forA, mapA, fold patterns |
| Try/catch everywhere | Assertions for validation; exceptions are deprecated |
Next Steps
Mirrored from Canton Network official documentation (CC-BY-4.0) by CC Privacy Club for learning purposes.