Commit 49ae9a11 by github-actions

Transpile 596c94a6

parent 4fcbae5d
...@@ -20,8 +20,13 @@ ...@@ -20,8 +20,13 @@
## Unreleased ## Unreleased
* Add beacon proxy. ([#2411](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2411))
* `Address`: added `functionDelegateCall`, similar to the existing `functionCall`. ([#2333](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2333))
## 3.3.0 (2020-11-26)
* Now supports both Solidity 0.6 and 0.7. Compiling with solc 0.7 will result in warnings. Install the `solc-0.7` tag to compile without warnings. * Now supports both Solidity 0.6 and 0.7. Compiling with solc 0.7 will result in warnings. Install the `solc-0.7` tag to compile without warnings.
* `Address`: added `functionStaticCall` and `functionDelegateCall`, similar to the existing `functionCall`. ([#2333](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2333)) * `Address`: added `functionStaticCall`, similar to the existing `functionCall`. ([#2333](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2333))
* `TimelockController`: added a contract to augment access control schemes with a delay. ([#2354](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2354)) * `TimelockController`: added a contract to augment access control schemes with a delay. ([#2354](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2354))
* `EnumerableSet`: added `Bytes32Set`, for sets of `bytes32`. ([#2395](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2395)) * `EnumerableSet`: added `Bytes32Set`, for sets of `bytes32`. ([#2395](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2395))
......
...@@ -19,6 +19,7 @@ This directory provides ways to restrict who can access the functions of a contr ...@@ -19,6 +19,7 @@ This directory provides ways to restrict who can access the functions of a contr
{{TimelockController}} {{TimelockController}}
[[timelock-terminology]]
==== Terminology ==== Terminology
* *Operation:* A transaction (or a set of transactions) that is the subject of the timelock. It has to be scheduled by a proposer and executed by an executor. The timelock enforces a minimum delay between the proposition and the execution (see xref:access-control.adoc#operation_lifecycle[operation lifecycle]). If the operation contains multiple transactions (batch mode), they are executed atomically. Operations are identified by the hash of their content. * *Operation:* A transaction (or a set of transactions) that is the subject of the timelock. It has to be scheduled by a proposer and executed by an executor. The timelock enforces a minimum delay between the proposition and the execution (see xref:access-control.adoc#operation_lifecycle[operation lifecycle]). If the operation contains multiple transactions (batch mode), they are executed atomically. Operations are identified by the hash of their content.
...@@ -32,6 +33,7 @@ This directory provides ways to restrict who can access the functions of a contr ...@@ -32,6 +33,7 @@ This directory provides ways to restrict who can access the functions of a contr
** *Proposer:* An address (smart contract or EOA) that is in charge of scheduling (and cancelling) operations. ** *Proposer:* An address (smart contract or EOA) that is in charge of scheduling (and cancelling) operations.
** *Executor:* An address (smart contract or EOA) that is in charge of executing operations. ** *Executor:* An address (smart contract or EOA) that is in charge of executing operations.
[[timelock-operation]]
==== Operation structure ==== Operation structure
Operation executed by the xref:api:access.adoc#TimelockController[`TimelockControler`] can contain one or multiple subsequent calls. Depending on whether you need to multiple calls to be executed atomically, you can either use simple or batched operations. Operation executed by the xref:api:access.adoc#TimelockController[`TimelockControler`] can contain one or multiple subsequent calls. Depending on whether you need to multiple calls to be executed atomically, you can either use simple or batched operations.
...@@ -51,6 +53,7 @@ const data = timelock.contract.methods.grantRole(ROLE, ACCOUNT).encodeABI() ...@@ -51,6 +53,7 @@ const data = timelock.contract.methods.grantRole(ROLE, ACCOUNT).encodeABI()
In the case of batched operations, `target`, `value` and `data` are specified as arrays, which must be of the same length. In the case of batched operations, `target`, `value` and `data` are specified as arrays, which must be of the same length.
[[timelock-operation-lifecycle]]
==== Operation lifecycle ==== Operation lifecycle
Timelocked operations are identified by a unique id (their hash) and follow a specific lifecycle: Timelocked operations are identified by a unique id (their hash) and follow a specific lifecycle:
...@@ -68,14 +71,17 @@ Operations status can be queried using the functions: ...@@ -68,14 +71,17 @@ Operations status can be queried using the functions:
* xref:api:access.adoc#TimelockController-isOperationReady-bytes32-[`isOperationReady(bytes32)`] * xref:api:access.adoc#TimelockController-isOperationReady-bytes32-[`isOperationReady(bytes32)`]
* xref:api:access.adoc#TimelockController-isOperationDone-bytes32-[`isOperationDone(bytes32)`] * xref:api:access.adoc#TimelockController-isOperationDone-bytes32-[`isOperationDone(bytes32)`]
[[timelock-roles]]
==== Roles ==== Roles
[[timelock-admin]]
===== Admin ===== Admin
The admins are in charge of managing proposers and executors. For the timelock to be self-governed, this role should only be given to the timelock itself. Upon deployment, both the timelock and the deployer have this role. After further configuration and testing, the deployer can renounce this role such that all further maintenance operations have to go through the timelock process. The admins are in charge of managing proposers and executors. For the timelock to be self-governed, this role should only be given to the timelock itself. Upon deployment, both the timelock and the deployer have this role. After further configuration and testing, the deployer can renounce this role such that all further maintenance operations have to go through the timelock process.
This role is identified by the *TIMELOCK_ADMIN_ROLE* value: `0x5f58e3a2316349923ce3780f8d587db2d72378aed66a8261c916544fa6846ca5` This role is identified by the *TIMELOCK_ADMIN_ROLE* value: `0x5f58e3a2316349923ce3780f8d587db2d72378aed66a8261c916544fa6846ca5`
[[timelock-proposer]]
===== Proposer ===== Proposer
The proposers are in charge of scheduling (and cancelling) operations. This is a critical role, that should be given to governing entities. This could be an EOA, a multisig, or a DAO. The proposers are in charge of scheduling (and cancelling) operations. This is a critical role, that should be given to governing entities. This could be an EOA, a multisig, or a DAO.
...@@ -84,6 +90,7 @@ WARNING: *Proposer fight:* Having multiple proposers, while providing redundancy ...@@ -84,6 +90,7 @@ WARNING: *Proposer fight:* Having multiple proposers, while providing redundancy
This role is identified by the *PROPOSER_ROLE* value: `0xb09aa5aeb3702cfd50b6b62bc4532604938f21248a27a1d5ca736082b6819cc1` This role is identified by the *PROPOSER_ROLE* value: `0xb09aa5aeb3702cfd50b6b62bc4532604938f21248a27a1d5ca736082b6819cc1`
[[timelock-executor]]
===== Executor ===== Executor
The executors are in charge of executing the operations scheduled by the proposers once the timelock expires. Logic dictates that multisig or DAO that are proposers should also be executors in order to guarantee operations that have been scheduled will eventually be executed. However, having additional executor can reduce the cost (the executing transaction does not require validation by the multisig or DAO that proposed it), while ensuring whoever is in charge of execution cannot trigger actions that have not been scheduled by the proposers. The executors are in charge of executing the operations scheduled by the proposers once the timelock expires. Logic dictates that multisig or DAO that are proposers should also be executors in order to guarantee operations that have been scheduled will eventually be executed. However, having additional executor can reduce the cost (the executing transaction does not require validation by the multisig or DAO that proposed it), while ensuring whoever is in charge of execution cannot trigger actions that have not been scheduled by the proposers.
......
...@@ -93,7 +93,7 @@ contract TimelockControllerUpgradeable is Initializable, AccessControlUpgradeabl ...@@ -93,7 +93,7 @@ contract TimelockControllerUpgradeable is Initializable, AccessControlUpgradeabl
_; _;
} }
/* /**
* @dev Contract might receive/hold ETH as part of the maintenance process. * @dev Contract might receive/hold ETH as part of the maintenance process.
*/ */
receive() external payable {} receive() external payable {}
......
// SPDX-License-Identifier: MIT
pragma solidity >=0.6.0 <0.8.0;
import "../proxy/Initializable.sol";
contract BadBeaconNoImplUpgradeable is Initializable {
function __BadBeaconNoImpl_init() internal initializer {
__BadBeaconNoImpl_init_unchained();
}
function __BadBeaconNoImpl_init_unchained() internal initializer {
}
uint256[50] private __gap;
}
contract BadBeaconNotContractUpgradeable is Initializable {
function __BadBeaconNotContract_init() internal initializer {
__BadBeaconNotContract_init_unchained();
}
function __BadBeaconNotContract_init_unchained() internal initializer {
}
function implementation() external view returns (address) {
return address(0x1);
}
uint256[50] private __gap;
}
...@@ -33,11 +33,11 @@ contract DummyImplementationUpgradeable is Initializable { ...@@ -33,11 +33,11 @@ contract DummyImplementationUpgradeable is Initializable {
value = 100; value = 100;
} }
function initializeNonPayable(uint256 _value) public { function initializeNonPayableWithValue(uint256 _value) public {
value = _value; value = _value;
} }
function initializePayable(uint256 _value) public payable { function initializePayableWithValue(uint256 _value) public payable {
value = _value; value = _value;
} }
...@@ -56,7 +56,7 @@ contract DummyImplementationUpgradeable is Initializable { ...@@ -56,7 +56,7 @@ contract DummyImplementationUpgradeable is Initializable {
} }
function reverts() public pure { function reverts() public pure {
require(false); require(false, "DummyImplementation reverted");
} }
uint256[47] private __gap; uint256[47] private __gap;
} }
......
...@@ -64,6 +64,20 @@ contract ArraysImplUpgradeableWithInit is ArraysImplUpgradeable { ...@@ -64,6 +64,20 @@ contract ArraysImplUpgradeableWithInit is ArraysImplUpgradeable {
__ArraysImpl_init(array); __ArraysImpl_init(array);
} }
} }
import "./BadBeaconUpgradeable.sol";
contract BadBeaconNoImplUpgradeableWithInit is BadBeaconNoImplUpgradeable {
constructor() public payable {
__BadBeaconNoImpl_init();
}
}
import "./BadBeaconUpgradeable.sol";
contract BadBeaconNotContractUpgradeableWithInit is BadBeaconNotContractUpgradeable {
constructor() public payable {
__BadBeaconNotContract_init();
}
}
import "./CallReceiverMockUpgradeable.sol"; import "./CallReceiverMockUpgradeable.sol";
contract CallReceiverMockUpgradeableWithInit is CallReceiverMockUpgradeable { contract CallReceiverMockUpgradeableWithInit is CallReceiverMockUpgradeable {
......
{ {
"name": "@openzeppelin/contracts-upgradeable", "name": "@openzeppelin/contracts-upgradeable",
"description": "Secure Smart Contract library for Solidity", "description": "Secure Smart Contract library for Solidity",
"version": "3.2.0", "version": "3.3.0",
"files": [ "files": [
"**/*.sol", "**/*.sol",
"/build/contracts/*.json", "/build/contracts/*.json",
......
...@@ -9,6 +9,8 @@ The abstract {Proxy} contract implements the core delegation functionality. If t ...@@ -9,6 +9,8 @@ The abstract {Proxy} contract implements the core delegation functionality. If t
Upgradeability is implemented in the {UpgradeableProxy} contract, although it provides only an internal upgrade interface. For an upgrade interface exposed externally to an admin, we provide {TransparentUpgradeableProxy}. Both of these contracts use the storage slots specified in https://eips.ethereum.org/EIPS/eip-1967[EIP1967] to avoid clashes with the storage of the implementation contract behind the proxy. Upgradeability is implemented in the {UpgradeableProxy} contract, although it provides only an internal upgrade interface. For an upgrade interface exposed externally to an admin, we provide {TransparentUpgradeableProxy}. Both of these contracts use the storage slots specified in https://eips.ethereum.org/EIPS/eip-1967[EIP1967] to avoid clashes with the storage of the implementation contract behind the proxy.
An alternative upgradeability mechanism is provided in <<UpgradeableBeacon>>. This pattern, popularized by Dharma, allows multiple proxies to be upgraded to a different implementation in a single transaction. In this pattern, the proxy contract doesn't hold the implementation address in storage like {UpgradeableProxy}, but the address of a {UpgradeableBeacon} contract, which is where the implementation address is actually stored and retrieved from. The `upgrade` operations that change the implementation contract address are then sent to the beacon instead of to the proxy contract, and all proxies that follow that beacon are automatically upgraded.
CAUTION: Using upgradeable proxies correctly and securely is a difficult task that requires deep knowledge of the proxy pattern, Solidity, and the EVM. Unless you want a lot of low level control, we recommend using the xref:upgrades-plugins::index.adoc[OpenZeppelin Upgrades Plugins] for Truffle and Buidler. CAUTION: Using upgradeable proxies correctly and securely is a difficult task that requires deep knowledge of the proxy pattern, Solidity, and the EVM. Unless you want a lot of low level control, we recommend using the xref:upgrades-plugins::index.adoc[OpenZeppelin Upgrades Plugins] for Truffle and Buidler.
== Core == Core
...@@ -19,6 +21,14 @@ CAUTION: Using upgradeable proxies correctly and securely is a difficult task th ...@@ -19,6 +21,14 @@ CAUTION: Using upgradeable proxies correctly and securely is a difficult task th
{{TransparentUpgradeableProxy}} {{TransparentUpgradeableProxy}}
== UpgradeableBeacon
{{BeaconProxy}}
{{IBeacon}}
{{UpgradeableBeacon}}
== Utilities == Utilities
{{Initializable}} {{Initializable}}
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
"private": true, "private": true,
"name": "openzeppelin-solidity", "name": "openzeppelin-solidity",
"description": "Secure Smart Contract library for Solidity", "description": "Secure Smart Contract library for Solidity",
"version": "3.2.0", "version": "3.3.0",
"files": [ "files": [
"/contracts/**/*.sol", "/contracts/**/*.sol",
"/build/contracts/*.json", "/build/contracts/*.json",
......
...@@ -16,8 +16,10 @@ current_version() { ...@@ -16,8 +16,10 @@ current_version() {
} }
current_release_branch() { current_release_branch() {
v="$(current_version)" v="$(current_version)" # 3.3.0-rc.0
echo "release-${v%.*-"$PRERELEASE_SUFFIX".*}" vf="${v%-"$PRERELEASE_SUFFIX".*}" # 3.3.0
r="${vf%.*}" # 3.3
echo "release-$r"
} }
assert_current_branch() { assert_current_branch() {
......
const { BN, expectRevert } = require('@openzeppelin/test-helpers');
const ethereumjsUtil = require('ethereumjs-util');
const { keccak256 } = ethereumjsUtil;
const { expect } = require('chai');
const UpgradeableBeacon = artifacts.require('UpgradeableBeacon');
const BeaconProxy = artifacts.require('BeaconProxy');
const DummyImplementation = artifacts.require('DummyImplementation');
const DummyImplementationV2 = artifacts.require('DummyImplementationV2');
const BadBeaconNoImpl = artifacts.require('BadBeaconNoImpl');
const BadBeaconNotContract = artifacts.require('BadBeaconNotContract');
function toChecksumAddress (address) {
return ethereumjsUtil.toChecksumAddress('0x' + address.replace(/^0x/, '').padStart(40, '0'));
}
const BEACON_LABEL = 'eip1967.proxy.beacon';
const BEACON_SLOT = '0x' + new BN(keccak256(Buffer.from(BEACON_LABEL))).subn(1).toString(16);
contract('BeaconProxy', function (accounts) {
const [anotherAccount] = accounts;
describe('bad beacon is not accepted', async function () {
it('non-contract beacon', async function () {
await expectRevert(
BeaconProxy.new(anotherAccount, '0x'),
'BeaconProxy: beacon is not a contract',
);
});
it('non-compliant beacon', async function () {
const beacon = await BadBeaconNoImpl.new();
await expectRevert.unspecified(
BeaconProxy.new(beacon.address, '0x'),
);
});
it('non-contract implementation', async function () {
const beacon = await BadBeaconNotContract.new();
await expectRevert(
BeaconProxy.new(beacon.address, '0x'),
'BeaconProxy: beacon implementation is not a contract',
);
});
});
before('deploy implementation', async function () {
this.implementationV0 = await DummyImplementation.new();
this.implementationV1 = await DummyImplementationV2.new();
});
describe('initialization', function () {
before(function () {
this.assertInitialized = async ({ value, balance }) => {
const beaconAddress = toChecksumAddress(await web3.eth.getStorageAt(this.proxy.address, BEACON_SLOT));
expect(beaconAddress).to.equal(this.beacon.address);
const dummy = new DummyImplementation(this.proxy.address);
expect(await dummy.value()).to.bignumber.eq(value);
expect(await web3.eth.getBalance(this.proxy.address)).to.bignumber.eq(balance);
};
});
beforeEach('deploy beacon', async function () {
this.beacon = await UpgradeableBeacon.new(this.implementationV0.address);
});
it('no initialization', async function () {
const data = Buffer.from('');
const balance = '10';
this.proxy = await BeaconProxy.new(this.beacon.address, data, { value: balance });
await this.assertInitialized({ value: '0', balance });
});
it('non-payable initialization', async function () {
const value = '55';
const data = this.implementationV0.contract.methods
.initializeNonPayableWithValue(value)
.encodeABI();
this.proxy = await BeaconProxy.new(this.beacon.address, data);
await this.assertInitialized({ value, balance: '0' });
});
it('payable initialization', async function () {
const value = '55';
const data = this.implementationV0.contract.methods
.initializePayableWithValue(value)
.encodeABI();
const balance = '100';
this.proxy = await BeaconProxy.new(this.beacon.address, data, { value: balance });
await this.assertInitialized({ value, balance });
});
it('reverting initialization', async function () {
const data = this.implementationV0.contract.methods.reverts().encodeABI();
await expectRevert(
BeaconProxy.new(this.beacon.address, data),
'DummyImplementation reverted',
);
});
});
it('upgrade a proxy by upgrading its beacon', async function () {
const beacon = await UpgradeableBeacon.new(this.implementationV0.address);
const value = '10';
const data = this.implementationV0.contract.methods
.initializeNonPayableWithValue(value)
.encodeABI();
const proxy = await BeaconProxy.new(beacon.address, data);
const dummy = new DummyImplementation(proxy.address);
// test initial values
expect(await dummy.value()).to.bignumber.eq(value);
// test initial version
expect(await dummy.version()).to.eq('V1');
// upgrade beacon
await beacon.upgradeTo(this.implementationV1.address);
// test upgraded version
expect(await dummy.version()).to.eq('V2');
});
it('upgrade 2 proxies by upgrading shared beacon', async function () {
const value1 = '10';
const value2 = '42';
const beacon = await UpgradeableBeacon.new(this.implementationV0.address);
const proxy1InitializeData = this.implementationV0.contract.methods
.initializeNonPayableWithValue(value1)
.encodeABI();
const proxy1 = await BeaconProxy.new(beacon.address, proxy1InitializeData);
const proxy2InitializeData = this.implementationV0.contract.methods
.initializeNonPayableWithValue(value2)
.encodeABI();
const proxy2 = await BeaconProxy.new(beacon.address, proxy2InitializeData);
const dummy1 = new DummyImplementation(proxy1.address);
const dummy2 = new DummyImplementation(proxy2.address);
// test initial values
expect(await dummy1.value()).to.bignumber.eq(value1);
expect(await dummy2.value()).to.bignumber.eq(value2);
// test initial version
expect(await dummy1.version()).to.eq('V1');
expect(await dummy2.version()).to.eq('V1');
// upgrade beacon
await beacon.upgradeTo(this.implementationV1.address);
// test upgraded version
expect(await dummy1.version()).to.eq('V2');
expect(await dummy2.version()).to.eq('V2');
});
});
...@@ -80,7 +80,7 @@ contract('ProxyAdmin', function (accounts) { ...@@ -80,7 +80,7 @@ contract('ProxyAdmin', function (accounts) {
describe('#upgradeAndCall', function () { describe('#upgradeAndCall', function () {
context('with unauthorized account', function () { context('with unauthorized account', function () {
it('fails to upgrade', async function () { it('fails to upgrade', async function () {
const callData = new ImplV1('').contract.methods['initializeNonPayable(uint256)'](1337).encodeABI(); const callData = new ImplV1('').contract.methods.initializeNonPayableWithValue(1337).encodeABI();
await expectRevert( await expectRevert(
this.proxyAdmin.upgradeAndCall(this.proxy.address, this.implementationV2.address, callData, this.proxyAdmin.upgradeAndCall(this.proxy.address, this.implementationV2.address, callData,
{ from: anotherAccount }, { from: anotherAccount },
...@@ -104,7 +104,7 @@ contract('ProxyAdmin', function (accounts) { ...@@ -104,7 +104,7 @@ contract('ProxyAdmin', function (accounts) {
context('with valid callData', function () { context('with valid callData', function () {
it('upgrades implementation', async function () { it('upgrades implementation', async function () {
const callData = new ImplV1('').contract.methods['initializeNonPayable(uint256)'](1337).encodeABI(); const callData = new ImplV1('').contract.methods.initializeNonPayableWithValue(1337).encodeABI();
await this.proxyAdmin.upgradeAndCall(this.proxy.address, this.implementationV2.address, callData, await this.proxyAdmin.upgradeAndCall(this.proxy.address, this.implementationV2.address, callData,
{ from: proxyAdminOwner }, { from: proxyAdminOwner },
); );
......
const { expectRevert, expectEvent } = require('@openzeppelin/test-helpers');
const { expect } = require('chai');
const UpgradeableBeacon = artifacts.require('UpgradeableBeacon');
const Implementation1 = artifacts.require('Implementation1');
const Implementation2 = artifacts.require('Implementation2');
contract('UpgradeableBeacon', function (accounts) {
const [owner, other] = accounts;
it('cannot be created with non-contract implementation', async function () {
await expectRevert(
UpgradeableBeacon.new(accounts[0]),
'UpgradeableBeacon: implementation is not a contract',
);
});
context('once deployed', async function () {
beforeEach('deploying beacon', async function () {
this.v1 = await Implementation1.new();
this.beacon = await UpgradeableBeacon.new(this.v1.address, { from: owner });
});
it('returns implementation', async function () {
expect(await this.beacon.implementation()).to.equal(this.v1.address);
});
it('can be upgraded by the owner', async function () {
const v2 = await Implementation2.new();
const receipt = await this.beacon.upgradeTo(v2.address, { from: owner });
expectEvent(receipt, 'Upgraded', { implementation: v2.address });
expect(await this.beacon.implementation()).to.equal(v2.address);
});
it('cannot be upgraded to a non-contract', async function () {
await expectRevert(
this.beacon.upgradeTo(other, { from: owner }),
'UpgradeableBeacon: implementation is not a contract',
);
});
it('cannot be upgraded by other account', async function () {
const v2 = await Implementation2.new();
await expectRevert(
this.beacon.upgradeTo(v2.address, { from: other }),
'Ownable: caller is not the owner',
);
});
});
});
...@@ -144,7 +144,7 @@ module.exports = function shouldBehaveLikeUpgradeableProxy (createProxy, proxyAd ...@@ -144,7 +144,7 @@ module.exports = function shouldBehaveLikeUpgradeableProxy (createProxy, proxyAd
describe('non payable', function () { describe('non payable', function () {
const expectedInitializedValue = 10; const expectedInitializedValue = 10;
const initializeData = new DummyImplementation('').contract const initializeData = new DummyImplementation('').contract
.methods['initializeNonPayable(uint256)'](expectedInitializedValue).encodeABI(); .methods.initializeNonPayableWithValue(expectedInitializedValue).encodeABI();
describe('when not sending balance', function () { describe('when not sending balance', function () {
beforeEach('creating proxy', async function () { beforeEach('creating proxy', async function () {
...@@ -175,7 +175,7 @@ module.exports = function shouldBehaveLikeUpgradeableProxy (createProxy, proxyAd ...@@ -175,7 +175,7 @@ module.exports = function shouldBehaveLikeUpgradeableProxy (createProxy, proxyAd
describe('payable', function () { describe('payable', function () {
const expectedInitializedValue = 42; const expectedInitializedValue = 42;
const initializeData = new DummyImplementation('').contract const initializeData = new DummyImplementation('').contract
.methods['initializePayable(uint256)'](expectedInitializedValue).encodeABI(); .methods.initializePayableWithValue(expectedInitializedValue).encodeABI();
describe('when not sending balance', function () { describe('when not sending balance', function () {
beforeEach('creating proxy', async function () { beforeEach('creating proxy', async function () {
......
...@@ -80,7 +80,7 @@ contract('ERC1155Pausable', function (accounts) { ...@@ -80,7 +80,7 @@ contract('ERC1155Pausable', function (accounts) {
it('reverts when trying to burnBatch', async function () { it('reverts when trying to burnBatch', async function () {
await expectRevert( await expectRevert(
this.token.burn(holder, [firstTokenId], [firstTokenAmount]), this.token.burnBatch(holder, [firstTokenId], [firstTokenAmount]),
'ERC1155Pausable: token transfer while paused', 'ERC1155Pausable: token transfer while paused',
); );
}); });
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment