# Tutorial 3: Smart Contracts on Synor

Learn to build, deploy, and interact with smart contracts on Synor's WebAssembly-based contract system.

## What You'll Learn

- Write a contract in Rust
- Compile to WebAssembly
- Deploy to Synor
- Call contract methods
- Handle state and events

## Prerequisites

- Completed [Tutorial 1: Getting Started](./01-getting-started.md)
- Rust installed (`rustup`)
- `wasm32-unknown-unknown` target

---

## Part 1: Understanding Synor Contracts

### How Contracts Work

```
┌─────────────────────────────────────────────┐
│           Synor Smart Contract               │
├─────────────────────────────────────────────┤
│  Written in: Rust (compiled to WASM)        │
│  Execution: Deterministic sandbox           │
│  State: Key-value storage                   │
│  Gas: Metered execution                     │
│  Size limit: 256 KB compiled WASM           │
└─────────────────────────────────────────────┘
```

### Contract Capabilities

| Feature | Support |
|---------|---------|
| State storage | ✅ Key-value store |
| Cross-contract calls | ✅ Via host functions |
| Events/Logs | ✅ Emit events |
| Token transfers | ✅ Native SYNOR |
| Cryptography | ✅ Blake3, Ed25519 |
| Time access | ✅ Block timestamp |
| Randomness | ❌ Not yet (determinism) |

---

## Part 2: Setting Up

### Install the Contract SDK

```bash
# Add the WASM target
rustup target add wasm32-unknown-unknown

# Install the Synor contract CLI
cargo install synor-contract-cli
```

### Create a New Contract Project

```bash
synor-contract new my_token
cd my_token
```

This creates:

```
my_token/
├── Cargo.toml
├── src/
│   └── lib.rs
└── tests/
    └── integration.rs
```

---

## Part 3: Writing Your First Contract

### A Simple Token Contract

Edit `src/lib.rs`:

```rust
//! A simple fungible token contract.

#![no_std]

use synor_contract_sdk::prelude::*;

// Contract state
#[derive(Default)]
struct Token {
    name: String,
    symbol: String,
    decimals: u8,
    total_supply: u128,
    balances: Map<Address, u128>,
    allowances: Map<(Address, Address), u128>,
}

// Contract implementation
#[synor_contract]
impl Token {
    /// Initialize the token contract
    #[init]
    pub fn new(name: String, symbol: String, decimals: u8, initial_supply: u128) -> Self {
        let caller = env::caller();

        let mut balances = Map::new();
        balances.insert(caller, initial_supply);

        emit!(TokenCreated {
            name: name.clone(),
            symbol: symbol.clone(),
            total_supply: initial_supply,
        });

        Token {
            name,
            symbol,
            decimals,
            total_supply: initial_supply,
            balances,
            allowances: Map::new(),
        }
    }

    /// Get token name
    #[view]
    pub fn name(&self) -> String {
        self.name.clone()
    }

    /// Get token symbol
    #[view]
    pub fn symbol(&self) -> String {
        self.symbol.clone()
    }

    /// Get decimals
    #[view]
    pub fn decimals(&self) -> u8 {
        self.decimals
    }

    /// Get total supply
    #[view]
    pub fn total_supply(&self) -> u128 {
        self.total_supply
    }

    /// Get balance of address
    #[view]
    pub fn balance_of(&self, owner: Address) -> u128 {
        self.balances.get(&owner).copied().unwrap_or(0)
    }

    /// Transfer tokens to another address
    #[mutate]
    pub fn transfer(&mut self, to: Address, amount: u128) -> bool {
        let from = env::caller();

        let from_balance = self.balance_of(from);
        require!(from_balance >= amount, "Insufficient balance");

        self.balances.insert(from, from_balance - amount);
        let to_balance = self.balance_of(to);
        self.balances.insert(to, to_balance + amount);

        emit!(Transfer { from, to, amount });

        true
    }

    /// Approve spender to transfer tokens
    #[mutate]
    pub fn approve(&mut self, spender: Address, amount: u128) -> bool {
        let owner = env::caller();
        self.allowances.insert((owner, spender), amount);

        emit!(Approval { owner, spender, amount });

        true
    }

    /// Get allowance
    #[view]
    pub fn allowance(&self, owner: Address, spender: Address) -> u128 {
        self.allowances.get(&(owner, spender)).copied().unwrap_or(0)
    }

    /// Transfer tokens on behalf of owner
    #[mutate]
    pub fn transfer_from(&mut self, from: Address, to: Address, amount: u128) -> bool {
        let spender = env::caller();

        let allowed = self.allowance(from, spender);
        require!(allowed >= amount, "Allowance exceeded");

        let from_balance = self.balance_of(from);
        require!(from_balance >= amount, "Insufficient balance");

        // Update balances
        self.balances.insert(from, from_balance - amount);
        let to_balance = self.balance_of(to);
        self.balances.insert(to, to_balance + amount);

        // Update allowance
        self.allowances.insert((from, spender), allowed - amount);

        emit!(Transfer { from, to, amount });

        true
    }

    /// Mint new tokens (only for demonstration)
    #[mutate]
    pub fn mint(&mut self, to: Address, amount: u128) {
        // In production, add access control!
        let balance = self.balance_of(to);
        self.balances.insert(to, balance + amount);
        self.total_supply += amount;

        emit!(Transfer {
            from: Address::zero(),
            to,
            amount
        });
    }

    /// Burn tokens
    #[mutate]
    pub fn burn(&mut self, amount: u128) {
        let caller = env::caller();
        let balance = self.balance_of(caller);
        require!(balance >= amount, "Insufficient balance");

        self.balances.insert(caller, balance - amount);
        self.total_supply -= amount;

        emit!(Transfer {
            from: caller,
            to: Address::zero(),
            amount
        });
    }
}

// Events
#[event]
struct TokenCreated {
    name: String,
    symbol: String,
    total_supply: u128,
}

#[event]
struct Transfer {
    from: Address,
    to: Address,
    amount: u128,
}

#[event]
struct Approval {
    owner: Address,
    spender: Address,
    amount: u128,
}
```

---

## Part 4: Building the Contract

### Compile to WASM

```bash
# Build optimized WASM
synor-contract build --release

# Output:
# Compiled: target/wasm32-unknown-unknown/release/my_token.wasm
# Size: 45.2 KB
# Hash: 0x1234...
```

### Verify the Build

```bash
# Check contract info
synor-contract info target/wasm32-unknown-unknown/release/my_token.wasm

# Output:
# Contract: my_token
# Methods: new, name, symbol, decimals, total_supply, balance_of, transfer, approve, allowance, transfer_from, mint, burn
# View methods: name, symbol, decimals, total_supply, balance_of, allowance
# Mutate methods: transfer, approve, transfer_from, mint, burn
```

---

## Part 5: Deploying the Contract

### Using the CLI

```bash
# Deploy to testnet
synor-contract deploy \
  --wasm target/wasm32-unknown-unknown/release/my_token.wasm \
  --init "new" \
  --args '"MyToken","MTK",18,1000000000000000000000' \
  --network testnet

# Output:
# Contract deployed!
# Address: tsynor1contract_address...
# Transaction: 0xabc123...
# Gas used: 150000
```

### Using JavaScript

```javascript
import { deployContract } from '@synor/sdk';
import fs from 'fs';

async function deploy(wallet) {
  const wasm = fs.readFileSync('./my_token.wasm');

  const result = await deployContract(wallet, {
    wasm,
    method: 'new',
    args: ['MyToken', 'MTK', 18, '1000000000000000000000'],
  });

  console.log('Contract address:', result.contractAddress);
  return result.contractAddress;
}
```

---

## Part 6: Interacting with the Contract

### Read Contract State (View Methods)

```javascript
import { callContractView } from '@synor/sdk';

// Get token info
const name = await callContractView(contractAddress, 'name', []);
console.log('Token name:', name); // "MyToken"

// Get balance
const balance = await callContractView(
  contractAddress,
  'balance_of',
  [myAddress]
);
console.log('Balance:', balance);
```

### Write to Contract (Mutate Methods)

```javascript
import { callContractMutate } from '@synor/sdk';

// Transfer tokens
const tx = await callContractMutate(wallet, contractAddress, 'transfer', [
  recipientAddress,
  '100000000000000000000', // 100 tokens with 18 decimals
]);

console.log('Transfer TX:', tx.id);
```

### Listen to Events

```javascript
const ws = new WebSocket('ws://localhost:17111');

ws.onopen = () => {
  ws.send(JSON.stringify({
    jsonrpc: '2.0',
    method: 'synor_subscribeContractEvents',
    params: [contractAddress],
    id: 1
  }));
};

ws.onmessage = (event) => {
  const msg = JSON.parse(event.data);
  if (msg.method === 'synor_contractEvent') {
    const { eventName, data } = msg.params;
    if (eventName === 'Transfer') {
      console.log(`Transfer: ${data.from} -> ${data.to}: ${data.amount}`);
    }
  }
};
```

---

## Part 7: Testing Your Contract

### Write Integration Tests

```rust
// tests/integration.rs

#[test]
fn test_token_transfer() {
    let mut contract = Token::new(
        "TestToken".to_string(),
        "TT".to_string(),
        18,
        1_000_000 * 10u128.pow(18),
    );

    let alice = Address::from_hex("0x1234...").unwrap();
    let bob = Address::from_hex("0x5678...").unwrap();

    // Alice transfers to Bob
    env::set_caller(alice);
    assert!(contract.transfer(bob, 100 * 10u128.pow(18)));

    // Check balances
    assert_eq!(contract.balance_of(bob), 100 * 10u128.pow(18));
}

#[test]
fn test_insufficient_balance() {
    let contract = Token::new(...);

    env::set_caller(bob); // Bob has no tokens
    assert!(!contract.transfer(alice, 100));
}
```

### Run Tests

```bash
cargo test
```

---

## Part 8: Best Practices

### Security Checklist

- [ ] Add access control (owner, admin roles)
- [ ] Validate all inputs
- [ ] Check for overflow (use checked_* operations)
- [ ] Test edge cases
- [ ] Audit before mainnet

### Gas Optimization

```rust
// Prefer: Direct storage access
let balance = self.balances.get(&addr);

// Avoid: Multiple reads
let b1 = self.balance_of(addr);
let b2 = self.balance_of(addr); // Wasteful!

// Prefer: Batch updates
self.balances.insert(from, from_balance - amount);
self.balances.insert(to, to_balance + amount);
```

### Error Handling

```rust
// Use require! for validation
require!(amount > 0, "Amount must be positive");
require!(balance >= amount, "Insufficient balance");

// Return Result for recoverable errors
pub fn safe_transfer(&mut self, to: Address, amount: u128) -> Result<(), &'static str> {
    if amount == 0 {
        return Err("Amount must be positive");
    }
    // ...
    Ok(())
}
```

---

## Example Contracts

For more examples, see:
- **NFT Contract:** `contracts/examples/nft/`
- **DEX Contract:** `contracts/examples/dex/`
- **Multisig:** `contracts/examples/multisig/`
- **DAO Governance:** `contracts/examples/dao/`

---

## What's Next

- [Tutorial 4: Working with the API](./04-api-guide.md)
- [Host Functions Reference](../contracts/HOST_FUNCTIONS.md)
- [Contract SDK Documentation](../contracts/SDK_REFERENCE.md)

---

*Next: [Working with the API](./04-api-guide.md)*
