Skip to content

Commit

Permalink
update demo code
Browse files Browse the repository at this point in the history
  • Loading branch information
jackleeio committed Jun 27, 2024
1 parent b58fd03 commit 65c0b76
Showing 1 changed file with 62 additions and 35 deletions.
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@

---
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 calls into a single transaction. This is a common feature in contracts like the Uniswap Router and the Compound Bulker. Multicall allows users to execute a sequence of calls within a single transaction, thereby optimizing gas usage and improving efficiency.
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**
**Demo code**

Below, we have a sample contract `MulticallRouter` that demonstrates how to implement multicall functionality. This contract includes a `multicall` function that accepts an array of encoded function calls and executes them in sequence.
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++) {
Expand All @@ -24,56 +28,79 @@ contract MulticallRouter {
}
}
// Example function to demonstrate usage
function exampleFunction1(uint256 x) external pure returns (uint256) {
return x * 2;
function incrementCounter(uint256 amount) external {
counter += amount; // Increment the counter by a specified amount
}
function exampleFunction2(uint256 y) external pure returns (uint256) {
return y + 3;
function updateData(uint256 key, uint256 value) external {
data[key] = value; // Update the mapping with a key-value pair
}
}
```

In this example, the `multicall` function uses `delegatecall` to execute each function call in the context of the current contract. This ensures that the state changes and storage modifications occur within the same contract.
To verify the functionality and efficiency of the `MulticallRouter`, here is the corresponding testing script:

### Usage Example
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
To use this `MulticallRouter` contract, you can encode the function calls and pass them to the `multicall` function. Here's an example of how to encode and call the functions using Solidity:
import "forge-std/Test.sol";
import "./MulticallRouter.sol";
```solidity
contract MulticallExample {
contract MulticallTest is Test {
MulticallRouter public router;
constructor(address _router) {
router = MulticallRouter(_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 performMulticall() external {
bytes[] memory callData = new bytes[](2);
callData[0] = abi.encodeWithSelector(router.exampleFunction1.selector, 42);
callData[1] = abi.encodeWithSelector(router.exampleFunction2.selector, 21);
bytes[] memory results = router.multicall(callData);
// Handle the results
uint256 result1 = abi.decode(results[0], (uint256));
uint256 result2 = abi.decode(results[1], (uint256));
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);
}
// Do something with the results
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);
}
}
```

**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:
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.

- **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.
**Gas Optimization Analysis**

By batching calls, multicall can significantly reduce the cumulative gas cost, especially when performing multiple operations.
The primary advantage of using multicall in this context is the reduction in gas costs by consolidating the execution of multiple state-modifying operations into a single transaction. This approach minimizes the overhead associated with transaction execution, making it highly efficient for scenarios where multiple operations need to be performed simultaneously.

**Recommendations for Gas Optimization**
**Conclusion**

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.
Implementing multicall functionality in contracts that manage multiple state changes is a robust strategy for optimizing gas usage and enhancing transaction efficiency on the Ethereum network.

0 comments on commit 65c0b76

Please sign in to comment.