- 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.
367 lines
9.1 KiB
Markdown
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)
|
|
|