Write, compile, and optimize smart contracts for TeQoin L2
Learn how to write and deploy smart contracts on TeQoin L2. TeQoin is 100% EVM-compatible, so your existing Ethereum contracts work without modifications.
Key Points:
✅ Full EVM compatibility (Shanghai version)
✅ Use Solidity 0.4.x - 0.8.x
✅ Same tooling as Ethereum (Hardhat, Foundry, Remix)
// SPDX-License-Identifier: MITpragma solidity ^0.8.20;/** * @title SimpleStorage * @dev Store and retrieve a value */contract SimpleStorage { uint256 private storedValue; event ValueChanged(uint256 newValue); /** * @dev Store a value * @param value The value to store */ function store(uint256 value) public { storedValue = value; emit ValueChanged(value); } /** * @dev Retrieve the stored value * @return The stored value */ function retrieve() public view returns (uint256) { return storedValue; }}
// For frequent random access mapping(address => uint256) public balances; // ✅ // For iteration address[] public users; // ✅ When you need to loop
Use calldata for external functions:
// ❌ BAD: Copies to memory function process(string memory data) external { // ... } // ✅ GOOD: Reads from calldata function process(string calldata data) external { // ... }
Mark view/pure functions:
// ✅ No state changes = view function getBalance(address user) public view returns (uint256) { return balances[user]; } // ✅ No state read = pure function add(uint256 a, uint256 b) public pure returns (uint256) { return a + b; }
Use events for data storage:
// Instead of storing history on-chain: // ❌ EXPENSIVE struct Transaction { address from; address to; uint256 amount; } Transaction[] public history; // ✅ CHEAP event Transfer( address indexed from, address indexed to, uint256 amount ); function transfer(address to, uint256 amount) public { // ... transfer logic ... emit Transfer(msg.sender, to, amount); }
Avoid unbounded loops:
// ❌ BAD: Could run out of gas function distributeToAll() public { for (uint i = 0; i < users.length; i++) { balances[users[i]] += reward; } } // ✅ GOOD: Paginated function distribute(uint256 start, uint256 end) public { require(end <= users.length, "Out of bounds"); for (uint i = start; i < end; i++) { balances[users[i]] += reward; } }
Problem: External calls before state updates can be exploited.
// ❌ VULNERABLE function withdraw(uint256 amount) public { require(balances[msg.sender] >= amount); (bool success,) = msg.sender.call{value: amount}(""); require(success); balances[msg.sender] -= amount; // State update AFTER external call } // ✅ SAFE: Checks-Effects-Interactions pattern function withdraw(uint256 amount) public { require(balances[msg.sender] >= amount); balances[msg.sender] -= amount; // State update BEFORE external call (bool success,) = msg.sender.call{value: amount}(""); require(success); } // ✅ SAFEST: Use ReentrancyGuard from OpenZeppelin import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; contract Safe is ReentrancyGuard { function withdraw(uint256 amount) public nonReentrant { // Safe from reentrancy } }
2. Integer Overflow/Underflow
Solution: Use Solidity 0.8.x which has built-in overflow checks.
// Solidity 0.8.x automatically reverts on overflow pragma solidity ^0.8.0; contract Safe { function add(uint256 a, uint256 b) public pure returns (uint256) { return a + b; // ✅ Safe: reverts on overflow } } // For 0.7.x and below, use SafeMath pragma solidity ^0.7.0; import "@openzeppelin/contracts/math/SafeMath.sol"; contract Safe { using SafeMath for uint256; function add(uint256 a, uint256 b) public pure returns (uint256) { return a.add(b); // ✅ Safe with SafeMath } }
3. Access Control
Use proper access control mechanisms:
import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/access/AccessControl.sol"; // ✅ Simple owner-only functions contract MyContract is Ownable { function adminFunction() public onlyOwner { // Only owner can call } } // ✅ Role-based access control (complex scenarios) contract MyContract is AccessControl { bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); constructor() { _grantRole(DEFAULT_ADMIN_ROLE, msg.sender); } function mint(address to) public onlyRole(MINTER_ROLE) { // Only minters can call } }
4. Front-Running Protection
Protect against transaction ordering attacks:
// Use commit-reveal scheme for sensitive operations contract Auction { mapping(address => bytes32) public commitments; // Step 1: Commit function commit(bytes32 hash) public { commitments[msg.sender] = hash; } // Step 2: Reveal (after commit period) function reveal(uint256 value, bytes32 secret) public { bytes32 hash = keccak256(abi.encodePacked(value, secret)); require(hash == commitments[msg.sender], "Invalid reveal"); // Process bid } }
5. Denial of Service
Avoid patterns that can be exploited:
// ❌ VULNERABLE: Relies on external call success function refundAll() public { for (uint i = 0; i < users.length; i++) { users[i].transfer(refunds[users[i]]); } } // ✅ SAFE: Pull payment pattern mapping(address => uint256) public refunds; function withdraw() public { uint256 amount = refunds[msg.sender]; refunds[msg.sender] = 0; payable(msg.sender).transfer(amount); }