Files
smom-dbis-138/docs/guides/MIGRATION_GUIDE.md
defiQUG 1fb7266469 Add Oracle Aggregator and CCIP Integration
- Introduced Aggregator.sol for Chainlink-compatible oracle functionality, including round-based updates and access control.
- Added OracleWithCCIP.sol to extend Aggregator with CCIP cross-chain messaging capabilities.
- Created .gitmodules to include OpenZeppelin contracts as a submodule.
- Developed a comprehensive deployment guide in NEXT_STEPS_COMPLETE_GUIDE.md for Phase 2 and smart contract deployment.
- Implemented Vite configuration for the orchestration portal, supporting both Vue and React frameworks.
- Added server-side logic for the Multi-Cloud Orchestration Portal, including API endpoints for environment management and monitoring.
- Created scripts for resource import and usage validation across non-US regions.
- Added tests for CCIP error handling and integration to ensure robust functionality.
- Included various new files and directories for the orchestration portal and deployment scripts.
2025-12-12 14:57:48 -08:00

367 lines
9.1 KiB
Markdown

# 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)