乐闻世界logo
搜索文章和话题

面试题手册

cURL 如何处理 HTTPS 和 SSL/TLS 证书验证?

HTTPS 和 SSL/TLS 证书验证是 cURL 安全通信的核心。正确配置 SSL 参数能确保数据传输的安全性。基本 HTTPS 请求# 标准 HTTPS 请求(自动验证证书)curl https://api.example.com# 显示 SSL 握手信息curl -v https://api.example.com 2>&1 | grep -A 20 "SSL connection"SSL 证书验证选项# 忽略证书验证(不安全,仅测试用)curl -k https://self-signed.badssl.comcurl --insecure https://api.example.com# 指定 CA 证书文件curl --cacert /path/to/ca.crt https://api.example.com# 指定 CA 证书目录curl --capath /etc/ssl/certs/ https://api.example.com# 使用系统默认证书库curl https://api.example.com客户端证书认证# 使用客户端证书(双向 SSL)curl --cert /path/to/client.crt \ --key /path/to/client.key \ https://api.example.com# 带密码的客户端证书curl --cert /path/to/client.crt:password \ --key /path/to/client.key \ https://api.example.com# PKCS#12 格式证书curl --cert /path/to/cert.p12:password \ https://api.example.comSSL 协议和加密套件# 指定最低 SSL 版本curl --ssl-version tls1.2 https://api.example.com# 指定 SSL 版本范围curl --ssl-allow-beast \ --tlsv1.2 \ --tls-max tls1.3 \ https://api.example.com# 列出支持的加密套件curl --ciphers 'HIGH:!aNULL:!MD5' https://api.example.com# 强制使用 TLS 1.3curl --tlsv1.3 https://api.example.com证书链验证# 查看服务器证书信息curl -v https://api.example.com 2>&1 | openssl x509 -text -noout# 验证证书链curl --cacert /path/to/ca-bundle.crt \ -v https://api.example.com 2>&1 | grep "SSL certificate verify"# 导出服务器证书openssl s_client -connect api.example.com:443 -showcerts# 测试特定证书curl --cacert <(echo | openssl s_client -connect api.example.com:443 2>/dev/null | openssl x509) \ https://api.example.comSSL 调试技巧# 详细的 SSL 信息curl -v --trace-ascii ssl-debug.txt https://api.example.com# 查看 SSL 握手过程curl -v https://api.example.com 2>&1 | grep -E "(SSL|TLS|certificate)"# 测试 SSL 连接openssl s_client -connect api.example.com:443 -servername api.example.com# 检查证书过期时间echo | openssl s_client -servername api.example.com -connect api.example.com:443 2>/dev/null | openssl x509 -noout -dates常见 SSL 问题解决# 问题 1:自签名证书# 解决:添加到信任列表或使用 -k(仅测试)curl --cacert /path/to/self-signed.crt https://internal.example.com# 问题 2:证书链不完整# 解决:提供完整的 CA 链curl --cacert /path/to/fullchain.crt https://api.example.com# 问题 3:主机名不匹配# 解决:使用正确的主机名或 resolvecurl --resolve api.example.com:443:192.168.1.100 https://api.example.com# 问题 4:过期证书# 解决:更新证书或临时跳过验证curl -k https://api.example.com# 问题 5:中间证书缺失# 解决:下载并指定中间证书curl --cacert /path/to/intermediate.crt https://api.example.com安全最佳实践# 推荐:始终验证证书curl https://api.example.com# 推荐:使用最新的 TLS 版本curl --tlsv1.2 --tls-max tls1.3 https://api.example.com# 推荐:指定证书固定curl --pinnedpubkey sha256//BASE64ENCODED= https://api.example.com# 推荐:检查证书撤销curl --crlfile /path/to/crl.pem https://api.example.com# 推荐:使用 OCSP 装订curl --cert-status https://api.example.comSSL 相关参数汇总| 参数 | 作用 | 示例 || ------------------- | ---------- | ------------------------- || -k 或 --insecure | 忽略证书验证 | -k || --cacert | 指定 CA 证书 | --cacert ca.crt || --capath | 指定 CA 目录 | --capath /etc/ssl/certs || --cert | 客户端证书 | --cert client.crt || --key | 客户端私钥 | --key client.key || --tlsv1.2 | 使用 TLS 1.2 | --tlsv1.2 || --tlsv1.3 | 使用 TLS 1.3 | --tlsv1.3 || --ciphers | 加密套件 | --ciphers 'HIGH:!aNULL' |完整 HTTPS 调用示例# 安全的 API 调用curl -X POST https://api.example.com/v1/data \ --tlsv1.2 \ --cacert /path/to/ca-bundle.crt \ --cert /path/to/client.crt \ --key /path/to/client.key \ -H "Content-Type: application/json" \ -H "Authorization: Bearer token123" \ -d '{"action":"secure_transfer"}' \ -v​
阅读 0·3月6日 21:53

Solidity 中签名验证(ECDSA)的原理和实现方式是什么?

ECDSA(椭圆曲线数字签名算法)是以太坊中用于验证交易和消息签名的核心密码学算法。在 Solidity 中实现签名验证对于实现元交易、免 Gas 交易、权限验证等场景非常重要。1. ECDSA 基本原理以太坊使用 secp256k1 椭圆曲线进行签名,签名包含三个部分:r:签名的 x 坐标s:签名的证明v:恢复标识符(27 或 28,或 0/1)// 签名结构struct Signature { bytes32 r; bytes32 s; uint8 v;}2. 基础签名验证使用 OpenZeppelin 的 ECDSA 库import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";contract SignatureVerification { using ECDSA for bytes32; // 验证签名者地址 function verifySignature( bytes32 messageHash, bytes memory signature ) public pure returns (address signer) { // 恢复签名者地址 signer = messageHash.recover(signature); return signer; } // 验证签名是否有效 function isValidSignature( bytes32 messageHash, bytes memory signature, address expectedSigner ) public pure returns (bool) { address recoveredSigner = messageHash.recover(signature); return recoveredSigner == expectedSigner; }}3. 消息哈希处理以太坊标准消息格式contract MessageHashing { // 方式 1:使用 keccak256 直接哈希 function getMessageHash( address _to, uint256 _amount, uint256 _nonce ) public pure returns (bytes32) { return keccak256(abi.encodePacked(_to, _amount, _nonce)); } // 方式 2:使用标准以太坊消息格式(推荐) function getEthSignedMessageHash(bytes32 _messageHash) public pure returns (bytes32) { // 按照以太坊标准添加前缀 // "\x19Ethereum Signed Message:\n32" + messageHash return keccak256(abi.encodePacked( "\x19Ethereum Signed Message:\n32", _messageHash )); } // 完整的签名验证流程 function verify( address _signer, address _to, uint256 _amount, uint256 _nonce, bytes memory signature ) public pure returns (bool) { // 1. 构建消息哈希 bytes32 messageHash = getMessageHash(_to, _amount, _nonce); // 2. 添加以太坊消息前缀 bytes32 ethSignedMessageHash = getEthSignedMessageHash(messageHash); // 3. 恢复签名者地址 address recoveredSigner = recoverSigner(ethSignedMessageHash, signature); // 4. 验证签名者 return recoveredSigner == _signer; } function recoverSigner( bytes32 _ethSignedMessageHash, bytes memory _signature ) public pure returns (address) { (bytes32 r, bytes32 s, uint8 v) = splitSignature(_signature); return ecrecover(_ethSignedMessageHash, v, r, s); } function splitSignature(bytes memory sig) public pure returns (bytes32 r, bytes32 s, uint8 v) { require(sig.length == 65, "Invalid signature length"); assembly { r := mload(add(sig, 32)) s := mload(add(sig, 64)) v := byte(0, mload(add(sig, 96))) } // 调整 v 值(MetaMask 等钱包通常返回 27/28) if (v < 27) { v += 27; } }}4. 元交易(Meta-Transaction)实现元交易允许用户在不支付 Gas 的情况下执行交易。import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";import "@openzeppelin/contracts/security/ReentrancyGuard.sol";contract MetaTransaction is ReentrancyGuard { using ECDSA for bytes32; // 防止重放攻击的 nonce mapping(address => uint256) public nonces; // 执行元交易的地址(可以支付 Gas) address public relayer; // 域分隔符(EIP-712) bytes32 public DOMAIN_SEPARATOR; // EIP-712 类型哈希 bytes32 public constant META_TRANSACTION_TYPEHASH = keccak256("MetaTransaction(address from,address to,uint256 value,uint256 nonce,uint256 data)"); event MetaTransactionExecuted( address indexed from, address indexed to, bytes functionSignature, uint256 nonce ); constructor() { relayer = msg.sender; // 构建域分隔符 DOMAIN_SEPARATOR = keccak256(abi.encode( keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), keccak256(bytes("MetaTransaction")), keccak256(bytes("1")), block.chainid, address(this) )); } // 执行元交易 function executeMetaTransaction( address from, address to, bytes memory functionSignature, bytes32 sigR, bytes32 sigS, uint8 sigV ) external payable nonReentrant returns (bytes memory) { require(msg.sender == relayer, "Only relayer can execute"); // 构建 EIP-712 结构化数据哈希 bytes32 digest = keccak256(abi.encodePacked( "\x19\x01", DOMAIN_SEPARATOR, keccak256(abi.encode( META_TRANSACTION_TYPEHASH, from, to, msg.value, nonces[from], keccak256(functionSignature) )) )); // 恢复签名者 address signer = ecrecover(digest, sigV, sigR, sigS); require(signer == from, "Invalid signature"); require(signer != address(0), "Zero address signer"); // 增加 nonce 防止重放 nonces[from]++; // 执行目标调用 (bool success, bytes memory returnData) = to.call{value: msg.value}(functionSignature); require(success, "Meta transaction failed"); emit MetaTransactionExecuted(from, to, functionSignature, nonces[from] - 1); return returnData; } function getNonce(address from) external view returns (uint256) { return nonces[from]; }}5. EIP-712 结构化数据签名EIP-712 提供了更友好的签名体验,用户在签名时可以看到结构化数据。import "@openzeppelin/contracts/utils/cryptography/EIP712.sol";import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";contract EIP712Example is EIP712 { using ECDSA for bytes32; // 定义 Permit 类型哈希 bytes32 public constant PERMIT_TYPEHASH = keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"); mapping(address => uint256) public nonces; constructor() EIP712("MyToken", "1") {} // 构建域分隔符 function DOMAIN_SEPARATOR() external view returns (bytes32) { return _domainSeparatorV4(); } // 验证 Permit 签名 function permit( address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s ) external { require(block.timestamp <= deadline, "Permit expired"); bytes32 structHash = keccak256(abi.encode( PERMIT_TYPEHASH, owner, spender, value, nonces[owner]++, deadline )); bytes32 hash = _hashTypedDataV4(structHash); address signer = hash.recover(v, r, s); require(signer == owner, "Invalid signature"); // 执行授权逻辑 _approve(owner, spender, value); } function _approve(address owner, address spender, uint256 value) internal { // 实现授权逻辑 }}6. 多签钱包实现contract MultiSigWallet { using ECDSA for bytes32; address[] public owners; mapping(address => bool) public isOwner; uint256 public requiredSignatures; struct Transaction { address to; uint256 value; bytes data; bool executed; uint256 signatureCount; } Transaction[] public transactions; mapping(uint256 => mapping(address => bool)) public signatures; event TransactionSubmitted(uint256 indexed txId, address indexed to, uint256 value); event TransactionSigned(uint256 indexed txId, address indexed signer); event TransactionExecuted(uint256 indexed txId); modifier onlyOwner() { require(isOwner[msg.sender], "Not an owner"); _; } constructor(address[] memory _owners, uint256 _required) { require(_owners.length > 0, "Owners required"); require(_required > 0 && _required <= _owners.length, "Invalid required"); for (uint i = 0; i < _owners.length; i++) { address owner = _owners[i]; require(owner != address(0), "Invalid owner"); require(!isOwner[owner], "Owner not unique"); isOwner[owner] = true; owners.push(owner); } requiredSignatures = _required; } // 提交交易并验证签名 function submitTransaction( address _to, uint256 _value, bytes memory _data, bytes[] memory _signatures ) external onlyOwner returns (uint256 txId) { require(_signatures.length >= requiredSignatures, "Insufficient signatures"); // 构建交易哈希 bytes32 txHash = keccak256(abi.encodePacked( address(this), _to, _value, keccak256(_data), transactions.length )); bytes32 ethSignedHash = keccak256(abi.encodePacked( "\x19Ethereum Signed Message:\n32", txHash )); // 验证每个签名 address[] memory signers = new address[](_signatures.length); for (uint i = 0; i < _signatures.length; i++) { address signer = recoverSigner(ethSignedHash, _signatures[i]); require(isOwner[signer], "Signer not an owner"); // 检查重复签名 for (uint j = 0; j < i; j++) { require(signers[j] != signer, "Duplicate signature"); } signers[i] = signer; } txId = transactions.length; transactions.push(Transaction({ to: _to, value: _value, data: _data, executed: false, signatureCount: _signatures.length })); emit TransactionSubmitted(txId, _to, _value); // 自动执行 executeTransaction(txId); } function executeTransaction(uint256 _txId) internal { Transaction storage transaction = transactions[_txId]; require(!transaction.executed, "Already executed"); require(transaction.signatureCount >= requiredSignatures, "Insufficient signatures"); transaction.executed = true; (bool success, ) = transaction.to.call{value: transaction.value}(transaction.data); require(success, "Transaction failed"); emit TransactionExecuted(_txId); } function recoverSigner(bytes32 _ethSignedMessageHash, bytes memory _signature) internal pure returns (address) { return _ethSignedMessageHash.recover(_signature); }}7. 签名重放攻击防护contract ReplayProtection { using ECDSA for bytes32; // 已使用的签名记录 mapping(bytes32 => bool) public usedSignatures; // 用户的 nonce mapping(address => uint256) public nonces; // 链 ID uint256 public chainId; constructor() { chainId = block.chainid; } // 安全的签名验证(包含链 ID 和合约地址) function verifyWithReplayProtection( address _signer, bytes memory _signature, bytes memory _data ) external returns (bool) { uint256 nonce = nonces[_signer]; // 构建包含防重放信息的消息 bytes32 messageHash = keccak256(abi.encodePacked( chainId, address(this), _signer, nonce, _data )); bytes32 ethSignedMessageHash = keccak256(abi.encodePacked( "\x19Ethereum Signed Message:\n32", messageHash )); // 检查签名是否已使用 require(!usedSignatures[ethSignedMessageHash], "Signature already used"); // 恢复签名者 address recoveredSigner = ethSignedMessageHash.recover(_signature); require(recoveredSigner == _signer, "Invalid signature"); // 标记签名已使用 usedSignatures[ethSignedMessageHash] = true; // 增加 nonce nonces[_signer]++; return true; }}8. 签名验证最佳实践始终使用标准以太坊消息格式:添加 \x19Ethereum Signed Message:\n32 前缀防止重放攻击:使用 nonce 和链 ID验证签名长度:确保签名长度为 65 字节检查签名者地址:确保恢复的地址不是零地址使用 EIP-712:提供更好的用户体验和安全性考虑使用 OpenZeppelin:经过审计的标准实现// 完整的签名验证最佳实践contract BestPracticeSignature { using ECDSA for bytes32; bytes32 public constant EIP712_DOMAIN_TYPEHASH = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"); bytes32 public immutable DOMAIN_SEPARATOR; constructor() { DOMAIN_SEPARATOR = keccak256(abi.encode( EIP712_DOMAIN_TYPEHASH, keccak256(bytes("BestPractice")), keccak256(bytes("1")), block.chainid, address(this) )); } function verifyEIP712Signature( bytes32 structHash, bytes memory signature, address expectedSigner ) external view returns (bool) { bytes32 digest = keccak256(abi.encodePacked( "\x19\x01", DOMAIN_SEPARATOR, structHash )); address signer = digest.recover(signature); return signer == expectedSigner && signer != address(0); }}​
阅读 0·3月6日 21:52

Solidity 中 ERC20 和 ERC721 代币标准的核心实现原理是什么?

ERC20 和 ERC721 是以太坊上最广泛使用的代币标准,分别代表同质化代币和非同质化代币(NFT)。理解它们的核心实现原理对于开发 DeFi 和 NFT 项目至关重要。1. ERC20 标准详解ERC20 定义了同质化代币的标准接口,每个代币都是可互换的。ERC20 接口定义interface IERC20 { // 查询总供应量 function totalSupply() external view returns (uint256); // 查询账户余额 function balanceOf(address account) external view returns (uint256); // 查询授权额度 function allowance(address owner, address spender) external view returns (uint256); // 转账 function transfer(address to, uint256 amount) external returns (bool); // 授权 function approve(address spender, uint256 amount) external returns (bool); // 从授权账户转账 function transferFrom(address from, address to, uint256 amount) external returns (bool); // 事件 event Transfer(address indexed from, address indexed to, uint256 value); event Approval(address indexed owner, address indexed spender, uint256 value);}ERC20 完整实现contract ERC20 is IERC20 { // 状态变量 mapping(address => uint256) private _balances; mapping(address => mapping(address => uint256)) private _allowances; uint256 private _totalSupply; string private _name; string private _symbol; uint8 private _decimals; // 构造函数 constructor(string memory name_, string memory symbol_, uint8 decimals_) { _name = name_; _symbol = symbol_; _decimals = decimals_; } // 查询函数 function name() public view returns (string memory) { return _name; } function symbol() public view returns (string memory) { return _symbol; } function decimals() public view returns (uint8) { return _decimals; } function totalSupply() public view override returns (uint256) { return _totalSupply; } function balanceOf(address account) public view override returns (uint256) { return _balances[account]; } function allowance(address owner, address spender) public view override returns (uint256) { return _allowances[owner][spender]; } // 转账函数 function transfer(address to, uint256 amount) public override returns (bool) { address owner = msg.sender; _transfer(owner, to, amount); return true; } // 授权函数 function approve(address spender, uint256 amount) public override returns (bool) { address owner = msg.sender; _approve(owner, spender, amount); return true; } // 授权转账 function transferFrom(address from, address to, uint256 amount) public override returns (bool) { address spender = msg.sender; _spendAllowance(from, spender, amount); _transfer(from, to, amount); return true; } // 内部转账逻辑 function _transfer(address from, address to, uint256 amount) internal { require(from != address(0), "ERC20: transfer from zero address"); require(to != address(0), "ERC20: transfer to zero address"); uint256 fromBalance = _balances[from]; require(fromBalance >= amount, "ERC20: insufficient balance"); unchecked { _balances[from] = fromBalance - amount; _balances[to] += amount; } emit Transfer(from, to, amount); } // 内部授权逻辑 function _approve(address owner, address spender, uint256 amount) internal { require(owner != address(0), "ERC20: approve from zero address"); require(spender != address(0), "ERC20: approve to zero address"); _allowances[owner][spender] = amount; emit Approval(owner, spender, amount); } // 消耗授权额度 function _spendAllowance(address owner, address spender, uint256 amount) internal { uint256 currentAllowance = allowance(owner, spender); if (currentAllowance != type(uint256).max) { require(currentAllowance >= amount, "ERC20: insufficient allowance"); unchecked { _approve(owner, spender, currentAllowance - amount); } } } // 铸造代币(内部函数) function _mint(address account, uint256 amount) internal { require(account != address(0), "ERC20: mint to zero address"); _totalSupply += amount; unchecked { _balances[account] += amount; } emit Transfer(address(0), account, amount); } // 销毁代币(内部函数) function _burn(address account, uint256 amount) internal { require(account != address(0), "ERC20: burn from zero address"); uint256 accountBalance = _balances[account]; require(accountBalance >= amount, "ERC20: burn amount exceeds balance"); unchecked { _balances[account] = accountBalance - amount; _totalSupply -= amount; } emit Transfer(account, address(0), amount); }}ERC20 扩展功能// 可暂停的 ERC20import "@openzeppelin/contracts/security/Pausable.sol";contract PausableERC20 is ERC20, Pausable { constructor() ERC20("PausableToken", "PTK", 18) {} function _beforeTokenTransfer(address from, address to, uint256 amount) internal override whenNotPaused { super._beforeTokenTransfer(from, to, amount); } function pause() public onlyOwner { _pause(); } function unpause() public onlyOwner { _unpause(); }}// 可销毁的 ERC20contract BurnableERC20 is ERC20 { constructor() ERC20("BurnableToken", "BTK", 18) {} function burn(uint256 amount) public { _burn(msg.sender, amount); } function burnFrom(address account, uint256 amount) public { _spendAllowance(account, msg.sender, amount); _burn(account, amount); }}2. ERC721 标准详解ERC721 定义了非同质化代币(NFT)的标准,每个代币都是唯一的。ERC721 接口定义interface IERC721 { // 查询余额 function balanceOf(address owner) external view returns (uint256 balance); // 查询代币所有者 function ownerOf(uint256 tokenId) external view returns (address owner); // 安全转账 function safeTransferFrom(address from, address to, uint256 tokenId) external; function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external; // 转账 function transferFrom(address from, address to, uint256 tokenId) external; // 授权 function approve(address to, uint256 tokenId) external; function setApprovalForAll(address operator, bool approved) external; // 查询授权 function getApproved(uint256 tokenId) external view returns (address operator); function isApprovedForAll(address owner, address operator) external view returns (bool); // 事件 event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId); event ApprovalForAll(address indexed owner, address indexed operator, bool approved);}// 元数据接口interface IERC721Metadata { function name() external view returns (string memory); function symbol() external view returns (string memory); function tokenURI(uint256 tokenId) external view returns (string memory);}ERC721 完整实现contract ERC721 is IERC721, IERC721Metadata { // Token name string private _name; // Token symbol string private _symbol; // Token ID 到所有者地址的映射 mapping(uint256 => address) private _owners; // 所有者地址到代币数量的映射 mapping(address => uint256) private _balances; // Token ID 到授权地址的映射 mapping(uint256 => address) private _tokenApprovals; // 所有者到操作员授权的映射 mapping(address => mapping(address => bool)) private _operatorApprovals; // Token ID 到 token URI 的映射 mapping(uint256 => string) private _tokenURIs; constructor(string memory name_, string memory symbol_) { _name = name_; _symbol = symbol_; } function name() public view override returns (string memory) { return _name; } function symbol() public view override returns (string memory) { return _symbol; } function tokenURI(uint256 tokenId) public view override returns (string memory) { require(_exists(tokenId), "ERC721: URI query for nonexistent token"); return _tokenURIs[tokenId]; } function balanceOf(address owner) public view override returns (uint256) { require(owner != address(0), "ERC721: balance query for zero address"); return _balances[owner]; } function ownerOf(uint256 tokenId) public view override returns (address) { address owner = _owners[tokenId]; require(owner != address(0), "ERC721: owner query for nonexistent token"); return owner; } function approve(address to, uint256 tokenId) public override { address owner = ownerOf(tokenId); require(to != owner, "ERC721: approval to current owner"); require( msg.sender == owner || isApprovedForAll(owner, msg.sender), "ERC721: approve caller is not owner nor approved for all" ); _approve(to, tokenId); } function getApproved(uint256 tokenId) public view override returns (address) { require(_exists(tokenId), "ERC721: approved query for nonexistent token"); return _tokenApprovals[tokenId]; } function setApprovalForAll(address operator, bool approved) public override { require(operator != msg.sender, "ERC721: approve to caller"); _operatorApprovals[msg.sender][operator] = approved; emit ApprovalForAll(msg.sender, operator, approved); } function isApprovedForAll(address owner, address operator) public view override returns (bool) { return _operatorApprovals[owner][operator]; } function transferFrom(address from, address to, uint256 tokenId) public override { require(_isApprovedOrOwner(msg.sender, tokenId), "ERC721: transfer caller is not owner nor approved"); _transfer(from, to, tokenId); } function safeTransferFrom(address from, address to, uint256 tokenId) public override { safeTransferFrom(from, to, tokenId, ""); } function safeTransferFrom( address from, address to, uint256 tokenId, bytes memory _data ) public override { require(_isApprovedOrOwner(msg.sender, tokenId), "ERC721: transfer caller is not owner nor approved"); _safeTransfer(from, to, tokenId, _data); } // 内部函数 function _exists(uint256 tokenId) internal view returns (bool) { return _owners[tokenId] != address(0); } function _isApprovedOrOwner(address spender, uint256 tokenId) internal view returns (bool) { require(_exists(tokenId), "ERC721: operator query for nonexistent token"); address owner = ownerOf(tokenId); return (spender == owner || getApproved(tokenId) == spender || isApprovedForAll(owner, spender)); } function _safeTransfer(address from, address to, uint256 tokenId, bytes memory _data) internal { _transfer(from, to, tokenId); require(_checkOnERC721Received(from, to, tokenId, _data), "ERC721: transfer to non ERC721Receiver implementer"); } function _transfer(address from, address to, uint256 tokenId) internal { require(ownerOf(tokenId) == from, "ERC721: transfer from incorrect owner"); require(to != address(0), "ERC721: transfer to the zero address"); // 清除授权 _approve(address(0), tokenId); _balances[from] -= 1; _balances[to] += 1; _owners[tokenId] = to; emit Transfer(from, to, tokenId); } function _approve(address to, uint256 tokenId) internal { _tokenApprovals[tokenId] = to; emit Approval(ownerOf(tokenId), to, tokenId); } // 铸造函数 function _mint(address to, uint256 tokenId) internal { require(to != address(0), "ERC721: mint to the zero address"); require(!_exists(tokenId), "ERC721: token already minted"); _balances[to] += 1; _owners[tokenId] = to; emit Transfer(address(0), to, tokenId); } // 安全铸造 function _safeMint(address to, uint256 tokenId) internal { _safeMint(to, tokenId, ""); } function _safeMint(address to, uint256 tokenId, bytes memory _data) internal { _mint(to, tokenId); require( _checkOnERC721Received(address(0), to, tokenId, _data), "ERC721: transfer to non ERC721Receiver implementer" ); } // 销毁函数 function _burn(uint256 tokenId) internal { address owner = ownerOf(tokenId); _approve(address(0), tokenId); _balances[owner] -= 1; delete _owners[tokenId]; emit Transfer(owner, address(0), tokenId); } // 检查接收者是否实现了 ERC721Receiver 接口 function _checkOnERC721Received( address from, address to, uint256 tokenId, bytes memory _data ) private returns (bool) { if (to.code.length > 0) { try IERC721Receiver(to).onERC721Received(msg.sender, from, tokenId, _data) returns (bytes4 retval) { return retval == IERC721Receiver.onERC721Received.selector; } catch (bytes memory reason) { if (reason.length == 0) { revert("ERC721: transfer to non ERC721Receiver implementer"); } else { assembly { revert(add(32, reason), mload(reason)) } } } } return true; }}// ERC721Receiver 接口interface IERC721Receiver { function onERC721Received( address operator, address from, uint256 tokenId, bytes calldata data ) external returns (bytes4);}3. ERC20 vs ERC721 对比| 特性 | ERC20 | ERC721 || ---- | ------------------------ | ------------------------------------------------------------ || 代币类型 | 同质化 | 非同质化 || 互换性 | 可互换 | 唯一不可互换 || 余额查询 | balanceOf(address) | balanceOf(address) + ownerOf(tokenId) || 转账 | transfer(to, amount) | transferFrom(from, to, tokenId) || 授权 | approve(spender, amount) | approve(to, tokenId) + setApprovalForAll(operator, approved) || 使用场景 | 货币、积分、治理代币 | 数字艺术品、游戏道具、身份凭证 |4. 实际应用示例完整的 ERC20 Tokenimport "@openzeppelin/contracts/token/ERC20/ERC20.sol";import "@openzeppelin/contracts/access/Ownable.sol";contract MyToken is ERC20, Ownable { constructor(uint256 initialSupply) ERC20("MyToken", "MTK") { _mint(msg.sender, initialSupply * 10 ** decimals()); } function mint(address to, uint256 amount) public onlyOwner { _mint(to, amount); }}完整的 NFT 合约import "@openzeppelin/contracts/token/ERC721/ERC721.sol";import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";import "@openzeppelin/contracts/access/Ownable.sol";contract MyNFT is ERC721URIStorage, Ownable { uint256 private _tokenIds; uint256 public mintPrice = 0.01 ether; uint256 public maxSupply = 10000; string public baseURI; constructor(string memory _baseURI) ERC721("MyNFT", "MNFT") { baseURI = _baseURI; } function mint(string memory tokenURI) public payable returns (uint256) { require(msg.value >= mintPrice, "Insufficient payment"); require(_tokenIds < maxSupply, "Max supply reached"); _tokenIds++; uint256 newTokenId = _tokenIds; _mint(msg.sender, newTokenId); _setTokenURI(newTokenId, tokenURI); return newTokenId; } function withdraw() public onlyOwner { payable(owner()).transfer(address(this).balance); } function _baseURI() internal view override returns (string memory) { return baseURI; }}5. 安全注意事项整数溢出:使用 Solidity 0.8+ 或 SafeMath 库重入攻击:遵循 Checks-Effects-Interactions 模式授权管理:正确处理 approve 和 allowance零地址检查:防止代币发送到零地址事件发射:所有状态变更都应触发事件6. 扩展标准ERC777:更高级的代币标准,支持钩子函数ERC1155:多代币标准,支持同质化和非同质化代币ERC2981:NFT 版税标准ERC4907:NFT 租赁标准
阅读 0·3月6日 21:51

Solidity 智能合约如何进行 Gas 优化?有哪些常见的优化技巧?

Gas 优化是 Solidity 开发中的关键技能,直接影响合约的执行成本和用户体验。以下是系统性的 Gas 优化策略和技巧。1. 存储优化Storage 是最昂贵的资源,优化存储使用是 Gas 优化的首要任务。使用合适的数据类型contract StorageOptimization { // 不推荐:使用 uint256 存储小数值 uint256 public smallValue; // 占用 32 字节 // 推荐:使用更小的数据类型 uint128 public value128; // 占用 16 字节 uint64 public value64; // 占用 8 字节 uint32 public value32; // 占用 4 字节 uint8 public value8; // 占用 1 字节 // 最佳:打包存储变量 uint128 public balance; // slot 0: 16 字节 uint32 public timestamp; // slot 0: 4 字节 uint16 public status; // slot 0: 2 字节 address public owner; // slot 0: 20 字节 - 需要 slot 1 // 注意:uint128 + uint32 + uint16 = 22 字节,address 需要新的 slot}存储变量打包contract PackingExample { // 未优化:占用 3 个 storage slot struct Unoptimized { uint256 a; // slot 0 uint128 b; // slot 1 uint128 c; // slot 2 } // 优化后:只占用 2 个 storage slot struct Optimized { uint128 b; // slot 0 (16 bytes) uint128 c; // slot 0 (16 bytes) - 打包在一起 uint256 a; // slot 1 } // 最佳实践:按大小排序 struct BestPractice { uint128 a; // 16 bytes uint128 b; // 16 bytes - 与 a 共用一个 slot uint64 c; // 8 bytes uint64 d; // 8 bytes - 与 c 共用 uint32 e; // 4 bytes uint32 f; // 4 bytes - 与 e 共用 }}使用 memory 代替 storagecontract MemoryVsStorage { uint256[] public data; // 昂贵:多次 storage 访问 function expensiveSum() public view returns (uint256) { uint256 sum = 0; for (uint i = 0; i < data.length; i++) { sum += data[i]; // 每次循环都访问 storage } return sum; } // 优化:先加载到 memory function optimizedSum() public view returns (uint256) { uint256[] memory localData = data; // 一次性加载 uint256 sum = 0; for (uint i = 0; i < localData.length; i++) { sum += localData[i]; // 访问 memory,便宜很多 } return sum; }}2. 变量和计算优化使用常量和不变量contract ConstantOptimization { // 昂贵:storage 变量 uint256 public constantValue = 1000; // 优化:常量(不占用 storage) uint256 public constant CONSTANT_VALUE = 1000; // 更优:不可变量(编译时确定,不占用 storage) uint256 public immutable immutableValue; constructor(uint256 _value) { immutableValue = _value; } // 使用常量计算 function calculate(uint256 amount) public pure returns (uint256) { return amount * CONSTANT_VALUE / 10000; }}短路求优contract ShortCircuit { // 优化:利用短路求值 function checkConditions(bool a, bool b, bool c) public pure returns (bool) { // 将最可能为 false 的条件放在前面 return a && b && c; } // 优化:|| 运算符 function checkOr(bool a, bool b, bool c) public pure returns (bool) { // 将最可能为 true 的条件放在前面 return a || b || c; }}3. 循环优化循环变量优化contract LoopOptimization { uint256[] public data; // 未优化:每次循环都访问 storage function unoptimizedLoop() public view returns (uint256) { uint256 sum = 0; for (uint256 i = 0; i < data.length; i++) { sum += data[i]; } return sum; } // 优化 1:缓存数组长度 function optimizedLoop1() public view returns (uint256) { uint256 sum = 0; uint256 len = data.length; // 缓存长度 for (uint256 i = 0; i < len; i++) { sum += data[i]; } return sum; } // 优化 2:使用 unchecked 和 ++i function optimizedLoop2() public view returns (uint256) { uint256 sum = 0; uint256 len = data.length; for (uint256 i = 0; i < len; ) { sum += data[i]; unchecked { ++i; } // 使用 unchecked 和前置递增 } return sum; } // 优化 3:使用 memory 数组 function optimizedLoop3() public view returns (uint256) { uint256[] memory localData = data; uint256 sum = 0; uint256 len = localData.length; for (uint256 i = 0; i < len; ) { sum += localData[i]; unchecked { ++i; } } return sum; }}4. 函数优化使用 calldatacontract CalldataOptimization { // 昂贵:使用 memory function processMemory(uint256[] memory data) external pure returns (uint256) { uint256 sum = 0; for (uint i = 0; i < data.length; i++) { sum += data[i]; } return sum; } // 优化:使用 calldata(只读,不复制) function processCalldata(uint256[] calldata data) external pure returns (uint256) { uint256 sum = 0; for (uint i = 0; i < data.length; i++) { sum += data[i]; } return sum; }}函数修饰符优化contract ModifierOptimization { // 昂贵:修饰符代码内联到每个函数 modifier expensiveCheck() { require(msg.sender == owner, "Not owner"); require(!paused, "Paused"); require(balance > 0, "No balance"); _; } // 优化:将检查提取为内部函数 modifier optimizedCheck() { _checkAccess(); _; } function _checkAccess() internal view { require(msg.sender == owner, "Not owner"); require(!paused, "Paused"); require(balance > 0, "No balance"); } // 这样编译器可以更有效地优化 function action1() external optimizedCheck { } function action2() external optimizedCheck { } function action3() external optimizedCheck { }}5. 错误处理优化自定义错误(Solidity 0.8.4+)// 文件: ErrorOptimization.sol// 开源许可: MITpragma solidity ^0.8.4;contract ErrorOptimization { address public owner; uint256 public balance; // 定义自定义错误(比 require 字符串更省 Gas) error NotOwner(address caller); error InsufficientBalance(uint256 requested, uint256 available); error InvalidAmount(uint256 amount); modifier onlyOwner() { if (msg.sender != owner) { revert NotOwner(msg.sender); } _; } function withdraw(uint256 amount) external onlyOwner { if (amount == 0) { revert InvalidAmount(amount); } if (amount > balance) { revert InsufficientBalance(amount, balance); } balance -= amount; payable(msg.sender).transfer(amount); }}6. 事件和日志优化contract EventOptimization { // 昂贵:存储大量数据 mapping(uint256 => Transaction) public transactions; struct Transaction { address from; address to; uint256 amount; uint256 timestamp; string metadata; } // 优化:使用事件代替 storage event TransactionExecuted( address indexed from, address indexed to, uint256 amount, uint256 timestamp ); function executeTransaction(address to, uint256 amount) external { // 执行业务逻辑... // 使用事件记录(比 storage 便宜得多) emit TransactionExecuted(msg.sender, to, amount, block.timestamp); }}7. 库的使用// 使用库减少代码重复library SafeMath { function add(uint256 a, uint256 b) internal pure returns (uint256) { unchecked { uint256 c = a + b; require(c >= a, "Addition overflow"); return c; } }}contract UsingLibrary { using SafeMath for uint256; function calculate(uint256 a, uint256 b) public pure returns (uint256) { return a.add(b); // 使用库函数 }}8. 位运算优化contract BitwiseOptimization { // 使用位运算代替数学运算 // 乘以/除以 2 的幂 function multiplyBy2(uint256 x) public pure returns (uint256) { return x << 1; // 等同于 x * 2 } function divideBy2(uint256 x) public pure returns (uint256) { return x >> 1; // 等同于 x / 2 } // 检查是否为 2 的幂 function isPowerOf2(uint256 x) public pure returns (bool) { return x > 0 && (x & (x - 1)) == 0; } // 使用位掩码存储多个布尔值 uint256 private flags; uint256 constant FLAG_PAUSED = 1 << 0; // 1 uint256 constant FLAG_FINALIZED = 1 << 1; // 2 uint256 constant FLAG_APPROVED = 1 << 2; // 4 function setFlag(uint256 flag) internal { flags |= flag; } function clearFlag(uint256 flag) internal { flags &= ~flag; } function hasFlag(uint256 flag) internal view returns (bool) { return (flags & flag) != 0; }}9. 编译器优化使用优化器// hardhat.config.jsmodule.exports = { solidity: { version: "0.8.19", settings: { optimizer: { enabled: true, runs: 200 // 根据合约调用频率调整 } } }};10. Gas 优化对比表| 优化技术 | Gas 节省 | 适用场景 || ------------ | --------------- | ------ || 存储打包 | 20,000 Gas/slot | 多个小变量 || 使用常量 | 20,000 Gas | 固定值 || 使用 calldata | 3,800 Gas/32字节 | 外部函数参数 || 自定义错误 | ~50 Gas | 错误处理 || unchecked | ~80 Gas/操作 | 数学运算 || 事件替代 storage | ~19,000 Gas | 日志记录 || 短路求值 | 变量 | 条件判断 |11. 优化工具hardhat-gas-reporter: 报告每个测试的 Gas 消耗eth-gas-reporter: 详细的 Gas 分析报告Remix IDE: 内置 Gas 估算Tenderly: Gas 分析和优化建议// hardhat.config.jsrequire("hardhat-gas-reporter");module.exports = { gasReporter: { enabled: true, currency: "USD", gasPrice: 21 }};12. 最佳实践总结优先优化存储:Storage 操作是最昂贵的使用合适的数据类型:避免过度分配利用编译器优化:启用优化器并调整 runs 参数批量操作:减少函数调用次数使用事件:代替不必要的 storage 写入延迟计算:尽可能在需要时计算而非存储定期审计:使用工具检查 Gas 消耗记住:过度优化可能降低代码可读性,需要在性能和可维护性之间找到平衡。
阅读 0·3月6日 21:51

Solidity 中的 Library 和 Contract 有什么区别?如何正确使用 Library?

Library(库)是 Solidity 中一种特殊的代码复用机制,与 Contract(合约)有本质区别。正确使用 Library 可以节省 Gas、提高代码复用性。1. Library 和 Contract 的核心区别| 特性 | Library | Contract || ------ | ---------------- | --------- || 部署 | 可以独立部署或内联 | 必须独立部署 || 状态变量 | 不能有状态变量 | 可以有状态变量 || 继承 | 不能被继承 | 可以被继承 || ETH 接收 | 不能接收 ETH | 可以接收 ETH || 销毁 | 不能自销毁 | 可以自销毁 || 调用方式 | DELEGATECALL 或内联 | CALL || 使用场景 | 工具函数、复用代码 | 业务逻辑、状态管理 |2. Library 的基本用法独立函数库// 文件: MathLibrary.sol// 纯函数库,所有函数都是 purelibrary MathLibrary { // 计算最大公约数 function gcd(uint256 a, uint256 b) internal pure returns (uint256) { while (b != 0) { uint256 temp = b; b = a % b; a = temp; } return a; } // 计算平方根 function sqrt(uint256 x) internal pure returns (uint256) { if (x == 0) return 0; uint256 z = (x + 1) / 2; uint256 y = x; while (z < y) { y = z; z = (x / z + z) / 2; } return y; } // 计算幂 function pow(uint256 base, uint256 exponent) internal pure returns (uint256) { if (exponent == 0) return 1; if (exponent == 1) return base; uint256 result = 1; uint256 b = base; uint256 e = exponent; while (e > 0) { if (e & 1 == 1) { result *= b; } b *= b; e >>= 1; } return result; }}// 使用库contract Calculator { // 使用 using for 语法 using MathLibrary for uint256; function calculate(uint256 a, uint256 b) external pure returns (uint256, uint256, uint256) { uint256 gcdResult = MathLibrary.gcd(a, b); uint256 sqrtResult = a.sqrt(); // 使用 using for 语法 uint256 powResult = a.pow(b); // 使用 using for 语法 return (gcdResult, sqrtResult, powResult); }}结构体库// 文件: ArrayLibrary.sollibrary ArrayLibrary { // 查找元素索引 function find(uint256[] storage arr, uint256 value) internal view returns (int256) { for (uint i = 0; i < arr.length; i++) { if (arr[i] == value) { return int256(i); } } return -1; } // 删除元素(保持顺序) function removeAt(uint256[] storage arr, uint256 index) internal { require(index < arr.length, "Index out of bounds"); for (uint i = index; i < arr.length - 1; i++) { arr[i] = arr[i + 1]; } arr.pop(); } // 删除元素(不保持顺序,更高效) function quickRemove(uint256[] storage arr, uint256 index) internal { require(index < arr.length, "Index out of bounds"); if (index != arr.length - 1) { arr[index] = arr[arr.length - 1]; } arr.pop(); } // 插入排序 function sort(uint256[] storage arr) internal { for (uint i = 1; i < arr.length; i++) { uint256 key = arr[i]; int256 j = int256(i) - 1; while (j >= 0 && arr[uint256(j)] > key) { arr[uint256(j) + 1] = arr[uint256(j)]; j--; } arr[uint256(j) + 1] = key; } } // 检查是否包含 function contains(uint256[] storage arr, uint256 value) internal view returns (bool) { for (uint i = 0; i < arr.length; i++) { if (arr[i] == value) { return true; } } return false; }}// 使用结构体库contract DataManager { using ArrayLibrary for uint256[]; uint256[] public data; function addData(uint256 value) external { data.push(value); } function removeData(uint256 index) external { data.quickRemove(index); // 调用库函数 } function findData(uint256 value) external view returns (int256) { return data.find(value); // 调用库函数 } function sortData() external { data.sort(); // 调用库函数 }}3. Library 的高级用法使用 storage 的库// 文件: MappingLibrary.sollibrary MappingLibrary { // 为 mapping 添加迭代功能 struct IterableMapping { mapping(address => uint256) data; address[] keys; mapping(address => uint256) keyIndex; } function set(IterableMapping storage self, address key, uint256 value) internal { if (self.keyIndex[key] == 0) { // 新键 self.keys.push(key); self.keyIndex[key] = self.keys.length; } self.data[key] = value; } function get(IterableMapping storage self, address key) internal view returns (uint256) { return self.data[key]; } function remove(IterableMapping storage self, address key) internal { require(self.keyIndex[key] != 0, "Key not found"); uint256 index = self.keyIndex[key] - 1; address lastKey = self.keys[self.keys.length - 1]; // 用最后一个元素替换要删除的元素 self.keys[index] = lastKey; self.keyIndex[lastKey] = index + 1; self.keys.pop(); delete self.keyIndex[key]; delete self.data[key]; } function contains(IterableMapping storage self, address key) internal view returns (bool) { return self.keyIndex[key] != 0; } function size(IterableMapping storage self) internal view returns (uint256) { return self.keys.length; } function getKeyAt(IterableMapping storage self, uint256 index) internal view returns (address) { require(index < self.keys.length, "Index out of bounds"); return self.keys[index]; }}// 使用可迭代映射contract TokenHolder { using MappingLibrary for MappingLibrary.IterableMapping; MappingLibrary.IterableMapping private balances; function setBalance(address holder, uint256 amount) external { balances.set(holder, amount); } function getBalance(address holder) external view returns (uint256) { return balances.get(holder); } function removeHolder(address holder) external { balances.remove(holder); } function getAllHolders() external view returns (address[] memory) { address[] memory holders = new address[](balances.size()); for (uint i = 0; i < balances.size(); i++) { holders[i] = balances.getKeyAt(i); } return holders; }}4. 常用的标准库SafeMath(Solidity \< 0.8.0)// 文件: SafeMath.sollibrary SafeMath { function add(uint256 a, uint256 b) internal pure returns (uint256) { uint256 c = a + b; require(c >= a, "SafeMath: addition overflow"); return c; } function sub(uint256 a, uint256 b) internal pure returns (uint256) { require(b <= a, "SafeMath: subtraction underflow"); return a - b; } function mul(uint256 a, uint256 b) internal pure returns (uint256) { if (a == 0) return 0; uint256 c = a * b; require(c / a == b, "SafeMath: multiplication overflow"); return c; } function div(uint256 a, uint256 b) internal pure returns (uint256) { require(b > 0, "SafeMath: division by zero"); return a / b; } function mod(uint256 a, uint256 b) internal pure returns (uint256) { require(b != 0, "SafeMath: modulo by zero"); return a % b; }}// 使用 SafeMathcontract SafeCalculator { using SafeMath for uint256; function safeAdd(uint256 a, uint256 b) external pure returns (uint256) { return a.add(b); // 使用库函数 }}Address 库// OpenZeppelin 的 Address 库library Address { function isContract(address account) internal view returns (bool) { return account.code.length > 0; } function sendValue(address payable recipient, uint256 amount) internal { require(address(this).balance >= amount, "Insufficient balance"); (bool success, ) = recipient.call{value: amount}(""); require(success, "Transfer failed"); } function functionCall(address target, bytes memory data) internal returns (bytes memory) { return functionCall(target, data, "Address: low-level call failed"); } function functionCall( address target, bytes memory data, string memory errorMessage ) internal returns (bytes memory) { (bool success, bytes memory returndata) = target.call(data); if (!success) { if (returndata.length > 0) { assembly { let returndata_size := mload(returndata) revert(add(32, returndata), returndata_size) } } else { revert(errorMessage); } } return returndata; }}// 使用 Address 库contract ContractInteractor { using Address for address; function checkIsContract(address account) external view returns (bool) { return account.isContract(); } function safeTransfer(address payable recipient, uint256 amount) external { recipient.sendValue(amount); }}5. Library 的部署方式内联库(Internal 函数)// 所有函数都是 internal,代码会被内联到调用合约中library InlineLibrary { function internalFunction(uint256 x) internal pure returns (uint256) { return x * 2; }}// 使用时不需要部署库,代码直接内联contract UsingInlineLibrary { using InlineLibrary for uint256; function double(uint256 x) external pure returns (uint256) { return x.internalFunction(); }}外部库(External 函数)// 包含 external 函数,需要单独部署library ExternalLibrary { function externalFunction(uint256 x) external pure returns (uint256) { return x * 2; }}// 使用时需要链接已部署的库地址contract UsingExternalLibrary { function double(uint256 x) external pure returns (uint256) { return ExternalLibrary.externalFunction(x); }}6. Library 的最佳实践命名规范// 库名使用 PascalCase,以 Library 结尾(可选但推荐)library StringLibrary { // 函数名使用 camelCase function toUpperCase(string memory str) internal pure returns (string memory) { // 实现... }}函数可见性library VisibilityExample { // internal:代码内联,不需要部署库(推荐) function internalFunc() internal pure returns (uint256) { return 1; } // external:需要部署库,通过 DELEGATECALL 调用 function externalFunc() external pure returns (uint256) { return 2; } // public:可以内部调用也可以外部调用 function publicFunc() public pure returns (uint256) { return 3; } // private:只能在库内部使用 function privateFunc() private pure returns (uint256) { return 4; }}使用 using forlibrary EnhancedMath { function square(uint256 x) internal pure returns (uint256) { return x * x; } function cube(uint256 x) internal pure returns (uint256) { return x * x * x; }}contract MathUser { // 方式 1:对特定类型使用 using for using EnhancedMath for uint256; function calculate(uint256 x) external pure returns (uint256, uint256) { return (x.square(), x.cube()); } // 方式 2:全局使用(所有 uint256) using EnhancedMath for *;}7. Library 的实际应用案例字符串处理库library StringUtils { function length(string memory str) internal pure returns (uint256) { return bytes(str).length; } function concat(string memory a, string memory b) internal pure returns (string memory) { bytes memory ba = bytes(a); bytes memory bb = bytes(b); bytes memory result = new bytes(ba.length + bb.length); for (uint i = 0; i < ba.length; i++) { result[i] = ba[i]; } for (uint i = 0; i < bb.length; i++) { result[ba.length + i] = bb[i]; } return string(result); } function substring( string memory str, uint256 start, uint256 length ) internal pure returns (string memory) { bytes memory strBytes = bytes(str); require(start + length <= strBytes.length, "Out of bounds"); bytes memory result = new bytes(length); for (uint i = 0; i < length; i++) { result[i] = strBytes[start + i]; } return string(result); } function compare(string memory a, string memory b) internal pure returns (int256) { bytes memory ba = bytes(a); bytes memory bb = bytes(b); uint256 minLength = ba.length < bb.length ? ba.length : bb.length; for (uint i = 0; i < minLength; i++) { if (ba[i] < bb[i]) return -1; if (ba[i] > bb[i]) return 1; } if (ba.length < bb.length) return -1; if (ba.length > bb.length) return 1; return 0; }}// 使用字符串库contract StringProcessor { using StringUtils for string; function processString(string memory input) external pure returns ( uint256 len, string memory sub, int256 cmp ) { len = input.length(); sub = input.substring(0, 5); cmp = input.compare("Hello"); }}8. Library 的 Gas 优化// 优化前:多次重复代码contract BeforeOptimization { function operation1(uint256 x) external pure returns (uint256) { // 复杂计算... return x * 2 + 1; } function operation2(uint256 x) external pure returns (uint256) { // 相同复杂计算... return x * 2 + 1; }}// 优化后:使用库复用代码library OptimizedMath { function complexCalculation(uint256 x) internal pure returns (uint256) { return x * 2 + 1; }}contract AfterOptimization { using OptimizedMath for uint256; function operation1(uint256 x) external pure returns (uint256) { return x.complexCalculation(); } function operation2(uint256 x) external pure returns (uint256) { return x.complexCalculation(); }}9. 总结Library 是 Solidity 中强大的代码复用工具:使用场景:工具函数、数据结构扩展、代码复用部署方式:internal 函数内联,external 函数需要部署最佳实践:优先使用 internal 函数使用 using for 语法提高可读性遵循命名规范利用 OpenZeppelin 等标准库Gas 优化:减少代码重复,提高部署效率
阅读 0·3月6日 21:50

Solidity 中的内联汇编(Inline Assembly)如何使用?有哪些注意事项?

Solidity 内联汇编允许开发者直接编写 EVM 汇编代码,用于优化 Gas 成本、执行底层操作或访问 Solidity 无法直接提供的功能。1. 基础语法Solidity 支持两种汇编方言:assembly { ... }(推荐)和 assembly { ... }。contract AssemblyBasic { function add(uint256 a, uint256 b) public pure returns (uint256) { uint256 result; assembly { // 使用 mload 和 mstore 操作内存 result := add(a, b) // := 是赋值操作符 } return result; } function getCaller() public view returns (address) { address caller; assembly { caller := caller() // 获取调用者地址 } return caller; }}2. 访问变量汇编代码可以读写 Solidity 变量,但需要注意变量类型和存储位置。contract VariableAccess { uint256 public storedValue; // storage 变量 function manipulateVariables(uint256 input) public returns (uint256) { uint256 localVar = 10; // memory 变量 assembly { // 读取 storage 变量 let stored := sload(storedValue.slot) // 写入 storage 变量 sstore(storedValue.slot, add(stored, input)) // 修改 memory 变量(注意:需要知道内存位置) // localVar 在内存中的位置需要计算 } return storedValue; } // 更安全的变量访问方式 function safeAccess(uint256 newValue) public { assembly { // 使用 .slot 访问 storage 变量位置 sstore(storedValue.slot, newValue) // 使用 .offset 访问结构体成员偏移 } }}3. 内存操作EVM 内存是一个线性字节数组,需要手动管理。contract MemoryOperations { function memoryDemo() public pure returns (bytes32) { bytes32 result; assembly { // 在内存位置 0x00 存储 32 字节数据 mstore(0x00, 0x1234567890abcdef) // 从内存位置 0x00 读取 32 字节 result := mload(0x00) // 只存储 1 字节(使用 mstore8) mstore8(0x20, 0xff) } return result; } function copyMemory(bytes memory data) public pure returns (bytes memory) { bytes memory result = new bytes(data.length); assembly { // 获取 data 的内存位置(跳过 32 字节长度前缀) let dataPtr := add(data, 0x20) // 获取 result 的内存位置 let resultPtr := add(result, 0x20) // 数据长度 let len := mload(data) // 复制内存 for { let i := 0 } lt(i, len) { i := add(i, 0x20) } { mstore(add(resultPtr, i), mload(add(dataPtr, i))) } } return result; }}4. 存储操作Storage 是永久存储,操作成本很高。contract StorageOperations { uint256 public value; mapping(address => uint256) public balances; function storageDemo(address user) public { assembly { // 简单 storage 变量 let slot := value.slot let current := sload(slot) sstore(slot, add(current, 1)) // mapping 的 storage 位置计算 // mapping[key] 的位置 = keccak256(key . slot) mstore(0x00, user) mstore(0x20, balances.slot) let mappingSlot := keccak256(0x00, 0x40) // 读取 mapping 值 let balance := sload(mappingSlot) // 写入 mapping 值 sstore(mappingSlot, add(balance, 100)) } }}5. 函数调用和返回contract FunctionCalls { function externalCall(address target, bytes memory data) public returns (bytes memory) { bytes memory result; assembly { // 获取 data 的长度和指针 let dataLen := mload(data) let dataPtr := add(data, 0x20) // 分配返回数据内存 result := mload(0x40) // 获取空闲内存指针 // 调用外部合约 let success := call( gas(), // 传递所有可用 gas target, // 目标地址 0, // 发送的 ETH 数量 dataPtr, // 输入数据指针 dataLen, // 输入数据长度 result, // 返回数据指针 0x40 // 返回数据最大长度 ) // 检查调用是否成功 if iszero(success) { revert(0, 0) } // 更新空闲内存指针 mstore(0x40, add(result, 0x60)) } return result; } // 使用 delegatecall function delegateCall(address target, bytes memory data) public returns (bytes memory) { bytes memory result; assembly { let dataLen := mload(data) let dataPtr := add(data, 0x20) result := mload(0x40) let success := delegatecall( gas(), target, dataPtr, dataLen, result, 0x40 ) if iszero(success) { revert(0, 0) } mstore(0x40, add(result, 0x60)) } return result; }}6. 条件判断和循环contract ControlFlow { function findMax(uint256[] memory arr) public pure returns (uint256) { require(arr.length > 0, "Empty array"); uint256 max; assembly { // 数组长度 let len := mload(arr) // 数组数据开始位置 let dataPtr := add(arr, 0x20) // 初始最大值设为第一个元素 max := mload(dataPtr) // 循环遍历 for { let i := 1 } lt(i, len) { i := add(i, 1) } { // 当前元素位置 let elemPtr := add(dataPtr, mul(i, 0x20)) let elem := mload(elemPtr) // 如果当前元素更大,更新最大值 if gt(elem, max) { max := elem } } } return max; } function conditional(uint256 x) public pure returns (uint256) { uint256 result; assembly { // if-else 逻辑 switch gt(x, 10) case 1 { result := mul(x, 2) } default { result := add(x, 5) } } return result; }}7. 实用优化示例Gas 优化的字符串拼接contract StringOptimization { // Solidity 方式(Gas 较高) function concatSolidity(string memory a, string memory b) public pure returns (string memory) { return string(abi.encodePacked(a, b)); } // 汇编方式(Gas 更低) function concatAssembly(string memory a, string memory b) public pure returns (string memory result) { assembly { let aLen := mload(a) let bLen := mload(b) let totalLen := add(aLen, bLen) // 分配内存 result := mload(0x40) mstore(result, totalLen) // 复制 a let aPtr := add(a, 0x20) let resultPtr := add(result, 0x20) // 使用 identity 预编译合约进行内存复制(更高效) // 或者手动复制 for { let i := 0 } lt(i, aLen) { i := add(i, 0x20) } { mstore(add(resultPtr, i), mload(add(aPtr, i))) } // 复制 b let bPtr := add(b, 0x20) let bResultPtr := add(resultPtr, aLen) for { let i := 0 } lt(i, bLen) { i := add(i, 0x20) } { mstore(add(bResultPtr, i), mload(add(bPtr, i))) } // 更新空闲内存指针 mstore(0x40, add(add(resultPtr, totalLen), 0x20)) } }}高效的数组操作contract ArrayOptimization { // 高效的数组元素删除(不保持顺序) function removeElement(uint256[] storage arr, uint256 index) internal { require(index < arr.length, "Invalid index"); assembly { // 获取数组长度位置 let lenSlot := arr.slot let len := sload(lenSlot) // 如果不是最后一个元素,用最后一个元素替换 if lt(add(index, 1), len) { // 计算存储位置 let lastIndex := sub(len, 1) let indexSlot := add(keccak256(lenSlot, 0x20), index) let lastSlot := add(keccak256(lenSlot, 0x20), lastIndex) // 替换 sstore(indexSlot, sload(lastSlot)) } // 减少长度 sstore(lenSlot, sub(len, 1)) } }}8. 安全注意事项危险操作示例contract AssemblyDangers { // 危险:直接操作 storage 可能导致数据损坏 function dangerousStorageWrite(uint256 slot, uint256 value) public { assembly { sstore(slot, value) // 可以写入任意 slot! } } // 危险:内存溢出 function dangerousMemory() public pure { assembly { // 写入超出分配范围的内存 mstore(0x1000000, 1) // 可能覆盖其他数据 } } // 危险:未检查的外部调用 function dangerousCall(address target) public { assembly { let success := call(gas(), target, 0, 0, 0, 0, 0) // 没有检查 success! } }}安全使用指南contract SafeAssembly { // 安全:验证输入 function safeStorageWrite(bytes32 slot, uint256 value) public { // 只允许写入特定 slot require(slot == keccak256("allowed_slot"), "Invalid slot"); assembly { sstore(slot, value) } } // 安全:检查内存边界 function safeMemoryOperation(bytes memory data) public pure returns (bytes32) { require(data.length >= 32, "Data too short"); bytes32 result; assembly { result := mload(add(data, 0x20)) } return result; } // 安全:检查外部调用结果 function safeExternalCall(address target, bytes memory data) public returns (bool) { bool success; assembly { let dataLen := mload(data) let dataPtr := add(data, 0x20) success := call( gas(), target, 0, dataPtr, dataLen, 0, 0 ) } return success; }}9. 常见使用场景| 场景 | 汇编优势 | 示例 || ------ | ------------ | ------- || Gas 优化 | 减少操作码 | 自定义存储布局 || 底层操作 | 访问 EVM 特性 | 调用预编译合约 || 复杂计算 | 更高效的算法 | 加密操作 || 代理合约 | delegatecall | 可升级合约 || 数据编码 | 自定义序列化 | 紧凑存储 |10. 最佳实践仅在必要时使用汇编:Solidity 编译器已经很优化充分测试:汇编代码难以调试,需要更多测试添加详细注释:汇编代码可读性差,需要解释使用常量:避免魔法数字验证所有输入:防止意外行为考虑使用库:将汇编封装在库中复用审计必不可少:汇编代码必须经过专业审计library SafeAssembly { // 将汇编操作封装在库中 function safeDelegateCall(address target, bytes memory data) internal returns (bool success, bytes memory result) { // 实现... }}​
阅读 0·3月6日 21:50

Solidity 中的继承机制是如何工作的?抽象合约和接口有什么区别?

Solidity 支持面向对象编程的继承特性,允许合约复用代码、建立层次关系。同时,抽象合约和接口提供了定义标准规范的机制。1. 基础继承Solidity 使用 is 关键字实现继承,支持多重继承。// 父合约contract Animal { string public name; constructor(string memory _name) { name = _name; } function speak() public pure virtual returns (string memory) { return "Some sound"; }}// 子合约继承父合约contract Dog is Animal { constructor(string memory _name) Animal(_name) {} // 重写父合约函数 function speak() public pure override returns (string memory) { return "Woof!"; }}// 多重继承contract PetDog is Animal, Pet { constructor(string memory _name) Animal(_name) Pet(_name) {} function speak() public pure override(Animal, Pet) returns (string memory) { return "Friendly Woof!"; }}2. 构造函数继承子合约需要显式调用父合约的构造函数。contract Parent { uint256 public value; constructor(uint256 _value) { value = _value; }}// 方式 1:在继承列表中传递参数contract Child1 is Parent(100) { // value 自动设置为 100}// 方式 2:在子合约构造函数中传递参数contract Child2 is Parent { constructor(uint256 _value) Parent(_value) { // 可以动态设置 value }}// 方式 3:使用输入参数contract Child3 is Parent { constructor(uint256 _value) Parent(_value * 2) { // 可以对参数进行处理 }}3. 函数重写(Override)使用 virtual 和 override 关键字控制函数重写。contract Base { // virtual 允许子合约重写 function getValue() public pure virtual returns (uint256) { return 1; } // 没有 virtual,不能重写 function fixedValue() public pure returns (uint256) { return 100; }}contract Derived is Base { // override 表示重写父合约函数 function getValue() public pure override returns (uint256) { return 2; } // 错误:不能重写没有 virtual 的函数 // function fixedValue() public pure override returns (uint256) { // return 200; // }}4. 多重继承与线性化Solidity 使用 C3 线性化算法解决多重继承的冲突。contract A { function foo() public pure virtual returns (string memory) { return "A"; }}contract B is A { function foo() public pure virtual override returns (string memory) { return "B"; }}contract C is A { function foo() public pure virtual override returns (string memory) { return "C"; }}// 多重继承:D 继承 B 和 C// 顺序很重要!D is B, C 意味着 B 在 C 前面contract D is B, C { // 必须重写 foo,因为 B 和 C 都重写了 A 的 foo function foo() public pure override(B, C) returns (string memory) { return super.foo(); // 调用 C.foo()(最右边的基类) }}// 顺序不同,结果不同contract E is C, B { function foo() public pure override(C, B) returns (string memory) { return super.foo(); // 调用 B.foo() }}5. 抽象合约(Abstract Contract)抽象合约是包含至少一个未实现函数的合约,不能直接部署。// 抽象合约abstract contract Animal { string public name; constructor(string memory _name) { name = _name; } // 未实现的函数(抽象函数) function speak() public pure virtual returns (string memory); // 已实现的函数 function getName() public view returns (string memory) { return name; }}// 具体合约必须实现所有抽象函数contract Dog is Animal { constructor(string memory _name) Animal(_name) {} function speak() public pure override returns (string memory) { return "Woof!"; }}// 错误:不能直接部署抽象合约// Animal animal = new Animal("Test"); // 编译错误!6. 接口(Interface)接口是更严格的抽象,只能包含函数声明,不能包含实现和状态变量。// ERC20 接口示例interface IERC20 { // 函数声明(没有实现) function totalSupply() external view returns (uint256); function balanceOf(address account) external view returns (uint256); function transfer(address to, uint256 amount) external returns (bool); function allowance(address owner, address spender) external view returns (uint256); function approve(address spender, uint256 amount) external returns (bool); function transferFrom(address from, address to, uint256 amount) external returns (bool); // 事件 event Transfer(address indexed from, address indexed to, uint256 value); event Approval(address indexed owner, address indexed spender, uint256 value);}// 实现接口contract MyToken is IERC20 { mapping(address => uint256) private _balances; mapping(address => mapping(address => uint256)) private _allowances; uint256 private _totalSupply; function totalSupply() external view override returns (uint256) { return _totalSupply; } function balanceOf(address account) external view override returns (uint256) { return _balances[account]; } function transfer(address to, uint256 amount) external override returns (bool) { // 实现逻辑... return true; } function allowance(address owner, address spender) external view override returns (uint256) { return _allowances[owner][spender]; } function approve(address spender, uint256 amount) external override returns (bool) { // 实现逻辑... return true; } function transferFrom(address from, address to, uint256 amount) external override returns (bool) { // 实现逻辑... return true; }}7. 抽象合约 vs 接口对比| 特性 | 抽象合约 (Abstract Contract) | 接口 (Interface) || ------ | ------------------------ | -------------- || 构造函数 | ✅ 可以有 | ❌ 不能有 || 状态变量 | ✅ 可以有 | ❌ 不能有 || 函数实现 | ✅ 可以有 | ❌ 不能有 || 函数可见性 | 任意 | 必须是 external || 继承其他合约 | ✅ 可以 | ❌ 不能(只能继承接口) || 修饰符 | ✅ 可以有 | ❌ 不能有 || 使用场景 | 部分实现的基础合约 | 完全抽象的规范定义 |8. 实际应用示例使用抽象合约构建基础功能abstract contract Ownable { address public owner; constructor() { owner = msg.sender; } modifier onlyOwner() { require(msg.sender == owner, "Not owner"); _; }}abstract contract Pausable is Ownable { bool public paused; modifier whenNotPaused() { require(!paused, "Paused"); _; } function pause() public onlyOwner { paused = true; } function unpause() public onlyOwner { paused = false; }}contract MyContract is Pausable { function sensitiveOperation() public whenNotPaused { // 只有未暂停时才能执行 }}使用接口实现标准兼容interface IERC721 { function balanceOf(address owner) external view returns (uint256 balance); function ownerOf(uint256 tokenId) external view returns (address owner); function safeTransferFrom(address from, address to, uint256 tokenId) external; function transferFrom(address from, address to, uint256 tokenId) external; function approve(address to, uint256 tokenId) external; function getApproved(uint256 tokenId) external view returns (address operator); function setApprovalForAll(address operator, bool _approved) external; function isApprovedForAll(address owner, address operator) external view returns (bool);}contract NFTMarketplace { // 使用接口与任何 ERC721 合约交互 function buyNFT(address nftContract, uint256 tokenId) public payable { IERC721 nft = IERC721(nftContract); address seller = nft.ownerOf(tokenId); // 验证支付... nft.safeTransferFrom(seller, msg.sender, tokenId); }}9. 继承的最佳实践使用 OpenZeppelin 标准库:import "@openzeppelin/contracts/token/ERC20/ERC20.sol";import "@openzeppelin/contracts/access/Ownable.sol";contract MyToken is ERC20, Ownable { constructor() ERC20("MyToken", "MTK") { _mint(msg.sender, 1000000 * 10 ** decimals()); }}注意继承顺序:// 正确:基础合约在前contract Good is Ownable, Pausable, ERC20 {}// 避免:顺序混乱可能导致意外行为contract Bad is ERC20, Pausable, Ownable {}明确使用 override:function transfer(address to, uint256 amount) public override(ERC20, IERC20) // 明确指定重写的接口 returns (bool) { return super.transfer(to, amount);}合理使用抽象和接口:需要部分实现时用抽象合约定义标准规范时用接口避免过度设计,保持简单
阅读 0·3月6日 21:50

Solidity 中事件(Event)的作用是什么?如何优化 Gas 成本?

事件(Event)是 Solidity 中一种重要的日志记录机制,用于在区块链上记录特定操作的发生,供外部应用监听和查询。事件的基本概念定义:事件是合约与外部世界通信的方式,将数据写入区块链日志,供 DApp 前端监听。特点:数据存储在交易收据的日志中,不占用合约 storage比 storage 存储便宜得多(每个 topic 消耗 375 Gas,每个数据字节 8 Gas)可以被前端应用通过 RPC 订阅和监听支持索引参数,便于高效过滤查询事件的基本用法contract EventExample { // 定义事件 event Transfer(address indexed from, address indexed to, uint256 amount); event Approval(address indexed owner, address indexed spender, uint256 value); event Log(string message); function transfer(address to, uint256 amount) public { // 执行转账逻辑... // 触发事件 emit Transfer(msg.sender, to, amount); } function approve(address spender, uint256 value) public { // 执行授权逻辑... emit Approval(msg.sender, spender, value); }}索引参数(Indexed Parameters)最多可以对 3 个参数使用 indexed 关键字,这些参数会被存储为 topics,便于过滤查询。contract IndexedEventExample { // indexed 参数最多 3 个 event Transfer( address indexed from, // topic[1] address indexed to, // topic[2] uint256 amount // data ); // 3 个 indexed 参数 event OrderCreated( bytes32 indexed orderId, // topic[1] address indexed buyer, // topic[2] address indexed seller, // topic[3] uint256 amount, // data uint256 timestamp // data ); function createOrder(address seller, uint256 amount) public { bytes32 orderId = keccak256(abi.encodePacked(msg.sender, block.timestamp)); emit OrderCreated(orderId, msg.sender, seller, amount, block.timestamp); }}事件的 Gas 成本分析| 操作 | Gas 成本 || ------------------ | ---------- || 基础事件成本 | 375 Gas || 每个 indexed topic | 375 Gas || 每个数据字节 | 8 Gas || 存储到 storage(32 字节) | 20,000 Gas |对比示例:contract GasComparison { // 方式 1:使用 storage(昂贵) struct Transaction { address from; address to; uint256 amount; uint256 timestamp; } Transaction[] public transactions; function recordWithStorage(address to, uint256 amount) public { transactions.push(Transaction(msg.sender, to, amount, block.timestamp)); // Gas 成本:约 20,000+ Gas } // 方式 2:使用事件(便宜) event TransactionRecorded( address indexed from, address indexed to, uint256 amount, uint256 timestamp ); function recordWithEvent(address to, uint256 amount) public { emit TransactionRecorded(msg.sender, to, amount, block.timestamp); // Gas 成本:约 1,000-2,000 Gas }}Gas 优化技巧1. 合理使用 indexed 参数contract IndexedOptimization { // 不推荐:对不需要过滤的大字段使用 indexed event BadEvent(string indexed largeData); // 浪费 Gas // 推荐:只对需要过滤的地址或 ID 使用 indexed event GoodEvent( address indexed user, // 需要按用户过滤 uint256 indexed itemId, // 需要按物品过滤 string description // 不需要过滤,不使用 indexed );}2. 减少事件参数数量contract ParameterOptimization { // 不推荐:包含过多参数 event VerboseEvent( address user, uint256 amount, uint256 timestamp, uint256 blockNumber, bytes32 txHash, string metadata ); // 推荐:只包含必要信息 event OptimizedEvent( address indexed user, uint256 amount, string metadata ); // 时间戳和区块号可以通过交易信息获取}3. 使用匿名事件匿名事件不使用事件签名作为 topic[0],可以节省 Gas。contract AnonymousEvent { // 匿名事件,节省一个 topic event QuickLog(address indexed user, uint256 amount) anonymous; function quickLog(uint256 amount) public { emit QuickLog(msg.sender, amount); // 节省约 375 Gas }}4. 批量事件 vs 单个事件contract BatchEvent { // 方式 1:逐个触发事件(Gas 高) event SingleTransfer(address indexed to, uint256 amount); function batchTransferV1(address[] calldata recipients, uint256[] calldata amounts) public { for (uint i = 0; i < recipients.length; i++) { // 执行转账... emit SingleTransfer(recipients[i], amounts[i]); // 每个事件约 375+ Gas } } // 方式 2:批量事件(更优) event BatchTransfer(address[] recipients, uint256[] amounts); function batchTransferV2(address[] calldata recipients, uint256[] calldata amounts) public { // 执行批量转账... emit BatchTransfer(recipients, amounts); // 单个事件,更省 Gas }}事件的实际应用场景1. ERC20 Token 标准事件interface IERC20 { event Transfer(address indexed from, address indexed to, uint256 value); event Approval(address indexed owner, address indexed spender, uint256 value);}2. DeFi 协议事件contract DeFiProtocol { event Deposit( address indexed user, address indexed token, uint256 amount, uint256 shares ); event Withdraw( address indexed user, address indexed token, uint256 amount, uint256 shares ); event RewardClaimed( address indexed user, address indexed rewardToken, uint256 amount ); function deposit(address token, uint256 amount) external { // 存款逻辑... uint256 shares = calculateShares(amount); emit Deposit(msg.sender, token, amount, shares); }}前端监听事件// 使用 ethers.js 监听事件const contract = new ethers.Contract(address, abi, provider);// 监听所有 Transfer 事件contract.on("Transfer", (from, to, amount, event) => { console.log(`Transfer: ${from} -> ${to}, Amount: ${amount}`);});// 过滤特定地址的事件const filter = contract.filters.Transfer(userAddress, null);contract.on(filter, (from, to, amount, event) => { console.log(`Outgoing transfer from ${from}`);});// 查询历史事件const events = await contract.queryFilter("Transfer", fromBlock, toBlock);最佳实践对关键操作触发事件:所有状态变更都应该有对应的事件合理使用 indexed:只对需要过滤查询的参数使用 indexed事件命名规范:使用过去时态(如 Transfer、Approved)包含完整信息:事件中包含足够的信息,避免前端需要额外查询Gas 优化:对于高频操作,考虑批量事件或匿名事件文档化:在合约文档中说明所有事件及其用途
阅读 0·3月6日 21:49

Solidity 中如何实现一个去中心化交易所(DEX)的核心功能?

去中心化交易所(DEX)是 DeFi 生态的核心组件。实现一个基础的 DEX 需要理解自动做市商(AMM)机制、流动性池、价格计算等核心概念。1. DEX 核心概念/*DEX 核心组件:1. 流动性池(Liquidity Pool) - 包含两种代币的储备 - 使用恒定乘积公式:x * y = k2. 自动做市商(AMM) - 无需订单簿 - 算法自动定价 - 流动性提供者赚取手续费3. 价格计算 - 基于储备比例 - 考虑滑点和手续费4. 流动性代币(LP Token) - 代表流动性份额 - 可赎回底层资产*/2. 基础 AMM 实现contract BasicAMM { // 代币接口 IERC20 public token0; IERC20 public token1; // 储备量 uint256 public reserve0; uint256 public reserve1; // 流动性代币总量 uint256 public totalSupply; mapping(address => uint256) public balanceOf; // 手续费(0.3% = 30 / 10000) uint256 public constant FEE = 30; uint256 public constant FEE_DENOMINATOR = 10000; // 最小流动性(防止除零) uint256 public constant MINIMUM_LIQUIDITY = 1000; // 事件 event Mint(address indexed sender, uint256 amount0, uint256 amount1); event Burn(address indexed sender, uint256 amount0, uint256 amount1, address indexed to); event Swap( address indexed sender, uint256 amount0In, uint256 amount1In, uint256 amount0Out, uint256 amount1Out, address indexed to ); event Sync(uint256 reserve0, uint256 reserve1); constructor(address _token0, address _token1) { token0 = IERC20(_token0); token1 = IERC20(_token1); } // 添加流动性 function addLiquidity( uint256 _amount0Desired, uint256 _amount1Desired, uint256 _amount0Min, uint256 _amount1Min, address _to ) external returns (uint256 liquidity) { // 计算需要添加的数量 (uint256 amount0, uint256 amount1) = _calculateLiquidity( _amount0Desired, _amount1Desired, _amount0Min, _amount1Min ); // 转账 token0.transferFrom(msg.sender, address(this), amount0); token1.transferFrom(msg.sender, address(this), amount1); // 计算流动性代币 if (totalSupply == 0) { liquidity = sqrt(amount0 * amount1) - MINIMUM_LIQUIDITY; _mint(address(0), MINIMUM_LIQUIDITY); // 永久锁定 } else { liquidity = min( (amount0 * totalSupply) / reserve0, (amount1 * totalSupply) / reserve1 ); } require(liquidity > 0, "Insufficient liquidity minted"); _mint(_to, liquidity); // 更新储备 _updateReserves(); emit Mint(msg.sender, amount0, amount1); } // 移除流动性 function removeLiquidity( uint256 _liquidity, uint256 _amount0Min, uint256 _amount1Min, address _to ) external returns (uint256 amount0, uint256 amount1) { // 计算可赎回的数量 uint256 balance0 = token0.balanceOf(address(this)); uint256 balance1 = token1.balanceOf(address(this)); amount0 = (_liquidity * balance0) / totalSupply; amount1 = (_liquidity * balance1) / totalSupply; require(amount0 >= _amount0Min, "Insufficient amount0"); require(amount1 >= _amount1Min, "Insufficient amount1"); // 销毁流动性代币 _burn(msg.sender, _liquidity); // 转账 token0.transfer(_to, amount0); token1.transfer(_to, amount1); // 更新储备 _updateReserves(); emit Burn(msg.sender, amount0, amount1, _to); } // 交换代币(token0 -> token1) function swap0For1( uint256 _amount0In, uint256 _amount1OutMin, address _to ) external returns (uint256 amount1Out) { require(_amount0In > 0, "Insufficient input amount"); // 计算输出数量(考虑手续费) amount1Out = getAmountOut(_amount0In, reserve0, reserve1); require(amount1Out >= _amount1OutMin, "Insufficient output amount"); // 转账 token0.transferFrom(msg.sender, address(this), _amount0In); token1.transfer(_to, amount1Out); // 更新储备 _updateReserves(); emit Swap(msg.sender, _amount0In, 0, 0, amount1Out, _to); } // 交换代币(token1 -> token0) function swap1For0( uint256 _amount1In, uint256 _amount0OutMin, address _to ) external returns (uint256 amount0Out) { require(_amount1In > 0, "Insufficient input amount"); amount0Out = getAmountOut(_amount1In, reserve1, reserve0); require(amount0Out >= _amount0OutMin, "Insufficient output amount"); token1.transferFrom(msg.sender, address(this), _amount1In); token0.transfer(_to, amount0Out); _updateReserves(); emit Swap(msg.sender, 0, _amount1In, amount0Out, 0, _to); } // 计算输出数量(恒定乘积公式) function getAmountOut( uint256 _amountIn, uint256 _reserveIn, uint256 _reserveOut ) public pure returns (uint256 amountOut) { require(_amountIn > 0, "Insufficient input amount"); require(_reserveIn > 0 && _reserveOut > 0, "Insufficient liquidity"); uint256 amountInWithFee = _amountIn * (FEE_DENOMINATOR - FEE); uint256 numerator = amountInWithFee * _reserveOut; uint256 denominator = (_reserveIn * FEE_DENOMINATOR) + amountInWithFee; amountOut = numerator / denominator; } // 计算输入数量 function getAmountIn( uint256 _amountOut, uint256 _reserveIn, uint256 _reserveOut ) public pure returns (uint256 amountIn) { require(_amountOut > 0, "Insufficient output amount"); require(_reserveIn > 0 && _reserveOut > 0, "Insufficient liquidity"); uint256 numerator = _reserveIn * _amountOut * FEE_DENOMINATOR; uint256 denominator = (_reserveOut - _amountOut) * (FEE_DENOMINATOR - FEE); amountIn = (numerator / denominator) + 1; } // 计算流动性 function _calculateLiquidity( uint256 _amount0Desired, uint256 _amount1Desired, uint256 _amount0Min, uint256 _amount1Min ) internal view returns (uint256 amount0, uint256 amount1) { if (reserve0 == 0 && reserve1 == 0) { // 首次添加流动性 (amount0, amount1) = (_amount0Desired, _amount1Desired); } else { // 按比例添加 uint256 amount1Optimal = (_amount0Desired * reserve1) / reserve0; if (amount1Optimal <= _amount1Desired) { require(amount1Optimal >= _amount1Min, "Insufficient amount1"); (amount0, amount1) = (_amount0Desired, amount1Optimal); } else { uint256 amount0Optimal = (_amount1Desired * reserve0) / reserve1; assert(amount0Optimal <= _amount0Desired); require(amount0Optimal >= _amount0Min, "Insufficient amount0"); (amount0, amount1) = (amount0Optimal, _amount1Desired); } } } // 更新储备 function _updateReserves() internal { reserve0 = token0.balanceOf(address(this)); reserve1 = token1.balanceOf(address(this)); emit Sync(reserve0, reserve1); } // 流动性代币操作 function _mint(address _to, uint256 _amount) internal { totalSupply += _amount; balanceOf[_to] += _amount; } function _burn(address _from, uint256 _amount) internal { balanceOf[_from] -= _amount; totalSupply -= _amount; } // 数学函数 function sqrt(uint256 y) internal pure returns (uint256 z) { if (y > 3) { z = y; uint256 x = y / 2 + 1; while (x < z) { z = x; x = (y / x + x) / 2; } } else if (y != 0) { z = 1; } } function min(uint256 a, uint256 b) internal pure returns (uint256) { return a < b ? a : b; }}interface IERC20 { function totalSupply() external view returns (uint256); function balanceOf(address account) external view returns (uint256); function transfer(address to, uint256 amount) external returns (bool); function transferFrom(address from, address to, uint256 amount) external returns (bool);}3. 路由合约contract Router { address public factory; constructor(address _factory) { factory = _factory; } // 添加流动性 function addLiquidity( address _tokenA, address _tokenB, uint256 _amountADesired, uint256 _amountBDesired, uint256 _amountAMin, uint256 _amountBMin, address _to, uint256 _deadline ) external returns (uint256 amountA, uint256 amountB, uint256 liquidity) { require(block.timestamp <= _deadline, "Expired"); // 获取或创建交易对 address pair = Factory(factory).getPair(_tokenA, _tokenB); if (pair == address(0)) { pair = Factory(factory).createPair(_tokenA, _tokenB); } // 转账代币到交易对 IERC20(_tokenA).transferFrom(msg.sender, pair, _amountADesired); IERC20(_tokenB).transferFrom(msg.sender, pair, _amountBDesired); // 添加流动性 liquidity = BasicAMM(pair).addLiquidity( _amountADesired, _amountBDesired, _amountAMin, _amountBMin, _to ); // 返回实际添加的数量 amountA = _amountADesired; amountB = _amountBDesired; } // 移除流动性 function removeLiquidity( address _tokenA, address _tokenB, uint256 _liquidity, uint256 _amountAMin, uint256 _amountBMin, address _to, uint256 _deadline ) external returns (uint256 amountA, uint256 amountB) { require(block.timestamp <= _deadline, "Expired"); address pair = Factory(factory).getPair(_tokenA, _tokenB); require(pair != address(0), "Pair does not exist"); // 转账 LP 代币到交易对 IERC20(pair).transferFrom(msg.sender, pair, _liquidity); // 移除流动性 (amountA, amountB) = BasicAMM(pair).removeLiquidity( _liquidity, _amountAMin, _amountBMin, _to ); } // 交换代币(精确输入) function swapExactTokensForTokens( uint256 _amountIn, uint256 _amountOutMin, address[] calldata _path, address _to, uint256 _deadline ) external returns (uint256[] memory amounts) { require(block.timestamp <= _deadline, "Expired"); require(_path.length >= 2, "Invalid path"); amounts = getAmountsOut(_amountIn, _path); require(amounts[amounts.length - 1] >= _amountOutMin, "Insufficient output"); // 转账第一个代币到第一个交易对 IERC20(_path[0]).transferFrom( msg.sender, Factory(factory).getPair(_path[0], _path[1]), amounts[0] ); // 执行交换 _swap(amounts, _path, _to); } // 交换代币(精确输出) function swapTokensForExactTokens( uint256 _amountOut, uint256 _amountInMax, address[] calldata _path, address _to, uint256 _deadline ) external returns (uint256[] memory amounts) { require(block.timestamp <= _deadline, "Expired"); require(_path.length >= 2, "Invalid path"); amounts = getAmountsIn(_amountOut, _path); require(amounts[0] <= _amountInMax, "Excessive input"); IERC20(_path[0]).transferFrom( msg.sender, Factory(factory).getPair(_path[0], _path[1]), amounts[0] ); _swap(amounts, _path, _to); } // 内部交换函数 function _swap( uint256[] memory _amounts, address[] memory _path, address _to ) internal { for (uint i = 0; i < _path.length - 1; i++) { (address input, address output) = (_path[i], _path[i + 1]); address pair = Factory(factory).getPair(input, output); uint256 amountOut = _amounts[i + 1]; (uint256 amount0Out, uint256 amount1Out) = input < output ? (uint256(0), amountOut) : (amountOut, uint256(0)); address to = i < _path.length - 2 ? Factory(factory).getPair(output, _path[i + 2]) : _to; BasicAMM(pair).swap(amount0Out, amount1Out, to); } } // 计算输出数量(多跳) function getAmountsOut( uint256 _amountIn, address[] memory _path ) public view returns (uint256[] memory amounts) { require(_path.length >= 2, "Invalid path"); amounts = new uint256[](_path.length); amounts[0] = _amountIn; for (uint i = 0; i < _path.length - 1; i++) { address pair = Factory(factory).getPair(_path[i], _path[i + 1]); require(pair != address(0), "Pair does not exist"); (uint256 reserveIn, uint256 reserveOut) = getReserves(pair, _path[i], _path[i + 1]); amounts[i + 1] = BasicAMM(pair).getAmountOut(amounts[i], reserveIn, reserveOut); } } // 计算输入数量(多跳) function getAmountsIn( uint256 _amountOut, address[] memory _path ) public view returns (uint256[] memory amounts) { require(_path.length >= 2, "Invalid path"); amounts = new uint256[](_path.length); amounts[amounts.length - 1] = _amountOut; for (uint i = _path.length - 1; i > 0; i--) { address pair = Factory(factory).getPair(_path[i - 1], _path[i]); require(pair != address(0), "Pair does not exist"); (uint256 reserveIn, uint256 reserveOut) = getReserves(pair, _path[i - 1], _path[i]); amounts[i - 1] = BasicAMM(pair).getAmountIn(amounts[i], reserveIn, reserveOut); } } // 获取储备 function getReserves( address _pair, address _tokenA, address _tokenB ) internal view returns (uint256 reserveA, uint256 reserveB) { (uint256 reserve0, uint256 reserve1) = BasicAMM(_pair).getReserves(); (reserveA, reserveB) = _tokenA < _tokenB ? (reserve0, reserve1) : (reserve1, reserve0); }}// 工厂合约contract Factory { mapping(address => mapping(address => address)) public getPair; address[] public allPairs; event PairCreated(address indexed token0, address indexed token1, address pair, uint256); function createPair(address _tokenA, address _tokenB) external returns (address pair) { require(_tokenA != _tokenB, "Identical addresses"); (address token0, address token1) = _tokenA < _tokenB ? (_tokenA, _tokenB) : (_tokenB, _tokenA); require(token0 != address(0), "Zero address"); require(getPair[token0][token1] == address(0), "Pair exists"); // 创建交易对合约(简化版,实际使用 create2) bytes memory bytecode = type(BasicAMM).creationCode; bytes32 salt = keccak256(abi.encodePacked(token0, token1)); assembly { pair := create2(0, add(bytecode, 32), mload(bytecode), salt) } BasicAMM(pair).initialize(token0, token1); getPair[token0][token1] = pair; getPair[token1][token0] = pair; allPairs.push(pair); emit PairCreated(token0, token1, pair, allPairs.length); }}4. 价格预言机contract PriceOracle { // 累积价格 uint256 public price0CumulativeLast; uint256 public price1CumulativeLast; // 上次更新时间 uint32 public blockTimestampLast; // 储备量 uint112 public reserve0; uint112 public reserve1; // 更新累积价格 function _update( uint256 _balance0, uint256 _balance1, uint112 _reserve0, uint112 _reserve1 ) internal { require( _balance0 <= type(uint112).max && _balance1 <= type(uint112).max, "Overflow" ); uint32 blockTimestamp = uint32(block.timestamp % 2**32); uint32 timeElapsed = blockTimestamp - blockTimestampLast; if (timeElapsed > 0 && _reserve0 != 0 && _reserve1 != 0) { // 累积价格 = 价格 * 时间 price0CumulativeLast += uint256(UQ112x112.encode(_reserve1).uqdiv(_reserve0)) * timeElapsed; price1CumulativeLast += uint256(UQ112x112.encode(_reserve0).uqdiv(_reserve1)) * timeElapsed; } reserve0 = uint112(_balance0); reserve1 = uint112(_balance1); blockTimestampLast = blockTimestamp; } // 获取当前价格 function getCurrentPrice() external view returns (uint256 price0, uint256 price1) { price0 = (uint256(reserve1) * 1e18) / reserve0; price1 = (uint256(reserve0) * 1e18) / reserve1; } // 计算 TWAP(时间加权平均价格) function consult( address _token, uint256 _amountIn ) external view returns (uint256 amountOut) { // 简化的 TWAP 计算 if (_token == token0) { amountOut = (_amountIn * reserve1) / reserve0; } else { amountOut = (_amountIn * reserve0) / reserve1; } }}// UQ112x112 库(定点数运算)library UQ112x112 { uint224 constant Q112 = 2**112; function encode(uint112 y) internal pure returns (uint224 z) { z = uint224(y) * Q112; } function uqdiv(uint224 x, uint112 y) internal pure returns (uint224 z) { z = x / uint224(y); }}5. 闪电贷功能contract FlashSwap { // 闪电贷回调接口 interface IFlashSwapCallee { function uniswapV2Call( address sender, uint256 amount0, uint256 amount1, bytes calldata data ) external; } // 执行闪电贷 function swap( uint256 _amount0Out, uint256 _amount1Out, address _to, bytes calldata _data ) external { require(_amount0Out > 0 || _amount1Out > 0, "Insufficient output"); // 转账给接收者 if (_amount0Out > 0) token0.transfer(_to, _amount0Out); if (_amount1Out > 0) token1.transfer(_to, _amount1Out); // 如果有数据,执行回调 if (_data.length > 0) { IFlashSwapCallee(_to).uniswapV2Call( msg.sender, _amount0Out, _amount1Out, _data ); } // 验证偿还 uint256 balance0 = token0.balanceOf(address(this)); uint256 balance1 = token1.balanceOf(address(this)); uint256 amount0In = balance0 > reserve0 - _amount0Out ? balance0 - (reserve0 - _amount0Out) : 0; uint256 amount1In = balance1 > reserve1 - _amount1Out ? balance1 - (reserve1 - _amount1Out) : 0; require(amount0In > 0 || amount1In > 0, "Insufficient input"); // 验证恒定乘积 uint256 balance0Adjusted = balance0 * 1000 - amount0In * 3; uint256 balance1Adjusted = balance1 * 1000 - amount1In * 3; require( balance0Adjusted * balance1Adjusted >= uint256(reserve0) * reserve1 * 1000**2, "K" ); _update(balance0, balance1, reserve0, reserve1); }}// 闪电贷套利示例contract Arbitrageur is IFlashSwapCallee { address public owner; constructor() { owner = msg.sender; } function executeArbitrage( address _pair, uint256 _amount0, uint256 _amount1, bytes calldata _data ) external { // 触发闪电贷 FlashSwap(_pair).swap(_amount0, _amount1, address(this), _data); } function uniswapV2Call( address _sender, uint256 _amount0, uint256 _amount1, bytes calldata _data ) external override { // 解码数据 (address targetPair, uint256 repayAmount) = abi.decode(_data, (address, uint256)); // 执行套利逻辑 // 1. 在其他交易所卖出 // 2. 获得利润 // 3. 偿还闪电贷 // 偿还贷款(加上手续费) if (_amount0 > 0) { IERC20(token0).transfer(msg.sender, repayAmount); } if (_amount1 > 0) { IERC20(token1).transfer(msg.sender, repayAmount); } // 发送利润给所有者 // ... }}6. DEX 安全考虑contract DEXSecurity { /* DEX 安全要点: 1. 重入攻击防护 - 使用 Checks-Effects-Interactions 模式 - 使用 ReentrancyGuard 2. 价格操纵防护 - 使用 TWAP 预言机 - 设置最大滑点 - 交易限额 3. 无常损失管理 - 流动性提供者教育 - 激励设计 4. 前端运行防护 - 提交-揭示模式 - 批量拍卖 5. 溢出检查 - Solidity 0.8.0+ 自动检查 - 或使用 SafeMath */}// 安全增强的 AMMcontract SecureAMM is ReentrancyGuard { // 最大滑点(1%) uint256 public constant MAX_SLIPPAGE = 100; uint256 public constant SLIPPAGE_DENOMINATOR = 10000; // 交易限额 uint256 public maxSwapAmount; // 暂停功能 bool public paused; address public guardian; modifier whenNotPaused() { require(!paused, "Paused"); _; } modifier onlyGuardian() { require(msg.sender == guardian, "Not guardian"); _; } // 带滑点保护的交换 function swapWithSlippageProtection( uint256 _amountIn, uint256 _minAmountOut, address _to ) external nonReentrant whenNotPaused { require(_amountIn <= maxSwapAmount, "Exceeds max swap"); uint256 amountOut = getAmountOut(_amountIn); // 计算预期输出 uint256 expectedOut = (_amountIn * reserve1) / reserve0; uint256 slippage = ((expectedOut - amountOut) * SLIPPAGE_DENOMINATOR) / expectedOut; require(slippage <= MAX_SLIPPAGE, "Slippage too high"); require(amountOut >= _minAmountOut, "Insufficient output"); // 执行交换... } // 紧急暂停 function pause() external onlyGuardian { paused = true; } function unpause() external onlyGuardian { paused = false; }}import "@openzeppelin/contracts/security/ReentrancyGuard.sol";7. 总结DEX 核心功能实现要点:AMM 机制:恒定乘积公式 x * y = k自动价格发现无需订单簿流动性管理:添加/移除流动性LP 代币代表份额按比例存入交易功能:精确输入/输出交换多跳路由滑点保护高级功能:价格预言机闪电贷治理集成安全考虑:重入防护价格操纵防护紧急暂停访问控制Gas 优化:使用自定义错误批量操作存储优化
阅读 0·3月6日 21:49

Solidity 中如何实现合约升级模式?有哪些常见的升级方案?

合约升级是智能合约开发中的重要话题。由于区块链的不可篡改性,一旦合约部署就无法修改,因此需要设计升级机制来修复漏洞或添加新功能。1. 为什么需要合约升级// 问题:合约部署后无法修改contract ImmutableContract { uint256 public value = 100; // 如果发现 bug,无法直接修复 function setValue(uint256 _value) external { value = _value; // 假设这里有逻辑错误 }}// 解决方案:使用代理模式实现升级2. 代理模式(Proxy Pattern)基本原理代理模式通过分离存储和逻辑来实现升级:Proxy 合约:持有状态存储,委托调用到 Implementation 合约Implementation 合约:包含业务逻辑,可以被替换// 简单的代理合约contract SimpleProxy { address public implementation; address public admin; constructor(address _implementation) { implementation = _implementation; admin = msg.sender; } modifier onlyAdmin() { require(msg.sender == admin, "Not admin"); _; } function upgrade(address _newImplementation) external onlyAdmin { implementation = _newImplementation; } // 委托调用到实现合约 fallback() external payable { address impl = implementation; require(impl != address(0), "Implementation not set"); assembly { calldatacopy(0, 0, calldatasize()) let result := delegatecall(gas(), impl, 0, calldatasize(), 0, 0) returndatacopy(0, 0, returndatasize()) switch result case 0 { revert(0, returndatasize()) } default { return(0, returndatasize()) } } } receive() external payable {}}// 实现合约 V1contract LogicV1 { uint256 public value; function setValue(uint256 _value) external { value = _value; } function getValue() external view returns (uint256) { return value; }}// 实现合约 V2(升级版本)contract LogicV2 { uint256 public value; uint256 public bonus; // 新增状态变量 function setValue(uint256 _value) external { value = _value; bonus = _value / 10; // 新功能:自动计算 10% 奖励 } function getValue() external view returns (uint256) { return value + bonus; }}3. 透明代理模式(Transparent Proxy Pattern)OpenZeppelin 推荐的升级模式,解决了函数选择器冲突问题。// 透明代理合约contract TransparentUpgradeableProxy { // 存储槽位:避免与实现合约冲突 bytes32 private constant IMPLEMENTATION_SLOT = bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1); bytes32 private constant ADMIN_SLOT = bytes32(uint256(keccak256('eip1967.proxy.admin')) - 1); constructor(address _logic, address _admin, bytes memory _data) { _setImplementation(_logic); _setAdmin(_admin); if (_data.length > 0) { (bool success, ) = _logic.delegatecall(_data); require(success, "Initialization failed"); } } modifier ifAdmin() { if (msg.sender == _getAdmin()) { _; } else { _fallback(); } } function admin() external ifAdmin returns (address) { return _getAdmin(); } function implementation() external ifAdmin returns (address) { return _getImplementation(); } function upgradeTo(address _newImplementation) external ifAdmin { _setImplementation(_newImplementation); } function upgradeToAndCall(address _newImplementation, bytes calldata _data) external payable ifAdmin { _setImplementation(_newImplementation); (bool success, ) = _newImplementation.delegatecall(_data); require(success, "Upgrade initialization failed"); } function _getImplementation() internal view returns (address impl) { bytes32 slot = IMPLEMENTATION_SLOT; assembly { impl := sload(slot) } } function _setImplementation(address _newImplementation) internal { bytes32 slot = IMPLEMENTATION_SLOT; assembly { sstore(slot, _newImplementation) } } function _getAdmin() internal view returns (address adm) { bytes32 slot = ADMIN_SLOT; assembly { adm := sload(slot) } } function _setAdmin(address _newAdmin) internal { bytes32 slot = ADMIN_SLOT; assembly { sstore(slot, _newAdmin) } } function _fallback() internal { address impl = _getImplementation(); require(impl != address(0), "Implementation not set"); assembly { calldatacopy(0, 0, calldatasize()) let result := delegatecall(gas(), impl, 0, calldatasize(), 0, 0) returndatacopy(0, 0, returndatasize()) switch result case 0 { revert(0, returndatasize()) } default { return(0, returndatasize()) } } } fallback() external payable { _fallback(); } receive() external payable { _fallback(); }}// 实现合约(使用初始化代替构造函数)contract MyTokenV1 { string public name; string public symbol; uint256 public totalSupply; mapping(address => uint256) public balanceOf; bool private initialized; function initialize( string memory _name, string memory _symbol, uint256 _initialSupply ) public { require(!initialized, "Already initialized"); initialized = true; name = _name; symbol = _symbol; totalSupply = _initialSupply; balanceOf[msg.sender] = _initialSupply; } function transfer(address _to, uint256 _amount) external returns (bool) { require(balanceOf[msg.sender] >= _amount, "Insufficient balance"); balanceOf[msg.sender] -= _amount; balanceOf[_to] += _amount; return true; }}// 升级版本 V2contract MyTokenV2 { string public name; string public symbol; uint256 public totalSupply; mapping(address => uint256) public balanceOf; mapping(address => mapping(address => uint256)) public allowance; // 新增 bool private initialized; function initialize( string memory _name, string memory _symbol, uint256 _initialSupply ) public { require(!initialized, "Already initialized"); initialized = true; name = _name; symbol = _symbol; totalSupply = _initialSupply; balanceOf[msg.sender] = _initialSupply; } function transfer(address _to, uint256 _amount) external returns (bool) { require(balanceOf[msg.sender] >= _amount, "Insufficient balance"); balanceOf[msg.sender] -= _amount; balanceOf[_to] += _amount; return true; } // 新增功能:授权转账 function approve(address _spender, uint256 _amount) external returns (bool) { allowance[msg.sender][_spender] = _amount; return true; } function transferFrom(address _from, address _to, uint256 _amount) external returns (bool) { require(balanceOf[_from] >= _amount, "Insufficient balance"); require(allowance[_from][msg.sender] >= _amount, "Insufficient allowance"); balanceOf[_from] -= _amount; balanceOf[_to] += _amount; allowance[_from][msg.sender] -= _amount; return true; }}4. UUPS 代理模式(Universal Upgradeable Proxy Standard)更轻量级的升级模式,升级逻辑放在实现合约中。// UUPS 代理合约contract ERC1967Proxy { bytes32 private constant IMPLEMENTATION_SLOT = bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1); constructor(address _logic, bytes memory _data) { _setImplementation(_logic); if (_data.length > 0) { (bool success, ) = _logic.delegatecall(_data); require(success, "Initialization failed"); } } function _getImplementation() internal view returns (address impl) { bytes32 slot = IMPLEMENTATION_SLOT; assembly { impl := sload(slot) } } function _setImplementation(address _newImplementation) internal { bytes32 slot = IMPLEMENTATION_SLOT; assembly { sstore(slot, _newImplementation) } } fallback() external payable { address impl = _getImplementation(); require(impl != address(0), "Implementation not set"); assembly { calldatacopy(0, 0, calldatasize()) let result := delegatecall(gas(), impl, 0, calldatasize(), 0, 0) returndatacopy(0, 0, returndatasize()) switch result case 0 { revert(0, returndatasize()) } default { return(0, returndatasize()) } } } receive() external payable {}}// UUPS 实现合约基类abstract contract UUPSUpgradeable { bytes32 private constant IMPLEMENTATION_SLOT = bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1); modifier onlyProxy() { require(address(this) != __self, "Function must be called through delegatecall"); _; } address private immutable __self = address(this); function proxiableUUID() external view returns (bytes32) { return IMPLEMENTATION_SLOT; } function upgradeTo(address _newImplementation) external onlyProxy { _authorizeUpgrade(_newImplementation); _upgradeToAndCall(_newImplementation, "", false); } function upgradeToAndCall(address _newImplementation, bytes memory _data) external payable onlyProxy { _authorizeUpgrade(_newImplementation); _upgradeToAndCall(_newImplementation, _data, true); } function _authorizeUpgrade(address _newImplementation) internal virtual; function _upgradeToAndCall( address _newImplementation, bytes memory _data, bool _forceCall ) internal { _setImplementation(_newImplementation); if (_data.length > 0 || _forceCall) { (bool success, ) = _newImplementation.delegatecall(_data); require(success, "Upgrade initialization failed"); } } function _setImplementation(address _newImplementation) private { bytes32 slot = IMPLEMENTATION_SLOT; assembly { sstore(slot, _newImplementation) } }}// 使用 UUPS 的合约contract MyUUPSTokenV1 is UUPSUpgradeable { string public name; uint256 public value; address public owner; bool private initialized; function initialize(string memory _name) public { require(!initialized, "Already initialized"); initialized = true; name = _name; owner = msg.sender; } function setValue(uint256 _value) external { value = _value; } function _authorizeUpgrade(address _newImplementation) internal override { require(msg.sender == owner, "Not authorized"); }}// 升级版本contract MyUUPSTokenV2 is UUPSUpgradeable { string public name; uint256 public value; uint256 public multiplier; // 新增 address public owner; bool private initialized; function initialize(string memory _name) public { require(!initialized, "Already initialized"); initialized = true; name = _name; owner = msg.sender; multiplier = 1; // 初始化新变量 } function setValue(uint256 _value) external { value = _value * multiplier; // 新逻辑 } function setMultiplier(uint256 _multiplier) external { require(msg.sender == owner, "Not owner"); multiplier = _multiplier; } function _authorizeUpgrade(address _newImplementation) internal override { require(msg.sender == owner, "Not authorized"); }}5. 钻石模式(Diamond Pattern - EIP-2535)支持多个实现合约,适合大型复杂系统。// 钻石存储结构library LibDiamond { bytes32 constant DIAMOND_STORAGE_POSITION = keccak256("diamond.standard.diamond.storage"); struct FacetAddressAndPosition { address facetAddress; uint96 functionSelectorPosition; } struct FacetFunctionSelectors { bytes4[] functionSelectors; uint256 facetAddressPosition; } struct DiamondStorage { mapping(bytes4 => FacetAddressAndPosition) selectorToFacetAndPosition; mapping(address => FacetFunctionSelectors) facetFunctionSelectors; address[] facetAddresses; address contractOwner; } function diamondStorage() internal pure returns (DiamondStorage storage ds) { bytes32 position = DIAMOND_STORAGE_POSITION; assembly { ds.slot := position } } event DiamondCut( address[] _facetAddresses, bytes4[][] _functionSelectors, address _initAddress, bytes _calldata );}// 钻石合约contract Diamond { constructor(address _contractOwner, address _diamondCutFacet) { LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); ds.contractOwner = _contractOwner; // 添加 diamondCut 函数 // ... } fallback() external payable { LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); address facet = ds.selectorToFacetAndPosition[msg.sig].facetAddress; require(facet != address(0), "Function does not exist"); assembly { calldatacopy(0, 0, calldatasize()) let result := delegatecall(gas(), facet, 0, calldatasize(), 0, 0) returndatacopy(0, 0, returndatasize()) switch result case 0 { revert(0, returndatasize()) } default { return(0, returndatasize()) } } } receive() external payable {}}// 钻石切面(Facet)示例contract TokenFacet { struct TokenStorage { mapping(address => uint256) balances; uint256 totalSupply; } bytes32 constant TOKEN_STORAGE_POSITION = keccak256("token.facet.storage"); function tokenStorage() internal pure returns (TokenStorage storage ts) { bytes32 position = TOKEN_STORAGE_POSITION; assembly { ts.slot := position } } function balanceOf(address _account) external view returns (uint256) { return tokenStorage().balances[_account]; } function transfer(address _to, uint256 _amount) external { TokenStorage storage ts = tokenStorage(); require(ts.balances[msg.sender] >= _amount, "Insufficient balance"); ts.balances[msg.sender] -= _amount; ts.balances[_to] += _amount; }}// 治理切面contract GovernanceFacet { struct GovernanceStorage { mapping(address => uint256) votingPower; uint256 proposalCount; } bytes32 constant GOVERNANCE_STORAGE_POSITION = keccak256("governance.facet.storage"); function createProposal(string memory _description) external { // 创建提案逻辑 } function vote(uint256 _proposalId, bool _support) external { // 投票逻辑 }}6. 升级模式对比| 特性 | 透明代理 | UUPS | 钻石模式 || ------- | ------ | ------ | ------ || Gas 成本 | 较高 | 较低 | 中等 || 复杂度 | 中等 | 低 | 高 || 函数选择器冲突 | 自动解决 | 需手动处理 | 支持多切面 || 升级权限 | 代理合约控制 | 实现合约控制 | 灵活配置 || 适用场景 | 通用场景 | 简单升级 | 大型复杂系统 || 部署成本 | 中等 | 低 | 高 |7. 升级最佳实践存储布局管理// 使用存储槽位避免冲突contract StorageLayout { // 存储槽位 0 uint256 public value1; // 存储槽位 1 address public owner; // 存储槽位 2 mapping(address => uint256) public balances; // 升级时只能追加新变量,不能修改已有变量的顺序和类型 // V2 版本 uint256 public value1; // 槽位 0 - 保持不变 address public owner; // 槽位 1 - 保持不变 mapping(address => uint256) public balances; // 槽位 2 - 保持不变 uint256 public newValue; // 槽位 3 - 新增变量}使用 OpenZeppelin Upgrades// hardhat.config.jsrequire('@openzeppelin/hardhat-upgrades');module.exports = { solidity: '0.8.19',};// 部署脚本const { ethers, upgrades } = require('hardhat');async function main() { const MyToken = await ethers.getContractFactory('MyTokenV1'); // 部署代理 const proxy = await upgrades.deployProxy(MyToken, ['My Token'], { initializer: 'initialize', }); await proxy.deployed(); console.log('Proxy deployed to:', proxy.address); // 升级 const MyTokenV2 = await ethers.getContractFactory('MyTokenV2'); const upgraded = await upgrades.upgradeProxy(proxy.address, MyTokenV2); console.log('Upgraded to:', upgraded.address);}main();安全注意事项// 1. 使用初始化锁防止重复初始化contract Initializable { bool private _initialized; bool private _initializing; modifier initializer() { require( _initializing || !_initialized, "Initializable: contract is already initialized" ); bool isTopLevelCall = !_initializing; if (isTopLevelCall) { _initializing = true; _initialized = true; } _; if (isTopLevelCall) { _initializing = false; } }}// 2. 权限控制contract UpgradeableWithAuth is Initializable { address public admin; modifier onlyAdmin() { require(msg.sender == admin, "Not admin"); _; } function initialize() public initializer { admin = msg.sender; } function upgrade(address _newImplementation) external onlyAdmin { // 升级逻辑 }}// 3. 升级前测试// - 在测试网上充分测试// - 验证存储布局兼容性// - 检查新功能的安全性8. 总结合约升级是智能合约开发的重要技能:透明代理模式:OpenZeppelin 推荐,适合大多数场景UUPS 模式:更轻量,Gas 效率更高钻石模式:适合大型复杂系统,支持模块化升级最佳实践:仔细管理存储布局使用初始化锁充分测试升级过程考虑使用 OpenZeppelin Upgrades 插件
阅读 0·3月6日 21:48