-
Notifications
You must be signed in to change notification settings - Fork 30
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Refs #454 -- Added multicall-in-router.md to general/build/smart-cont…
…racts/gas-optimization (#619) * added multicall-in-router to gas-optimization * updated multicall-in-router.md * update multicall-in-router.md * update title * update demo code
- Loading branch information
Showing
1 changed file
with
111 additions
and
0 deletions.
There are no files selected for viewing
111 changes: 111 additions & 0 deletions
111
docs/general/build/smart-contracts/gas-optimization/multicall-in-router.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
|
||
--- | ||
displayed_sidebar: generalSidebar | ||
--- | ||
|
||
# Implement multicall in router-like contracts | ||
|
||
In Solidity, implementing multicall functionality in router-like contracts can significantly reduce gas costs by batching multiple state-modifying calls into a single transaction. This technique is invaluable in contracts similar to those used by platforms like Uniswap and Compound. | ||
|
||
**Demo Code** | ||
|
||
Below, we have a sample contract `MulticallRouter` that demonstrates how to implement multicall functionality with state-modifying operations. This contract includes a `multicall` function that executes an array of encoded function calls, modifying contract state in an efficient manner. | ||
|
||
```solidity | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.24; | ||
contract MulticallRouter { | ||
uint256 public counter; // State variable to demonstrate state changes | ||
mapping(uint256 => uint256) public data; // Mapping to store arbitrary data | ||
function multicall(bytes[] calldata data) external returns (bytes[] memory results) { | ||
results = new bytes[](data.length); | ||
for (uint256 i = 0; i < data.length; i++) { | ||
(bool success, bytes memory result) = address(this).delegatecall(data[i]); | ||
require(success, "Multicall execution failed"); | ||
results[i] = result; | ||
} | ||
} | ||
function incrementCounter(uint256 amount) external { | ||
counter += amount; | ||
} | ||
function updateData(uint256 key, uint256 value) external { | ||
data[key] = value; | ||
} | ||
} | ||
``` | ||
|
||
To verify the functionality and efficiency of the `MulticallRouter`, here is the corresponding testing script: | ||
|
||
```solidity | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.24; | ||
import "forge-std/Test.sol"; | ||
import "./MulticallRouter.sol"; | ||
contract MulticallTest is Test { | ||
MulticallRouter public router; | ||
bytes[] callData; | ||
function setUp() public { | ||
router = new MulticallRouter(); | ||
callData = new bytes[](4); | ||
callData[0] = abi.encodeWithSelector( | ||
router.incrementCounter.selector, | ||
1 | ||
); | ||
callData[1] = abi.encodeWithSelector( | ||
router.updateData.selector, | ||
0, | ||
100 | ||
); | ||
callData[2] = abi.encodeWithSelector( | ||
router.incrementCounter.selector, | ||
2 | ||
); | ||
callData[3] = abi.encodeWithSelector( | ||
router.updateData.selector, | ||
1, | ||
200 | ||
); | ||
} | ||
function testIndividualCalls() public { | ||
uint256 gasStart = gasleft(); | ||
router.incrementCounter(1); | ||
router.updateData(0, 100); | ||
router.incrementCounter(2); | ||
router.updateData(1, 200); | ||
uint256 gasEnd = gasleft(); | ||
uint256 gasUsed = gasStart - gasEnd; | ||
emit log_named_uint("Gas used for individual calls", gasUsed); | ||
} | ||
function testMulticall() public { | ||
uint256 gasStart = gasleft(); | ||
router.multicall(callData); | ||
uint256 gasEnd = gasleft(); | ||
uint256 gasUsed = gasStart - gasEnd; | ||
emit log_named_uint("Gas used for multicall", gasUsed); | ||
} | ||
} | ||
``` | ||
|
||
By running the tests in the Foundry project, we found that `testIndividualCalls()` consumes `166259` gas, while `testMulticall()` consumes `139753` gas, indicating that using Multicall can save gas to some extent. | ||
|
||
**Gas Optimization Analysis** | ||
|
||
The primary advantage of using multicall is the reduction in gas costs by avoiding multiple transaction overheads. Here’s a comparison of gas usage between individual transactions and a single multicall: | ||
|
||
- **Individual Transactions**: Each call incurs base transaction costs plus the gas for executing the function. | ||
- **Single Multicall**: Incurs only one base transaction cost plus the gas for executing each function within a loop. | ||
|
||
By batching calls, multicall can significantly reduce the cumulative gas cost, especially when performing multiple operations. | ||
|
||
**Recommendations for Gas Optimization** | ||
|
||
Implementing multicall in router-like contracts can save gas by reducing the number of transactions and leveraging the lower cumulative gas cost of executing multiple operations in a single transaction. |