Solidity內部合約創建全解析:解鎖Web3開發新姿勢

合約創建基礎

new 關鍵字創建合約

在 Solidity 中,new關鍵字是創建合約實例的最基本方式,它就像是一個 “魔法鑰匙”,能夠在以太坊區塊鏈上生成一個全新的合約實例。使用new關鍵字創建合約的過程非常直觀,就像我們在其他編程語言中創建對象一樣。下面通過一個簡單的示例來展示如何使用new關鍵字創建合約:

pragma solidity ^0.8.0;
// 定義一個簡單的合約
contract SimpleContract {uint256 value;// 構造函數,用于初始化valueconstructor(uint256 _value) {value = _value;}// 函數,用于獲取value的值function getValue() public view returns (uint256) {return value;}
}contract ContractCreator {function createSimpleContract() public returns (SimpleContract) {// 使用new關鍵字創建SimpleContract合約實例SimpleContract newContract = new SimpleContract(10); return newContract;}
}

在上述代碼中,首先定義了一個名為SimpleContract的合約,它包含一個狀態變量value和一個構造函數,構造函數用于初始化value的值。然后定義了一個ContractCreator合約,在ContractCreator合約中,createSimpleContract函數使用new關鍵字創建了一個SimpleContract合約的實例,并傳入初始值10 。最后返回新創建的合約實例。

當我們在以太坊區塊鏈上部署ContractCreator合約,并調用createSimpleContract函數時,就會在區塊鏈上創建一個新的SimpleContract合約實例,并且這個實例的value值會被初始化為10 。通過這種方式,我們可以動態地在區塊鏈上創建和部署新的合約,為 Web3 應用的開發提供了極大的靈活性。

構造函數的作用

構造函數是合約中的一個特殊函數,它在合約創建時被自動調用,就像是合約的 “初始化向導”,負責為合約的狀態變量設置初始值,以及執行其他必要的初始化操作。構造函數的重要性不言而喻,它確保了合約在創建后處于一個正確的初始狀態,為后續的功能實現奠定了基礎。

構造函數的名稱與合約名稱相同(在 Solidity 0.4.22 及之后版本,也可以使用constructor關鍵字定義構造函數 ),并且在合約的生命周期中只執行一次。例如,在前面的SimpleContract合約中,構造函數的定義如下:

constructor(uint256 _value) {value = _value;
}

這個構造函數接受一個uint256類型的參數_value,并將其賦值給狀態變量value 。當使用new關鍵字創建SimpleContract合約實例時,構造函數會被自動調用,傳入的參數10會被用來初始化value ,使得新創建的合約實例中的value值為10 。

構造函數還可以執行更復雜的初始化邏輯,比如設置合約的所有者、初始化多個狀態變量、調用其他合約的初始化函數等。例如,下面的合約中,構造函數不僅初始化了狀態變量,還設置了合約的所有者:

pragma solidity ^0.8.0;contract OwnedContract {address owner;uint256 initialValue;constructor(uint256 _initialValue) {owner = msg.sender;initialValue = _initialValue;}function getOwner() public view returns (address) {return owner;}function getInitialValue() public view returns (uint256) {return initialValue;}
}

在這個合約中,構造函數將msg.sender(即合約的部署者)賦值給owner變量,同時將傳入的參數_initialValue賦值給initialValue變量。這樣,在合約創建后,就可以通過getOwner和getInitialValue函數獲取合約的所有者和初始值。

多種創建方式深度剖析

工廠合約模式

代碼示例:下面通過一個具體的代碼示例來深入理解工廠合約模式:

pragma solidity ^0.8.0;// 定義一個簡單的Token合約
contract Token {address public owner;uint256 public totalSupply;constructor(uint256 _initialSupply) {owner = msg.sender;totalSupply = _initialSupply;}function transfer(address to, uint256 amount) public {require(msg.sender == owner, "Only owner can transfer");totalSupply -= amount;// 這里可以添加實際的轉賬邏輯,例如更新余額等}
}// 定義Token工廠合約
contract TokenFactory {// 用于存儲創建的Token合約地址Token[] public createdTokens; function createToken(uint256 initialSupply) public returns (Token) {// 使用new關鍵字創建Token合約實例Token newToken = new Token(initialSupply); // 將新創建的Token合約地址添加到數組中createdTokens.push(newToken); return newToken;}function getCreatedTokensCount() public view returns (uint256) {return createdTokens.length;}
}

在上述代碼中,首先定義了一個Token合約,它包含了owner和totalSupply兩個狀態變量,以及constructor和transfer兩個函數。constructor函數用于初始化合約的所有者和總供應量,transfer函數用于實現代幣的轉賬功能(這里僅為示例,實際轉賬邏輯可根據需求完善)。

接著定義了TokenFactory工廠合約,它包含一個createdTokens數組,用于存儲所有創建的Token合約地址。createToken函數是工廠合約的核心,它接受一個initialSupply參數,用于指定新創建的Token合約的初始供應量。在函數內部,使用new關鍵字創建一個新的Token合約實例,并將其添加到createdTokens數組中,最后返回新創建的合約實例。getCreatedTokensCount函數用于獲取已經創建的Token合約數量。

當我們部署TokenFactory合約后,可以通過調用createToken函數來創建多個Token合約實例,每個實例都有自己獨立的狀態和功能,并且可以通過TokenFactory合約對這些實例進行統一管理。

通過庫(Library)創建

  1. 庫的特性與創建原理:在 Solidity 中,庫是一種特殊的合約類型,它主要用于提供無狀態的功能。與普通合約不同,庫不能存儲狀態變量,也沒有自己的存儲空間,這使得庫具有更高的可復用性和效率。庫的代碼在編譯時會被嵌入到使用它的合約中,就像在其他編程語言中使用靜態函數庫一樣,從而避免了額外的合約調用開銷。

盡管庫主要用于提供無狀態的功能,但仍然可以在庫中創建和部署合約。這是因為庫可以訪問外部合約的代碼和功能,通過使用new關鍵字,庫可以像普通合約一樣創建其他合約的實例。例如,假設我們有一個用于創建簡單計數器合約的庫:

pragma solidity ^0.8.0;// 定義計數器合約
contract Counter {uint256 public count;constructor() {count = 0;}function increment() public {count++;}
}// 定義用于創建Counter合約的庫
library CounterDeployer {function deployCounter() external returns (Counter) {return new Counter();}
}

在上述代碼中,首先定義了一個Counter合約,它包含一個count狀態變量和constructor、increment兩個函數。constructor函數用于初始化count為 0,increment函數用于將count加 1。

然后定義了CounterDeployer庫,它包含一個deployCounter函數,該函數使用new關鍵字創建一個新的Counter合約實例,并返回這個實例。通過這種方式,其他合約可以使用CounterDeployer庫來創建Counter合約,而無需重復編寫創建合約的代碼。

  1. 應用場景與案例:在實際開發中,庫創建合約的應用場景非常廣泛。例如,在開發去中心化金融(DeFi)應用時,可能需要創建大量的借貸合約、流動性池合約等。通過使用庫來創建這些合約,可以將創建合約的邏輯封裝在庫中,提高代碼的復用性和可維護性。

以一個簡單的借貸應用為例,假設有一個LoanContract合約用于管理借貸業務,我們可以創建一個庫來負責創建LoanContract合約實例:

pragma solidity ^0.8.0;// 定義借貸合約
contract LoanContract {address public lender;address public borrower;uint256 public loanAmount;constructor(address _lender, address _borrower, uint256 _loanAmount) {lender = _lender;borrower = _borrower;loanAmount = _loanAmount;}// 其他借貸相關的函數,如還款、計息等
}// 定義用于創建LoanContract合約的庫
library LoanDeployer {function deployLoanContract(address _lender, address _borrower, uint256 _loanAmount) external returns (LoanContract) {return new LoanContract(_lender, _borrower, _loanAmount);}
}

在這個例子中,LoanDeployer庫的deployLoanContract函數可以根據傳入的參數創建新的LoanContract合約實例。其他合約可以通過調用這個函數來快速創建借貸合約,而無需關心合約創建的具體細節。這種方式使得代碼結構更加清晰,也方便了后續對借貸合約創建邏輯的修改和擴展。

代理(Proxy)合約創建

  1. 代理模式介紹:代理合約創建是一種在區塊鏈開發中非常重要的模式,它允許合約邏輯的升級而不改變合約地址。在傳統的智能合約開發中,一旦合約部署到區塊鏈上,其字節碼就無法修改,如果需要對合約進行升級,就必須部署一個新的合約,這會帶來一系列問題,如合約地址變更、用戶需要重新關聯新地址等。代理模式的出現解決了這些問題,它通過引入一個代理合約和一個或多個實現合約,實現了合約邏輯的動態更新。

代理合約就像是一個中間層,它主要負責存儲狀態變量,并將所有的函數調用轉發給實現合約。當外部對代理合約進行調用時,代理合約會根據預先設定的邏輯,將調用委托給相應的實現合約進行處理。實現合約則包含了具體的業務邏輯和功能代碼。當需要升級合約邏輯時,只需部署一個新的實現合約,并將代理合約的指向更新為新的實現合約地址,就可以實現合約的無縫升級,而不會影響用戶對合約的使用。

這種模式的原理基于 Solidity 中的delegatecall函數調用方式。delegatecall是一種特殊的函數調用,它允許合約調用另一個合約的代碼,但使用的是調用者的上下文(包括存儲、地址、余額等)。通過delegatecall,代理合約可以借用實現合約的功能,同時保持自己的狀態數據不變,從而實現了合約邏輯的升級和狀態的持續性。下面通過一個具體的代碼示例來展示代理合約的創建和工作原理:

pragma solidity ^0.8.0;// 定義實現合約
contract CounterImplementation {uint256 public count;constructor() {count = 0;}function increment() public {count++;}
}// 定義代理合約
contract CounterProxy {address public implementation;constructor(address _implementation) {implementation = _implementation;}fallback() external payable {address _impl = implementation;assembly {let ptr := mload(0x40)calldatacopy(ptr, 0, calldatasize())let result := delegatecall(gas(), _impl, ptr, calldatasize(), 0, 0)let size := returndatasize()returndatacopy(ptr, 0, size)switch result case 0 { revert(ptr, size) } default { return(ptr, size) }}}
}

在上述代碼中,首先定義了CounterImplementation實現合約,它包含一個count狀態變量和constructor、increment兩個函數。constructor函數用于初始化count為 0,increment函數用于將count加 1。

接著定義了CounterProxy代理合約,它包含一個implementation狀態變量,用于存儲實現合約的地址。constructor函數用于初始化implementation為傳入的實現合約地址。fallback函數是代理合約的關鍵,當代理合約接收到無法識別的函數調用時,會自動進入fallback函數。在fallback函數中,通過內聯匯編代碼實現了delegatecall操作,將調用委托給implementation指向的實現合約。具體步驟如下:

  • 首先獲取內存指針ptr,用于存儲調用數據。

  • 使用calldatacopy將調用數據從調用棧復制到內存中。

  • 使用delegatecall調用實現合約的函數,將調用數據傳遞給實現合約,并返回執行結果。

  • 根據delegatecall的返回結果,處理返回數據或進行錯誤處理。如果delegatecall執行成功(result不為 0),則將返回數據復制回調用棧并返回;如果執行失敗(result為 0),則進行回滾操作。

通過這種方式,代理合約可以將外部的調用轉發給實現合約,實現合約邏輯的執行,同時保持代理合約自身的狀態不變。當需要升級合約邏輯時,只需部署一個新的CounterImplementation實現合約,并將CounterProxy代理合約的implementation變量更新為新的實現合約地址,就可以實現合約的升級,而不會影響用戶對合約的使用。

Assembly 創建

  1. 底層原理:使用內聯匯編(Assembly)創建合約是一種深入底層的操作,它允許開發者直接與以太坊虛擬機(EVM)進行交互,實現更加精細的控制和優化。在 Solidity 中,雖然大多數開發者使用高級語言特性進行合約開發,但在某些特定場景下,內聯匯編可以提供更高的性能和更多的靈活性。

內聯匯編創建合約的底層原理基于 EVM 的操作碼。EVM 是以太坊的核心執行環境,它通過一系列的操作碼來執行合約代碼。在創建合約時,主要使用create操作碼。create操作碼用于在區塊鏈上創建一個新的合約,并返回新合約的地址。其操作需要三個參數:要發送的以太數量(以 wei 為單位)、指向合約創建字節碼(Creation ByteCode)的內存指針以及合約創建字節碼的長度。

在 Solidity 中使用內聯匯編創建合約時,需要注意以下幾點:

  • 內存管理:內聯匯編直接操作 EVM 的內存,開發者需要手動管理內存的分配和釋放。例如,在獲取合約創建字節碼的內存指針時,需要考慮動態數組和字符串的長度信息存儲位置。在 Solidity 中,動態數組和字符串的前 32 字節用于存儲長度信息,因此實際的合約創建字節碼數據從第 33 字節開始。在匯編中,可以通過add(_creationCode, 0x20)來跳過這 32 字節,獲取實際的字節碼數據。

  • 字節碼長度獲取:可以使用mload(_creationCode)來獲取合約創建字節碼的長度。這里的_creationCode是指向合約創建字節碼的內存指針,mload操作碼用于從內存中加載數據。

以下是一個使用內聯匯編創建合約的簡單示例:

pragma solidity ^0.8.0;contract AssemblyDeployer {function deploy(bytes memory _code) public returns (address addr) {assembly {addr := create(0, add(_code, 0x20), mload(_code))}}
}

在上述代碼中,AssemblyDeployer合約包含一個deploy函數,該函數接受一個bytes類型的參數_code,表示合約的創建字節碼。在函數內部,通過內聯匯編代碼使用create操作碼創建新的合約。create操作碼的第一個參數為 0,表示不發送以太幣;第二個參數add(_code, 0x20)用于獲取實際的合約創建字節碼內存指針;第三個參數mload(_code)用于獲取合約創建字節碼的長度。最后,將創建的合約地址賦值給addr并返回。

  1. 使用場景與注意事項:內聯匯編創建合約適用于一些對性能要求極高,或者需要實現特殊底層功能的場景。例如,在開發一些對 gas 消耗非常敏感的合約時,通過內聯匯編可以優化代碼,減少不必要的開銷,從而降低 gas 消耗。此外,當需要直接訪問 EVM 的某些底層功能,而這些功能在 Solidity 高級語言中沒有直接接口時,內聯匯編也可以派上用場。

然而,使用內聯匯編創建合約也存在一定的風險和挑戰,需要開發者特別注意:

  • 安全性風險:內聯匯編繞過了 Solidity 的許多安全檢查機制,這使得代碼更容易引入錯誤和漏洞。例如,在手動管理內存時,如果出現內存越界、懸空指針等問題,可能會導致合約的安全性受到威脅。因此,開發者需要對 EVM 的工作原理有深入的理解,并且在編寫內聯匯編代碼時格外小心,確保代碼的正確性和安全性。

  • 代碼可讀性和可維護性:內聯匯編代碼通常比 Solidity 高級語言代碼更難閱讀和理解。由于其語法和操作與底層的 EVM 緊密相關,對于不熟悉 EVM 的開發者來說,理解和維護內聯匯編代碼可能會非常困難。因此,在使用內聯匯編時,應盡量添加詳細的注釋,以提高代碼的可讀性和可維護性。同時,除非必要,應盡量避免在大型項目中大量使用內聯匯編,以免增加項目的維護成本。

create2 創建

  1. create2是以太坊在君士坦丁堡硬分叉中引入的一個新操作碼,它為合約創建帶來了一種全新的方式,具有一些獨特的優勢,其中最顯著的就是能夠提前確定合約地址。

在傳統的合約創建方式中,使用CREATE操作碼(在 Solidity 中對應new關鍵字)創建的合約地址是根據交易發起者(sender)的地址以及交易序號(nonce)來計算確定的。具體計算方式是將 sender 和 nonce 進行 RLP 編碼,然后用 Keccak-256 進行哈希計算(偽碼表示為keccak256(rlp([sender, nonce])))。由于交易序號nonce會隨著每次交易或合約創建而遞增,因此在創建合約之前,無法準確預知合約的最終地址。

而create2操作碼則改變了這一情況。create2主要是根據創建合約的初始化代碼(init_code)及鹽(salt)來生成合約地址。其計算方式的偽碼表示為keccak256(0xff + sender + salt + keccak256(init_code))。這里的0xff是一個常數,用于避免和CREATE操作碼沖突;sender是合約創建者的地址;salt是一個任意的 256 位值,由開發者自由選擇;init_code通常就是合約編譯生成的字節碼。通過這種方式,只要初始化代碼和鹽值確定,無論在何時何地創建合約,其地址都是固定可預測的。

這種能夠提前確定合約地址的特性在許多應用場景中都非常有用。例如,在一些需要預先規劃合約地址的項目中,如去中心化交易所(DEX)創建交易對合約時,使用create2可以提前計算出交易對合約的地址,并將其包含在事先發布的交易或文檔中,方便后續的交互和操作。此外,在一些涉及狀態通道、側鏈等復雜的區塊鏈架構中,提前確定合約地址也有助于提高系統的穩定性和可預測性,用create2創建合約:

// (一)模擬去中心化交易所創建幣對合約
//以一個簡化的去中心化交易所(DEX)為例,我們來展示如何使用工廠合約創建幣對合約。在去中心化交易所中,不同的加密貨幣對需要對應的交易合約來管理交易邏輯和流動性。//首先,定義一個`TokenPair`合約,用于管理單個幣對的交易:
pragma solidity ^0.8.0;contract TokenPair {address public tokenA;address public tokenB;constructor(address _tokenA, address _tokenB) {tokenA = _tokenA;tokenB = _tokenB;}// 模擬交易函數,實際應用中需要更復雜的邏輯function trade(uint256 amountA, uint256 amountB) public {// 這里可以添加交易邏輯,如檢查余額、更新流動性等}
}

在上述TokenPair合約中,包含了兩個狀態變量tokenA和tokenB,分別表示幣對中的兩種代幣地址。構造函數接受兩個代幣地址作為參數,并將它們賦值給對應的狀態變量。trade函數用于模擬幣對之間的交易,在實際應用中,這個函數需要包含更復雜的邏輯,如檢查用戶的余額、更新流動性池、處理交易手續費等。

接著,定義一個TokenPairFactory工廠合約,用于創建TokenPair合約實例:

pragma solidity ^0.8.0;
contract TokenPairFactory {// 用于存儲創建的TokenPair合約地址mapping(address => mapping(address => address)) public tokenPairs; function createTokenPair(address tokenA, address tokenB) public returns (address) {require(tokenA != tokenB, "Tokens cannot be the same");require(tokenPairs[tokenA][tokenB] == address(0), "Pair already exists");TokenPair newPair = new TokenPair(tokenA, tokenB);tokenPairs[tokenA][tokenB] = address(newPair);tokenPairs[tokenB][tokenA] = address(newPair);return address(newPair);}
}

在TokenPairFactory工廠合約中,使用了一個二維映射tokenPairs來存儲創建的TokenPair合約地址。createTokenPair函數是工廠合約的核心函數,它接受兩個代幣地址tokenA和tokenB作為參數。在函數內部,首先進行一些條件檢查,確保傳入的兩個代幣地址不同,并且當前幣對的合約尚未創建。然后使用new關鍵字創建一個新的TokenPair合約實例,并將其地址存儲到tokenPairs映射中,最后返回新創建的合約地址。

通過這種方式,當有新的幣對需要在去中心化交易所中進行交易時,只需要調用TokenPairFactory的createTokenPair函數,就可以快速創建對應的TokenPair合約實例,實現了幣對合約創建的自動化和規范化,提高了去中心化交易所的可擴展性和靈活性。


以知名的去中心化交易所 Uniswap V2 為例,Uniswap V2 是以太坊上最具代表性的去中心化交易所之一,其創新的自動做市商(AMM)模式和高效的合約設計,為眾多 Web3 項目提供了借鑒。

Uniswap V2 主要包含三個核心合約:UniswapV2Factory(工廠合約)、UniswapV2Pair(幣對合約)和UniswapV2Router(路由合約) ,其中與合約創建密切相關的是UniswapV2Factory和UniswapV2Pair。

UniswapV2Factory合約負責創建和管理UniswapV2Pair幣對合約。它的核心功能是通過createPair函數來創建新的幣對合約實例:

function createPair(address tokenA, address tokenB) external returns (address pair) {require(tokenA != tokenB, 'UniswapV2: IDENTICAL_ADDRESSES');(address token0, address token1) = tokenA < tokenB? (tokenA, tokenB) : (tokenB, tokenA);require(token0 != address(0), 'UniswapV2: ZERO_ADDRESS');require(getPair[token0][token1] == address(0), 'UniswapV2: PAIR_EXISTS'); // single check is sufficientbytes memory bytecode = type(UniswapV2Pair).creationCode;bytes32 salt = keccak256(abi.encodePacked(token0, token1));assembly {pair := create2(0, add(bytecode, 32), mload(bytecode), salt)}IUniswapV2Pair(pair).initialize(token0, token1);getPair[token0][token1] = pair;getPair[token1][token0] = pair; // populate mapping in the reverse directionallPairs.push(pair);emit PairCreated(token0, token1, pair, allPairs.length);
}

在上述代碼中,createPair函數首先對傳入的兩個代幣地址tokenA和tokenB進行檢查,確保它們不相同且不為零地址,同時檢查當前幣對合約是否已經存在。然后,獲取UniswapV2Pair合約的創建字節碼bytecode,并根據兩個代幣地址生成一個鹽值salt 。接下來,使用create2操作碼創建新的UniswapV2Pair合約實例,create2操作碼可以根據字節碼和鹽值確定性地生成合約地址,這在前面介紹create2時已詳細說明。創建合約后,調用合約的initialize函數進行初始化,將兩個代幣地址設置到合約中。最后,將新創建的合約地址存儲到getPair映射中,并添加到allPairs數組中,同時觸發PairCreated事件,通知外部應用新的幣對合約已創建。

UniswapV2Pair合約則負責管理單個幣對的流動性和交易邏輯。它包含了流動性的添加和移除、代幣兌換等功能:

contract UniswapV2Pair is IUniswapV2Pair, UniswapV2ERC20 {address public factory;address public token0;address public token1;// 其他狀態變量和函數定義...function initialize(address _token0, address _token1) external {require(msg.sender == factory, 'UniswapV2: FORBIDDEN'); // sufficient checktoken0 = _token0;token1 = _token1;// 其他初始化邏輯...}// 流動性添加函數function mint(address to) external returns (uint256 liquidity) {// 流動性添加邏輯...}// 代幣兌換函數function swap(uint256 amount0Out, uint256 amount1Out, address to, bytes calldata data) external {// 兌換邏輯...}
}

在UniswapV2Pair合約中,initialize函數在合約創建后被調用,用于初始化合約的狀態變量,包括設置工廠合約地址factory、兩個代幣地址token0和token1 。mint函數用于添加流動性,用戶可以將兩種代幣存入合約,合約會根據存入的數量計算并發放流動性代幣。swap函數則實現了代幣之間的兌換功能,根據用戶傳入的兌換數量和目標地址,在合約內部進行代幣的交換操作。

通過對 Uniswap V2 合約創建邏輯的分析,可以看到其設計的精妙之處。UniswapV2Factory工廠合約通過create2操作碼確保了幣對合約地址的可預測性和唯一性,同時方便了合約的管理和查詢。UniswapV2Pair合約則專注于單個幣對的流動性管理和交易處理,使得整個去中心化交易所的架構清晰、功能明確,為用戶提供了高效、可靠的交易服務。這種設計思路在許多其他 Web3 項目中也得到了廣泛應用和借鑒,成為了 Web3 開發中的經典范例。

注意事項

重入攻擊

在合約創建過程中,安全是至關重要的。以重入攻擊為例,這是一種常見且危險的攻擊方式,它利用了合約在處理外部調用時的漏洞,使得攻擊者能夠多次進入合約的關鍵函數,從而實現非法操作,如多次提取資金等。在創建合約時,為了避免重入攻擊,可采用 “檢查 - 效果 - 交互(CEI)” 模式,即先進行條件檢查,再執行狀態修改等效果操作,最后進行外部交互。例如,在一個簡單的取款合約中:

contract SafeWithdraw {mapping(address => uint) public balances;function withdraw(uint amount) public {require(balances[msg.sender] >= amount, "Insufficient balance"); // 檢查balances[msg.sender] -= amount; // 效果(bool success, ) = msg.sender.call{value: amount}(""); // 交互require(success, "Failed to send Ether");}
}

在上述代碼中,首先檢查用戶的余額是否足夠,然后更新用戶的余額,最后進行轉賬操作,這樣就避免了在轉賬過程中被重入攻擊導致余額錯誤減少的問題。

另外,還可以使用互斥鎖來防止重入攻擊。通過定義一個狀態變量來表示合約是否正在執行關鍵操作,在進入關鍵函數時檢查該變量,若合約正在執行操作則阻止再次進入,操作完成后再釋放鎖。例如:

contract ReentrancyGuard {bool internal locked;modifier noReentrant() {require(!locked, "No re-entrancy");locked = true;_;locked = false;}mapping(address => uint) public balances;function withdraw(uint amount) public noReentrant {require(balances[msg.sender] >= amount, "Insufficient balance");balances[msg.sender] -= amount;(bool success, ) = msg.sender.call{value: amount}("");require(success, "Failed to send Ether");}
}

在這個合約中,noReentrant修飾符起到了互斥鎖的作用,確保在withdraw函數執行期間不會被重入調用。

其他問題

  • Gas 不足
    當部署合約或調用創建合約的函數時,如果消耗的 Gas 超過了設置的 Gas 上限,就會導致交易失敗。例如,在部署一個復雜的合約時,由于合約代碼量大、邏輯復雜,可能會消耗較多的 Gas。解決方法是在部署或調用函數時,適當增加 Gas 的設置。在使用 Web3.js 進行合約部署時,可以通過設置gas參數來調整 Gas 的用量:
const MyContract = new web3.eth.Contract(abi, bytecode);
MyContract.deploy({ data: bytecode, arguments: [arg1, arg2] }).send({ from: account.address, gas: 5000000 }) // 增加gas值.on('error', function(error) {console.error(error);}).on('transactionHash', function(transactionHash) {console.log('Transaction Hash:', transactionHash);}).then(function(newContractInstance) {console.log('Contract deployed at:', newContractInstance.options.address);});
  • 類型不匹配
    Solidity 是一種靜態類型語言,變量和函數參數都有明確的類型。如果在創建合約時,傳遞的參數類型與函數定義的類型不匹配,就會導致編譯錯誤。例如,在調用合約的構造函數時,傳入的參數類型錯誤:
contract Example {uint256 value;constructor(uint256 _value) {value = _value;}
}contract Caller {function createExample() public {string memory wrongType = "10"; // 錯誤的類型,應為uint256Example newExample = new Example(wrongType); // 編譯錯誤}
}

解決方法是確保傳遞的參數類型與函數定義的類型一致,將上述代碼中的wrongType改為正確的uint256類型:

contract Caller {function createExample() public {uint256 correctType = 10;Example newExample = new Example(correctType);}
}
  • 地址為空
    在合約創建過程中,如果涉及到地址相關的操作,如設置合約的所有者地址、調用其他合約的地址等,若使用了空地址(address(0)),可能會導致邏輯錯誤或安全問題。例如,在一個需要設置所有者地址的合約中:
contract Owned {address owner;constructor(address _owner) {owner = _owner;}function doSomething() public {require(msg.sender == owner, "Only owner can perform this action");// 執行操作}
}contract Creator {function createOwned() public {address emptyAddress = address(0);Owned newOwned = new Owned(emptyAddress); // 錯誤,使用了空地址}
}

解決方法是在使用地址時,確保地址不為空。在上述代碼中,應傳入一個有效的地址:

contract Creator {function createOwned() public {address validAddress = msg.sender;Owned newOwned = new Owned(validAddress);}
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/910232.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/910232.shtml
英文地址,請注明出處:http://en.pswp.cn/news/910232.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

OCR大模型,破解金融文檔處理困境,從文字識別到文字理解

金融機構在日常運營中處理海量文檔。這些文檔類型多樣&#xff0c;格式復雜&#xff0c;是業務運營的基礎。如何高效、準確地處理這些文檔&#xff0c;直接影響機構的運營效率與風險控制水平。新一代的OCR大模型技術為此提供了有效的解決方案。它提升了文檔處理的自動化程度與數…

2025.6.21筆記(2)

1.編寫一個程序&#xff0c;輸入一個整數&#xff0c;判斷它是奇數還是偶數 解題思路&#xff1a; 1.因為要判斷輸入的數是奇數還是偶數&#xff0c;所以要用到if判斷 2.判讀奇偶數&#xff1a;如果這個數%20&#xff0c;則它為偶數&#xff0c;如果這個數%2!0&#xff0c;則…

【Ambari3.0.0 部署】Step7—Mariadb初始化-適用于el8

如果有其他系統部署需求可以參考原文 https://doc.janettr.com/install/manual/ MariaDB 10 是 Ambari 及大數據平臺的常見數據庫方案。本文適配 Rocky Linux 8.10&#xff0c;涵蓋 MariaDB 10.11 推薦安裝、YUM 源配置、參數優化、初始化和安全設置&#xff0c;幫助你一步到位…

SpringBoot電腦商城項目--刪除收獲地址+熱銷排行

刪除收獲地址 1 刪除收獲地址-持久層 1.1 規劃sql語句 在刪除操作之前判斷該數據是否存在&#xff0c;判斷該條地址的歸屬是否是當前的用戶執行刪除收貨地址的操作 delete from t_address where aid? 如果用戶刪除的時默認地址&#xff0c;將剩下地址的某一條作為默認收貨地…

MIMIC-III 數據集文件簡介

文件簡介&#xff1a; 共26個文件 admissions.csv 患者入院信息&#xff08;入院時間、出院時間、入院類型、科室等&#xff09;。 callout.csv ICU 外科室請求 ICU 會診的呼叫記錄。 caregivers.csv 護理患者的醫護人員信息&#xff08;身份、角色等&#xff09;。…

UL/CE雙認證!光寶MOC3052-A雙向可控硅輸出光耦 智能家居/工業控制必備!

光寶MOC3052-A雙向可控硅輸出光耦詳解 1. 產品定位 MOC3052-A 是光寶科技&#xff08;Lite-On&#xff09;推出的 雙向可控硅驅動光耦&#xff0c;屬于光電隔離型半導體器件&#xff0c;主要用于交流負載的隔離控制&#xff0c;實現低壓控制電路&#xff08;如MCU&#xff09;…

讓沒有小窗播放的視頻網站的視頻小窗播放

讓沒有小窗播放的視頻網站的視頻小窗播放 // 視頻小窗播放控制臺腳本 // 將此代碼復制到瀏覽器控制臺運行 // 運行后&#xff0c;頁面中的視頻將添加小窗播放功能(function() {// 獲取頁面中的所有video元素const videos document.querySelectorAll(video);if (videos.length…

Linux內核在啟動過程中掛載根文件系統rootfs的過程

一、掛載根文件系統rootfs的過程&#xff1a; 1. ?初始虛擬根文件系統的掛載? 內核啟動時首先會創建并掛載一個?臨時虛擬根文件系統&#xff08;如initramfs或rootfs&#xff09;??15。該階段主要作用&#xff1a; 提供基礎的設備節點和目錄結構&#xff0c;確保內核能訪…

【LeetCode】力扣題——輪轉數組、消失的數字、數組串聯

&#x1f525;個人主頁&#xff1a;艾莉絲努力練劍 ?專欄傳送門&#xff1a;《C語言》、《數據結構與算法》、C語言刷題12天IO強訓 &#x1f349;學習方向&#xff1a;C/C方向 ??人生格言&#xff1a;為天地立心&#xff0c;為生民立命&#xff0c;為往圣繼絕學&#xff0c;…

Java Stream詳解

Java Stream詳解 Stream 是 Java 8 引入的流式數據處理工具&#xff0c;可以像流水線一樣對集合數據進行高效操作&#xff08;過濾、轉換、統計等&#xff09;。核心特點&#xff1a; 鏈式操作&#xff1a;支持多個操作串聯不修改原始數據&#xff1a;生成新結果支持并行處理…

Java回歸循環理解

一、Java循環的四種 1. 傳統for循環 - 精確控制的首選 // 遍歷數組 int[] numbers {1, 2, 3, 4, 5}; for (int i 0; i < numbers.length; i) {System.out.println(numbers[i]); }// 嵌套示例&#xff1a;矩陣遍歷 int[][] matrix {{1, 2}, {3, 4}}; for (int row 0; r…

飛騰D2000金融工控主板,點亮經濟高質量發展

近年來&#xff0c;國家不斷推出金融行業的政策和法規&#xff0c;推動金融業高質量發展。在國家大力推進金融行業改革和創新的大環境下&#xff0c;金融工控主板市場也迎來了新的發展機遇。隨著國產CPU技術的不斷突破&#xff0c;以及我國對金融安全重視程度的提高&#xff0c…

SimpleITK——創建nrrd體素模型

在介紹如何生成nrrd前&#xff0c;了解一下為什么醫學影像上一般使用nrrd的體素模型&#xff1f; 為什么醫學影像上一般使用nrrd的體素模型&#xff1f; 在醫學影像領域&#xff0c;?NRRD&#xff08;Nearly Raw Raster Data&#xff09;格式?被廣泛用于存儲體素模型&#x…

Docker容器部署KES

一、安裝部署 1&#xff0c;導入鏡像 #導入鏡像&#xff08;root用戶&#xff09; [rootnode docker ]# mv kdb_x86_64_V008R006C009B0014.tar kingbase.tar [rootnode docker]# docker load -i kingbase.tar#查看鏡像&#xff08;root用戶&#xff09; [rootnode docker]# d…

C++基礎練習 sort函數,用于排序函數

題目&#xff1a; https://acm.hdu.edu.cn/showproblem.php?pid2039 解答&#xff1a; #include <iostream> #include <cmath> #include <algorithm> using namespace std;double a[3]; int main(){int n;cin>>n;while(n--){cin>>a[0]>>…

棱鏡觀察|EMB“重構”卡鉗,車企降本壓力與Brembo困局

傳統制動卡鉗市場&#xff0c;正在迎來變革時刻。 一直以來&#xff0c;采埃孚、大陸集團、日立安斯泰莫等外資供應商占據中國乘用車卡鉗前裝市場&#xff08;包括前制動卡鉗和后集成EPB卡鉗&#xff09;的半壁江山。同時&#xff0c;伯特利、亞太股份、萬向、弗迪等中國供應商…

《顛覆傳統:CSS遮罩的圖像創意設計指南》

想象有一塊神奇的模板&#xff0c;上面有各種形狀的鏤空區域&#xff0c;當我們將這塊模板覆蓋在圖像上時&#xff0c;只有透過鏤空區域才能看到圖像的部分&#xff0c;而模板遮擋的地方則被隱藏起來&#xff0c;這便是CSS遮罩的核心概念。遮罩&#xff0c;簡單來說&#xff0c…

5.基于神經網絡的時間序列預測

近年來&#xff0c;已經開發了一些深度學習方法并將其應用于單變量時間預測場景&#xff0c;其中時間序列由在等時間增量上按順序記錄的單個觀測數據組成。 5.1 將深度學習用于時間序列預測的原因 機器學習的目標是提取特征來訓練模型。模型將輸入數據&#xff08;例如圖片&am…

【軟考高級系統架構論文】論軟件設計方法及其應用

論文真題 軟件設計 (Software Design,SD) 根據軟件需求規格說明書設計軟件系統的整體結構、劃分功能模塊、確定每個模塊的實現算法以及程序流程等,形成軟件的具體設計方案。軟件設計把許多事物和問題按不同的層次和角度進行抽象,將問題或事物進行模塊化分解,以便更容易解決…

什么是水平擴展

什么是水平擴展 在現代系統架構設計中&#xff0c;可擴展性&#xff08;Scalability&#xff09;是衡量系統面對業務增長時應對能力的重要指標。而“水平擴展”&#xff08;Horizontal Scaling&#xff09;&#xff0c;又稱為“橫向擴展”或“擴容節點”&#xff0c;正是應對高…