# Migration Guide: Avoiding OpenZeppelin Dependencies

## Overview

This guide provides patterns and best practices for creating new contracts without OpenZeppelin dependencies, following the patterns used in the new WETH contracts.

## Patterns to Follow

### 1. Minimal IERC20 Interface

Instead of using OpenZeppelin's IERC20, use a minimal interface:

```solidity
// Minimal IERC20 interface for token operations
interface IERC20 {
    function transferFrom(address from, address to, uint256 amount) external returns (bool);
    function transfer(address to, uint256 amount) external returns (bool);
    function approve(address spender, uint256 amount) external returns (bool);
    function balanceOf(address account) external view returns (uint256);
}

// Usage
require(IERC20(token).transferFrom(msg.sender, address(this), amount), "Transfer failed");
require(IERC20(token).approve(spender, amount), "Approval failed");
```

**Reference**: `contracts/ccip/CCIPWETH9Bridge.sol`

---

### 2. Custom Admin Pattern

Instead of using OpenZeppelin's Ownable, use a custom admin pattern:

```solidity
contract MyContract {
    address public admin;
    
    modifier onlyAdmin() {
        require(msg.sender == admin, "MyContract: only admin");
        _;
    }
    
    constructor(address _admin) {
        require(_admin != address(0), "MyContract: zero address");
        admin = _admin;
    }
    
    function changeAdmin(address newAdmin) external onlyAdmin {
        require(newAdmin != address(0), "MyContract: zero address");
        admin = newAdmin;
    }
}
```

**Reference**: `contracts/ccip/CCIPWETH9Bridge.sol`

---

### 3. Standard ERC20 Calls

Instead of using SafeERC20, use standard ERC20 calls with require statements:

```solidity
// Instead of SafeERC20
// IERC20(token).safeTransferFrom(msg.sender, address(this), amount);

// Use standard ERC20 calls
require(IERC20(token).transferFrom(msg.sender, address(this), amount), "Transfer failed");
require(IERC20(token).approve(spender, amount), "Approval failed");
```

**Note**: This works for standard ERC20 tokens. If you need to handle non-standard tokens, consider using SafeERC20 or a try-catch pattern.

**Reference**: `contracts/ccip/CCIPWETH9Bridge.sol`

---

### 4. Error Handling

Always use require statements for error handling:

```solidity
// Good: Explicit error messages
require(amount > 0, "MyContract: invalid amount");
require(recipient != address(0), "MyContract: zero recipient");
require(balance >= amount, "MyContract: insufficient balance");

// Bad: Silent failures
if (amount == 0) return;
```

---

## Migration Checklist

### For New Contracts

- [ ] Use minimal IERC20 interface instead of OpenZeppelin's IERC20
- [ ] Use custom admin pattern instead of Ownable
- [ ] Use standard ERC20 calls instead of SafeERC20
- [ ] Add explicit error messages with require statements
- [ ] Test with standard ERC20 tokens
- [ ] Document any non-standard token requirements

### For Existing Contracts

- [ ] Identify OpenZeppelin dependencies
- [ ] Replace SafeERC20 with standard ERC20 calls
- [ ] Replace Ownable with custom admin pattern
- [ ] Replace IERC20 with minimal interface
- [ ] Update tests
- [ ] Verify security
- [ ] Update documentation

---

## Examples

### Example 1: Token Transfer Contract

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

// Minimal IERC20 interface
interface IERC20 {
    function transferFrom(address from, address to, uint256 amount) external returns (bool);
    function transfer(address to, uint256 amount) external returns (bool);
    function approve(address spender, uint256 amount) external returns (bool);
    function balanceOf(address account) external view returns (uint256);
}

contract TokenTransfer {
    address public admin;
    address public token;
    
    modifier onlyAdmin() {
        require(msg.sender == admin, "TokenTransfer: only admin");
        _;
    }
    
    constructor(address _admin, address _token) {
        require(_admin != address(0), "TokenTransfer: zero admin");
        require(_token != address(0), "TokenTransfer: zero token");
        admin = _admin;
        token = _token;
    }
    
    function transferTokens(address to, uint256 amount) external onlyAdmin {
        require(to != address(0), "TokenTransfer: zero recipient");
        require(amount > 0, "TokenTransfer: invalid amount");
        require(IERC20(token).transfer(to, amount), "TokenTransfer: transfer failed");
    }
    
    function changeAdmin(address newAdmin) external onlyAdmin {
        require(newAdmin != address(0), "TokenTransfer: zero address");
        admin = newAdmin;
    }
}
```

---

### Example 2: Cross-Chain Bridge

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

interface IERC20 {
    function transferFrom(address from, address to, uint256 amount) external returns (bool);
    function transfer(address to, uint256 amount) external returns (bool);
    function balanceOf(address account) external view returns (uint256);
}

contract CrossChainBridge {
    address public admin;
    address public token;
    address public router;
    
    modifier onlyAdmin() {
        require(msg.sender == admin, "CrossChainBridge: only admin");
        _;
    }
    
    constructor(address _admin, address _token, address _router) {
        require(_admin != address(0), "CrossChainBridge: zero admin");
        require(_token != address(0), "CrossChainBridge: zero token");
        require(_router != address(0), "CrossChainBridge: zero router");
        admin = _admin;
        token = _token;
        router = _router;
    }
    
    function bridgeTokens(address recipient, uint256 amount) external {
        require(recipient != address(0), "CrossChainBridge: zero recipient");
        require(amount > 0, "CrossChainBridge: invalid amount");
        require(IERC20(token).transferFrom(msg.sender, address(this), amount), "CrossChainBridge: transfer failed");
        // Bridge logic here
    }
    
    function changeAdmin(address newAdmin) external onlyAdmin {
        require(newAdmin != address(0), "CrossChainBridge: zero address");
        admin = newAdmin;
    }
}
```

---

## Best Practices

### 1. Always Validate Inputs

```solidity
// Good
require(amount > 0, "MyContract: invalid amount");
require(recipient != address(0), "MyContract: zero recipient");

// Bad
if (amount == 0) return;
```

### 2. Use Explicit Error Messages

```solidity
// Good
require(balance >= amount, "MyContract: insufficient balance");

// Bad
require(balance >= amount);
```

### 3. Check Return Values

```solidity
// Good
require(IERC20(token).transfer(to, amount), "Transfer failed");

// Bad
IERC20(token).transfer(to, amount);
```

### 4. Validate Addresses

```solidity
// Good
require(admin != address(0), "MyContract: zero address");

// Bad
admin = _admin;
```

---

## Security Considerations

### 1. Non-Standard ERC20 Tokens

Standard ERC20 calls may fail with non-standard tokens. If you need to handle non-standard tokens:

- Use SafeERC20 (requires OpenZeppelin)
- Use try-catch pattern
- Document token requirements

### 2. Access Control

Custom admin pattern provides same security as Ownable:

- Simple address-based access control
- Same security level
- No external dependencies

### 3. Error Handling

Always use require statements:

- Explicit error messages
- Revert on failure
- Gas-efficient

---

## Testing

### Test with Standard ERC20 Tokens

```solidity
function testTransfer() public {
    // Deploy standard ERC20 token
    ERC20 token = new ERC20();
    
    // Test transfer
    require(token.transfer(recipient, amount), "Transfer failed");
}
```

### Test Error Cases

```solidity
function testInvalidAmount() public {
    vm.expectRevert("MyContract: invalid amount");
    contract.transferTokens(recipient, 0);
}

function testZeroRecipient() public {
    vm.expectRevert("MyContract: zero recipient");
    contract.transferTokens(address(0), amount);
}
```

---

## References

### Contract Examples
- `contracts/ccip/CCIPWETH9Bridge.sol` - Minimal IERC20 interface, custom admin pattern
- `contracts/ccip/CCIPWETH10Bridge.sol` - Minimal IERC20 interface, custom admin pattern
- `contracts/tokens/WETH10.sol` - No external dependencies

### Documentation
- [Contract Inventory](./CONTRACT_INVENTORY.md)
- [OpenZeppelin Usage Analysis](./OPENZEPPELIN_USAGE_ANALYSIS.md)
- [Dependencies Guide](./DEPENDENCIES.md)

---

## Summary

### Key Patterns
1. ✅ Minimal IERC20 interface
2. ✅ Custom admin pattern
3. ✅ Standard ERC20 calls
4. ✅ Explicit error handling

### Benefits
- ✅ No external dependencies
- ✅ Smaller code size
- ✅ Lower gas costs
- ✅ Better maintainability

### When to Use OpenZeppelin
- ⚠️ Complex security features needed
- ⚠️ Battle-tested implementation required
- ⚠️ Non-standard token handling needed

---

## Next Steps

1. ✅ Follow patterns from new WETH contracts
2. ✅ Use minimal interfaces
3. ✅ Use custom admin pattern
4. ✅ Test thoroughly
5. ✅ Document dependencies

---

## Questions?

For questions about migration patterns, refer to:
- [Contract Inventory](./CONTRACT_INVENTORY.md)
- [OpenZeppelin Usage Analysis](./OPENZEPPELIN_USAGE_ANALYSIS.md)
- [Dependencies Guide](./DEPENDENCIES.md)

