Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Multicall module #2608

Merged
merged 18 commits into from
Apr 7, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
go back to existing naming across files *
  • Loading branch information
martriay committed Mar 26, 2021
commit 01c2a032c2e69c5cb429d1d713b684c46787baa9
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* `IERC20Metadata`: add a new extended interface that includes the optional `name()`, `symbol()` and `decimals()` functions. ([#2561](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2561))
* `ERC777`: make reception acquirement optional in `_mint`. ([#2552](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2552))
* `ERC20Permit`: add a `_useNonce` to enable further usage of ERC712 signatures. ([#2565](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2565))
* `BatchCall`: add abstract contract with `batchCall(bytes[] calldata data)` function to bundle multiple calls together ([#2608](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2608))
* `Multicall`: add abstract contract with `Multicall(bytes[] calldata data)` function to bundle multiple calls together ([#2608](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2608))

## 4.0.0 (2021-03-23)

Expand Down
6 changes: 3 additions & 3 deletions contracts/mocks/BatchCallTokenMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

pragma solidity ^0.8.0;

import "../utils/BatchCall.sol";
import "../utils/Multicall.sol";
import "./ERC20Mock.sol";

contract BatchCallTokenMock is ERC20Mock, BatchCall {
constructor (uint256 initialBalance) ERC20Mock("BatchCallToken", "BCT", msg.sender, initialBalance) {}
contract MulticallTokenMock is ERC20Mock, Multicall {
constructor (uint256 initialBalance) ERC20Mock("MulticallToken", "BCT", msg.sender, initialBalance) {}
}
File renamed without changes.
4 changes: 2 additions & 2 deletions contracts/utils/README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ NOTE: This document is better viewed at https://docs.openzeppelin.com/contracts/
Miscellaneous contracts and libraries containing utility functions you can use to improve security, work with new data types, or safely use low-level primitives.

The {Address}, {Arrays} and {Strings} libraries provide more operations related to these native data types, while {SafeCast} adds ways to safely convert between the different signed and unsigned numeric types.
{BatchCall} provides a function to batch together multiple calls in a single external call.
{Multicall} provides a function to batch together multiple calls in a single external call.

For new data types:

Expand Down Expand Up @@ -94,4 +94,4 @@ Note that, in all cases, accounts simply _declare_ their interfaces, but they ar

{{Strings}}

{{BatchCall}}
{{Multicall}}
12 changes: 6 additions & 6 deletions docs/modules/ROOT/pages/utilities.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,9 @@ Want to check if an address is a contract? Use xref:api:utils.adoc#Address[`Addr

Want to keep track of some numbers that increment by 1 every time you want another one? Check out xref:api:utils.adoc#Counters[`Counters`]. This is useful for lots of things, like creating incremental identifiers, as shown on the xref:erc721.adoc[ERC721 guide].

=== BatchCall
=== Multicall

The `BatchCall` abstract contract comes with a `batchCall` function that bundles together multiple calls in a single external call. With it, external accounts may perform atomic operations comprising several function calls. This is not only useful for EOAs to make multiple calls in a single transaction, it's also a way to revert a previous call if a later one fails.
The `Multicall` abstract contract comes with a `multicall` function that bundles together multiple calls in a single external call. With it, external accounts may perform atomic operations comprising several function calls. This is not only useful for EOAs to make multiple calls in a single transaction, it's also a way to revert a previous call if a later one fails.

Consider this dummy contract:

Expand All @@ -111,9 +111,9 @@ Consider this dummy contract:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/utils/BatchCall.sol";
import "@openzeppelin/contracts/utils/Multicall.sol";

contract Box is BatchCall {
contract Box is Multicall {
function foo() public {
...
}
Expand All @@ -124,15 +124,15 @@ contract Box is BatchCall {
}
----

This is how to call the `batchCall` function using Truffle, allowing `foo` and `bar` to be called in a single transaction:
This is how to call the `multicall` function using Truffle, allowing `foo` and `bar` to be called in a single transaction:
[source,javascript]
----
// scripts/foobar.js

const Box = artifacts.require('Box');
const instance = await Box.new();

await instance.batchCall([
await instance.multicall([
instance.contract.methods.foo().encodeABI(),
instance.contract.methods.bar().encodeABI()
]);
Expand Down
26 changes: 13 additions & 13 deletions test/utils/BatchCall.test.js
Original file line number Diff line number Diff line change
@@ -1,31 +1,31 @@
const { BN, expectRevert } = require('@openzeppelin/test-helpers');
const BatchCallTokenMock = artifacts.require('BatchCallTokenMock');
const MulticallTokenMock = artifacts.require('MulticallTokenMock');

contract('BatchCallToken', function (accounts) {
contract('MulticallToken', function (accounts) {
const [deployer, alice, bob] = accounts;
const amount = 12000;

beforeEach(async function () {
this.batchCallToken = await BatchCallTokenMock.new(new BN(amount), { from: deployer });
this.multicallToken = await MulticallTokenMock.new(new BN(amount), { from: deployer });
});

it('batches function calls', async function () {
expect(await this.batchCallToken.balanceOf(alice)).to.be.bignumber.equal(new BN('0'));
expect(await this.batchCallToken.balanceOf(bob)).to.be.bignumber.equal(new BN('0'));
expect(await this.multicallToken.balanceOf(alice)).to.be.bignumber.equal(new BN('0'));
expect(await this.multicallToken.balanceOf(bob)).to.be.bignumber.equal(new BN('0'));

await this.batchCallToken.batchCall([
this.batchCallToken.contract.methods.transfer(alice, amount / 2).encodeABI(),
this.batchCallToken.contract.methods.transfer(bob, amount / 3).encodeABI(),
await this.multicallToken.Multicall([
this.multicallToken.contract.methods.transfer(alice, amount / 2).encodeABI(),
this.multicallToken.contract.methods.transfer(bob, amount / 3).encodeABI(),
], { from: deployer });

expect(await this.batchCallToken.balanceOf(alice)).to.be.bignumber.equal(new BN(amount / 2));
expect(await this.batchCallToken.balanceOf(bob)).to.be.bignumber.equal(new BN(amount / 3));
expect(await this.multicallToken.balanceOf(alice)).to.be.bignumber.equal(new BN(amount / 2));
expect(await this.multicallToken.balanceOf(bob)).to.be.bignumber.equal(new BN(amount / 3));
});

it('bubbles up revert reasons', async function () {
const call = this.batchCallToken.batchCall([
this.batchCallToken.contract.methods.transfer(alice, amount).encodeABI(),
this.batchCallToken.contract.methods.transfer(bob, amount).encodeABI(),
const call = this.multicallToken.Multicall([
this.multicallToken.contract.methods.transfer(alice, amount).encodeABI(),
this.multicallToken.contract.methods.transfer(bob, amount).encodeABI(),
], { from: deployer });

await expectRevert(call, 'ERC20: transfer amount exceeds balance');
Expand Down