Library is a special code reuse mechanism in Solidity that has essential differences from Contract. Correct use of Library can save Gas and improve code reusability.
1. Core Differences Between Library and Contract
| Feature | Library | Contract |
|---|---|---|
| Deployment | Can be deployed independently or inlined | Must be deployed independently |
| State Variables | Cannot have state variables | Can have state variables |
| Inheritance | Cannot be inherited | Can be inherited |
| ETH Reception | Cannot receive ETH | Can receive ETH |
| Self-Destruction | Cannot self-destruct | Can self-destruct |
| Call Method | DELEGATECALL or inline | CALL |
| Use Cases | Utility functions, code reuse | Business logic, state management |
2. Basic Usage of Library
Standalone Function Library
solidity// File: MathLibrary.sol // Pure function library, all functions are pure library MathLibrary { // Calculate greatest common divisor function gcd(uint256 a, uint256 b) internal pure returns (uint256) { while (b != 0) { uint256 temp = b; b = a % b; a = temp; } return a; } // Calculate square root 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; } // Calculate power 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; } } // Using the library contract Calculator { // Use using for syntax 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 using for syntax uint256 powResult = a.pow(b); // Using using for syntax return (gcdResult, sqrtResult, powResult); } }
Struct Library
solidity// File: ArrayLibrary.sol library ArrayLibrary { // Find element index 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; } // Remove element (maintain order) 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(); } // Remove element (don't maintain order, more efficient) 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(); } // Insertion sort 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; } } // Check if contains 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; } } // Using struct library 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); // Call library function } function findData(uint256 value) external view returns (int256) { return data.find(value); // Call library function } function sortData() external { data.sort(); // Call library function } }
3. Advanced Usage of Library
Library Using Storage
solidity// File: MappingLibrary.sol library MappingLibrary { // Add iteration capability to 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) { // New key 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]; // Replace element to be deleted with last element 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]; } } // Using iterable mapping 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. Common Standard Libraries
SafeMath (Solidity < 0.8.0)
solidity// File: SafeMath.sol library 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; } } // Using SafeMath contract SafeCalculator { using SafeMath for uint256; function safeAdd(uint256 a, uint256 b) external pure returns (uint256) { return a.add(b); // Use library function } }
Address Library
solidity// OpenZeppelin's Address library 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; } } // Using Address library 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 Deployment Methods
Inline Library (Internal Functions)
solidity// All functions are internal, code will be inlined into calling contract library InlineLibrary { function internalFunction(uint256 x) internal pure returns (uint256) { return x * 2; } } // No need to deploy library when using, code is inlined directly contract UsingInlineLibrary { using InlineLibrary for uint256; function double(uint256 x) external pure returns (uint256) { return x.internalFunction(); } }
External Library (External Functions)
solidity// Contains external functions, needs to be deployed separately library ExternalLibrary { function externalFunction(uint256 x) external pure returns (uint256) { return x * 2; } } // Need to link deployed library address when using contract UsingExternalLibrary { function double(uint256 x) external pure returns (uint256) { return ExternalLibrary.externalFunction(x); } }
6. Best Practices for Library
Naming Conventions
solidity// Library names use PascalCase, optionally ending with Library (recommended) library StringLibrary { // Function names use camelCase function toUpperCase(string memory str) internal pure returns (string memory) { // Implementation... } }
Function Visibility
soliditylibrary VisibilityExample { // internal: code inlined, no library deployment needed (recommended) function internalFunc() internal pure returns (uint256) { return 1; } // external: library needs to be deployed, called via DELEGATECALL function externalFunc() external pure returns (uint256) { return 2; } // public: can be called internally or externally function publicFunc() public pure returns (uint256) { return 3; } // private: can only be used inside library function privateFunc() private pure returns (uint256) { return 4; } }
Using Using For
soliditylibrary 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 { // Method 1: Use using for for specific type using EnhancedMath for uint256; function calculate(uint256 x) external pure returns (uint256, uint256) { return (x.square(), x.cube()); } // Method 2: Global use (all uint256) using EnhancedMath for *; }
7. Practical Application Cases of Library
String Processing Library
soliditylibrary 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; } } // Using string library 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. Gas Optimization with Library
solidity// Before optimization: multiple code repetitions contract BeforeOptimization { function operation1(uint256 x) external pure returns (uint256) { // Complex calculation... return x * 2 + 1; } function operation2(uint256 x) external pure returns (uint256) { // Same complex calculation... return x * 2 + 1; } } // After optimization: use library to reuse code 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. Summary
Library is a powerful code reuse tool in Solidity:
-
Use Cases: Utility functions, data structure extensions, code reuse
-
Deployment Methods: internal functions are inlined, external functions need deployment
-
Best Practices:
- Prioritize internal functions
- Use using for syntax to improve readability
- Follow naming conventions
- Utilize standard libraries like OpenZeppelin
-
Gas Optimization: Reduce code duplication, improve deployment efficiency