{"author":{"address":null,"user":"https://learnblockchain.cn/people/13255"},"content":{"body":"一、Solidity 的重入攻击代码实例\r\n（一）存在重入漏洞的 Solidity 计数器合约\r\n\r\n```js\r\n\r\npragma solidity ^0.8.0;\r\n\r\ncontract ReentrantCounter {\r\n    uint256 public balance;\r\n\r\n    constructor() {\r\n        balance = 0;\r\n    }\r\n\r\n    function deposit() public payable {\r\n        balance += msg.value;\r\n    }\r\n\r\n    function withdraw() public {\r\n        require(balance \u003e= msg.value, \"Insufficient balance\");\r\n        (bool success, ) = msg.sender.call{value: msg.value}(\"\");\r\n        require(success, \"Transfer failed\");\r\n        balance -= msg.value;\r\n    }\r\n}\r\n\r\ncontract AttackContract {\r\n    ReentrantCounter target;\r\n\r\n    constructor(ReentrantCounter _target) {\r\n        target = _target;\r\n    }\r\n\r\n    fallback() external payable {\r\n        if (address(target).balance \u003e= msg.value) {\r\n            target.withdraw();\r\n        }\r\n    }\r\n\r\n    function attack() public payable {\r\n        target.deposit{value: msg.value}();\r\n        target.withdraw();\r\n    }\r\n}\r\n```\r\n\r\n（二）重入攻击原理分析\r\n在上述 Solidity 代码中，ReentrantCounter合约实现了一个简单的存款和取款功能。withdraw函数中，在向外部地址发送以太币后，才更新balance变量。AttackContract合约利用这一点，在fallback函数中反复调用target.withdraw()，只要ReentrantCounter合约还有余额，就能持续取款，导致ReentrantCounter合约的balance出现负数，造成资金损失。\r\n二、Move 语言实现的计数器合约\r\n\r\n\r\n```js\r\nmodule counter {\r\n    struct Counter has key {\r\n        value: u64\r\n    }\r\n\r\n    public fun deposit(account: \u0026signer, amount: u64) {\r\n        let counter = borrow_global_mut\u003cCounter\u003e(account);\r\n        counter.value = counter.value + amount;\r\n    }\r\n\r\n    public fun withdraw(account: \u0026signer, amount: u64) {\r\n        let counter = borrow_global_mut\u003cCounter\u003e(account);\r\n        assert!(counter.value \u003e= amount, 0);\r\n        counter.value = counter.value - amount;\r\n    }\r\n\r\n    public entry fun init(account: \u0026signer) {\r\n        assert!(!exists\u003cCounter\u003e(account), 0);\r\n        move_to(account, Counter { value: 0 });\r\n    }\r\n}\r\n```\r\n\r\n（一）Move 避免重入攻击的原理\r\nMove 语言通过资源管理和线性逻辑来避免重入攻击。在 Move 中，资源是 “一等公民”，Counter结构体作为资源，其操作遵循严格的规则。在withdraw函数中，先检查余额足够后直接扣除余额，不存在像 Solidity 那样先进行外部操作再更新状态的情况。而且 Move 的资源不能被复制，同一时间只能被一个操作访问，这从根本上杜绝了重入攻击的可能性。\r\n三、Rust 实现的计数器合约（基于 Substrate 框架）\r\n#![cfg_attr(not(feature = \"std\"), no_std)]\r\n\r\n\r\n```js\r\nuse codec::{Decode, Encode};\r\nuse frame_support::{decl_module, decl_storage, dispatch::Result};\r\nuse sp_std::prelude::*;\r\n\r\ndecl_storage! {\r\n    trait Store for Module\u003cT: Config\u003e as CounterModule {\r\n        Balance: u64;\r\n    }\r\n}\r\n\r\ndecl_module! {\r\n    pub struct Module\u003cT: Config\u003e for enum Call where origin: T::Origin {\r\n        #[weight = 10_000]\r\n        pub fn deposit(origin, amount: u64) -\u003e Result {\r\n            let who = ensure_signed(origin)?;\r\n            let mut balance = \u003cBalance\u003cT\u003e\u003e::get();\r\n            balance += amount;\r\n            \u003cBalance\u003cT\u003e\u003e::put(balance);\r\n            Ok(())\r\n        }\r\n\r\n        #[weight = 10_000]\r\n        pub fn withdraw(origin, amount: u64) -\u003e Result {\r\n            let who = ensure_signed(origin)?;\r\n            let mut balance = \u003cBalance\u003cT\u003e\u003e::get();\r\n            if balance \u003c amount {\r\n                return Err(\"Insufficient balance\");\r\n            }\r\n            balance -= amount;\r\n            \u003cBalance\u003cT\u003e\u003e::put(balance);\r\n            Ok(())\r\n        }\r\n\r\n        #[weight = 10_000]\r\n        pub fn init(origin) -\u003e Result {\r\n            let who = ensure_signed(origin)?;\r\n            \u003cBalance\u003cT\u003e\u003e::put(0);\r\n            Ok(())\r\n        }\r\n    }\r\n}\r\n```\r\n\r\n（一）Rust 避免重入攻击的原理\r\nRust 利用所有权和借用机制以及严格的类型检查来防止重入攻击。在withdraw函数中，先获取当前余额并检查是否足够，然后直接更新余额。Rust 的所有权系统确保在同一时间只有一个所有者可以修改Balance，避免了并发修改导致的重入问题。同时，Rust 的编译时检查可以发现潜在的逻辑错误和类型不匹配问题。\r\n四、Move 语言的优势总结\r\n（一）资源管理的直接性\r\nMove 语言直接将资源作为核心概念，资源的操作规则简单且直接，开发者可以清晰地理解和控制资源的流动和状态变化，相比 Rust 的所有权机制，更专注于区块链领域的资源管理，减少了因复杂所有权转换带来的理解成本。\r\n（二）语法和逻辑的简洁性\r\nMove 的语法设计简洁明了，在处理合约逻辑时，代码结构更加紧凑，对于实现简单的计数器合约等功能，代码量相对较少，可读性更高。而 Rust 基于 Substrate 框架的实现，虽然功能强大，但框架本身的复杂性使得代码相对冗长。\r\n（三）针对区块链场景的优化\r\nMove 语言是专门为区块链智能合约设计的，在处理区块链特定的问题，如账户管理、资源转移等方面，具有天然的优势。相比之下，Rust 虽然可以用于区块链开发，但它是一种通用编程语言，在区块链场景下需要更多的适配和整合。\r\n\r\n上述内容从代码实例出发，深入分析了三种语言在应对重入攻击时的表现。如果你有调整语言风格、补充案例等需求，欢迎随时告诉我。","title":"从代码实例看 Move、Solidity 和 Rust 在应对重入攻击的差异"},"history":null,"timestamp":1739177482,"version":1}