Commit bce31dee by Francisco Giordano

Merge upstream openzeppelin-contracts into upstream-patched

parents e6b62839 6842518b
...@@ -30,5 +30,6 @@ jobs: ...@@ -30,5 +30,6 @@ jobs:
env: env:
FORCE_COLOR: 1 FORCE_COLOR: 1
ENABLE_GAS_REPORT: true ENABLE_GAS_REPORT: true
- run: npm run test:inheritance
- name: Print gas report - name: Print gas report
run: cat gas-report.txt run: cat gas-report.txt
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
* `ERC20Votes`: add a new extension of the `ERC20` token with support for voting snapshots and delegation. ([#2632](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2632)) * `ERC20Votes`: add a new extension of the `ERC20` token with support for voting snapshots and delegation. ([#2632](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2632))
* `ERC20VotesComp`: Variant of `ERC20Votes` that is compatible with Compound's `Comp` token interface but restricts supply to `uint96`. ([#2706](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2706)) * `ERC20VotesComp`: Variant of `ERC20Votes` that is compatible with Compound's `Comp` token interface but restricts supply to `uint96`. ([#2706](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2706))
* `ERC20Wrapper`: add a new extension of the `ERC20` token which wraps an underlying token. Deposit and withdraw guarantee that the total supply is backed by a corresponding amount of underlying token. ([#2633](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2633))
* Enumerables: Improve gas cost of removal in `EnumerableSet` and `EnumerableMap`. * Enumerables: Improve gas cost of removal in `EnumerableSet` and `EnumerableMap`.
* Enumerables: Improve gas cost of lookup in `EnumerableSet` and `EnumerableMap`. * Enumerables: Improve gas cost of lookup in `EnumerableSet` and `EnumerableMap`.
* `Counter`: add a reset method. ([#2678](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2678)) * `Counter`: add a reset method. ([#2678](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2678))
......
...@@ -25,9 +25,7 @@ abstract contract Ownable is Context { ...@@ -25,9 +25,7 @@ abstract contract Ownable is Context {
* @dev Initializes the contract setting the deployer as the initial owner. * @dev Initializes the contract setting the deployer as the initial owner.
*/ */
constructor() { constructor() {
address msgSender = _msgSender(); _setOwner(_msgSender());
_owner = msgSender;
emit OwnershipTransferred(address(0), msgSender);
} }
/** /**
...@@ -53,8 +51,7 @@ abstract contract Ownable is Context { ...@@ -53,8 +51,7 @@ abstract contract Ownable is Context {
* thereby removing any functionality that is only available to the owner. * thereby removing any functionality that is only available to the owner.
*/ */
function renounceOwnership() public virtual onlyOwner { function renounceOwnership() public virtual onlyOwner {
emit OwnershipTransferred(_owner, address(0)); _setOwner(address(0));
_owner = address(0);
} }
/** /**
...@@ -63,7 +60,12 @@ abstract contract Ownable is Context { ...@@ -63,7 +60,12 @@ abstract contract Ownable is Context {
*/ */
function transferOwnership(address newOwner) public virtual onlyOwner { function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address"); require(newOwner != address(0), "Ownable: new owner is the zero address");
emit OwnershipTransferred(_owner, newOwner); _setOwner(newOwner);
}
function _setOwner(address newOwner) private {
address oldOwner = _owner;
_owner = newOwner; _owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
} }
} }
...@@ -5,7 +5,7 @@ pragma solidity ^0.8.0; ...@@ -5,7 +5,7 @@ pragma solidity ^0.8.0;
import "../token/ERC1155/IERC1155Receiver.sol"; import "../token/ERC1155/IERC1155Receiver.sol";
import "../utils/introspection/ERC165.sol"; import "../utils/introspection/ERC165.sol";
contract ERC1155ReceiverMock is IERC1155Receiver, ERC165 { contract ERC1155ReceiverMock is ERC165, IERC1155Receiver {
bytes4 private _recRetval; bytes4 private _recRetval;
bool private _recReverts; bool private _recReverts;
bytes4 private _batRetval; bytes4 private _batRetval;
......
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../token/ERC20/extensions/ERC20Wrapper.sol";
contract ERC20WrapperMock is ERC20Wrapper {
constructor(
IERC20 _underlyingToken,
string memory name,
string memory symbol
) ERC20(name, symbol) ERC20Wrapper(_underlyingToken) {}
function recover(address account) public returns (uint256) {
return _recover(account);
}
}
...@@ -65,8 +65,7 @@ abstract contract ERC1967Upgrade { ...@@ -65,8 +65,7 @@ abstract contract ERC1967Upgrade {
bytes memory data, bytes memory data,
bool forceCall bool forceCall
) internal { ) internal {
_setImplementation(newImplementation); _upgradeTo(newImplementation);
emit Upgraded(newImplementation);
if (data.length > 0 || forceCall) { if (data.length > 0 || forceCall) {
_functionDelegateCall(newImplementation, data); _functionDelegateCall(newImplementation, data);
} }
...@@ -103,26 +102,7 @@ abstract contract ERC1967Upgrade { ...@@ -103,26 +102,7 @@ abstract contract ERC1967Upgrade {
// Check rollback was effective // Check rollback was effective
require(oldImplementation == _getImplementation(), "ERC1967Upgrade: upgrade breaks further upgrades"); require(oldImplementation == _getImplementation(), "ERC1967Upgrade: upgrade breaks further upgrades");
// Finally reset to the new implementation and log the upgrade // Finally reset to the new implementation and log the upgrade
_setImplementation(newImplementation); _upgradeTo(newImplementation);
emit Upgraded(newImplementation);
}
}
/**
* @dev Perform beacon upgrade with additional setup call. Note: This upgrades the address of the beacon, it does
* not upgrade the implementation contained in the beacon (see {UpgradeableBeacon-_setImplementation} for that).
*
* Emits a {BeaconUpgraded} event.
*/
function _upgradeBeaconToAndCall(
address newBeacon,
bytes memory data,
bool forceCall
) internal {
_setBeacon(newBeacon);
emit BeaconUpgraded(newBeacon);
if (data.length > 0 || forceCall) {
_functionDelegateCall(IBeacon(newBeacon).implementation(), data);
} }
} }
...@@ -194,6 +174,24 @@ abstract contract ERC1967Upgrade { ...@@ -194,6 +174,24 @@ abstract contract ERC1967Upgrade {
} }
/** /**
* @dev Perform beacon upgrade with additional setup call. Note: This upgrades the address of the beacon, it does
* not upgrade the implementation contained in the beacon (see {UpgradeableBeacon-_setImplementation} for that).
*
* Emits a {BeaconUpgraded} event.
*/
function _upgradeBeaconToAndCall(
address newBeacon,
bytes memory data,
bool forceCall
) internal {
_setBeacon(newBeacon);
emit BeaconUpgraded(newBeacon);
if (data.length > 0 || forceCall) {
_functionDelegateCall(IBeacon(newBeacon).implementation(), data);
}
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a delegate call. * but performing a delegate call.
* *
......
...@@ -203,9 +203,9 @@ contract ERC20 is Context, IERC20, IERC20Metadata { ...@@ -203,9 +203,9 @@ contract ERC20 is Context, IERC20, IERC20Metadata {
} }
/** /**
* @dev Moves tokens `amount` from `sender` to `recipient`. * @dev Moves `amount` of tokens from `sender` to `recipient`.
* *
* This is internal function is equivalent to {transfer}, and can be used to * This internal function is equivalent to {transfer}, and can be used to
* e.g. implement automatic token fees, slashing mechanisms, etc. * e.g. implement automatic token fees, slashing mechanisms, etc.
* *
* Emits a {Transfer} event. * Emits a {Transfer} event.
...@@ -234,6 +234,8 @@ contract ERC20 is Context, IERC20, IERC20Metadata { ...@@ -234,6 +234,8 @@ contract ERC20 is Context, IERC20, IERC20Metadata {
_balances[recipient] += amount; _balances[recipient] += amount;
emit Transfer(sender, recipient, amount); emit Transfer(sender, recipient, amount);
_afterTokenTransfer(sender, recipient, amount);
} }
/** @dev Creates `amount` tokens and assigns them to `account`, increasing /** @dev Creates `amount` tokens and assigns them to `account`, increasing
...@@ -253,6 +255,8 @@ contract ERC20 is Context, IERC20, IERC20Metadata { ...@@ -253,6 +255,8 @@ contract ERC20 is Context, IERC20, IERC20Metadata {
_totalSupply += amount; _totalSupply += amount;
_balances[account] += amount; _balances[account] += amount;
emit Transfer(address(0), account, amount); emit Transfer(address(0), account, amount);
_afterTokenTransfer(address(0), account, amount);
} }
/** /**
...@@ -279,6 +283,8 @@ contract ERC20 is Context, IERC20, IERC20Metadata { ...@@ -279,6 +283,8 @@ contract ERC20 is Context, IERC20, IERC20Metadata {
_totalSupply -= amount; _totalSupply -= amount;
emit Transfer(account, address(0), amount); emit Transfer(account, address(0), amount);
_afterTokenTransfer(account, address(0), amount);
} }
/** /**
...@@ -313,7 +319,7 @@ contract ERC20 is Context, IERC20, IERC20Metadata { ...@@ -313,7 +319,7 @@ contract ERC20 is Context, IERC20, IERC20Metadata {
* Calling conditions: * Calling conditions:
* *
* - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
* will be to transferred to `to`. * will be transferred to `to`.
* - when `from` is zero, `amount` tokens will be minted for `to`. * - when `from` is zero, `amount` tokens will be minted for `to`.
* - when `to` is zero, `amount` of ``from``'s tokens will be burned. * - when `to` is zero, `amount` of ``from``'s tokens will be burned.
* - `from` and `to` are never both zero. * - `from` and `to` are never both zero.
...@@ -325,4 +331,24 @@ contract ERC20 is Context, IERC20, IERC20Metadata { ...@@ -325,4 +331,24 @@ contract ERC20 is Context, IERC20, IERC20Metadata {
address to, address to,
uint256 amount uint256 amount
) internal virtual {} ) internal virtual {}
/**
* @dev Hook that is called after any transfer of tokens. This includes
* minting and burning.
*
* Calling conditions:
*
* - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
* has been transferred to `to`.
* - when `from` is zero, `amount` tokens have been minted for `to`.
* - when `to` is zero, `amount` of ``from``'s tokens have been burned.
* - `from` and `to` are never both zero.
*
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
*/
function _afterTokenTransfer(
address from,
address to,
uint256 amount
) internal virtual {}
} }
...@@ -23,6 +23,7 @@ Additionally there are multiple custom extensions, including: ...@@ -23,6 +23,7 @@ Additionally there are multiple custom extensions, including:
* {ERC20FlashMint}: token level support for flash loans through the minting and burning of ephemeral tokens (standardized as ERC3156). * {ERC20FlashMint}: token level support for flash loans through the minting and burning of ephemeral tokens (standardized as ERC3156).
* {ERC20Votes}: support for voting and vote delegation. * {ERC20Votes}: support for voting and vote delegation.
* {ERC20VotesComp}: support for voting and vote delegation (compatible with Compound's tokenn, with uint96 restrictions). * {ERC20VotesComp}: support for voting and vote delegation (compatible with Compound's tokenn, with uint96 restrictions).
* {ERC20Wrapper}: wrapper to create an ERC20 backed by another ERC20, with deposit and withdraw methods. Useful in conjunction with {ERC20Votes}.
Finally, there are some utilities to interact with ERC20 contracts in various ways. Finally, there are some utilities to interact with ERC20 contracts in various ways.
...@@ -58,6 +59,8 @@ NOTE: This core set of contracts is designed to be unopinionated, allowing devel ...@@ -58,6 +59,8 @@ NOTE: This core set of contracts is designed to be unopinionated, allowing devel
{{ERC20VotesComp}} {{ERC20VotesComp}}
{{ERC20Wrapper}}
== Draft EIPs == Draft EIPs
The following EIPs are still in Draft status. Due to their nature as drafts, the details of these contracts may change and we cannot guarantee their xref:ROOT:releases-stability.adoc[stability]. Minor releases of OpenZeppelin Contracts may contain breaking changes for the contracts in this directory, which will be duly announced in the https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/CHANGELOG.md[changelog]. The EIPs included here are used by projects in production and this may make them less likely to change significantly. The following EIPs are still in Draft status. Due to their nature as drafts, the details of these contracts may change and we cannot guarantee their xref:ROOT:releases-stability.adoc[stability]. Minor releases of OpenZeppelin Contracts may contain breaking changes for the contracts in this directory, which will be duly announced in the https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/CHANGELOG.md[changelog]. The EIPs included here are used by projects in production and this may make them less likely to change significantly.
......
...@@ -190,11 +190,13 @@ abstract contract ERC20Votes is ERC20Permit { ...@@ -190,11 +190,13 @@ abstract contract ERC20Votes is ERC20Permit {
* *
* Emits a {DelegateVotesChanged} event. * Emits a {DelegateVotesChanged} event.
*/ */
function _beforeTokenTransfer( function _afterTokenTransfer(
address from, address from,
address to, address to,
uint256 amount uint256 amount
) internal virtual override { ) internal virtual override {
super._afterTokenTransfer(from, to, amount);
_moveVotingPower(delegates(from), delegates(to), amount); _moveVotingPower(delegates(from), delegates(to), amount);
} }
......
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../ERC20.sol";
import "../utils/SafeERC20.sol";
/**
* @dev Extension of the ERC20 token contract to support token wrapping.
*
* Users can deposit and withdraw "underlying tokens" and receive a matching number of "wrapped tokens". This is useful
* in conjunction with other modules. For example, combining this wrapping mechanism with {ERC20Votes} will allow the
* wrapping of an existing "basic" ERC20 into a governance token.
*
* _Available since v4.2._
*/
abstract contract ERC20Wrapper is ERC20 {
IERC20 public immutable underlying;
constructor(IERC20 underlyingToken) {
underlying = underlyingToken;
}
/**
* @dev Allow a user to deposit underlying tokens and mint the corresponding number of wrapped tokens.
*/
function depositFor(address account, uint256 amount) public virtual returns (bool) {
SafeERC20.safeTransferFrom(underlying, _msgSender(), address(this), amount);
_mint(account, amount);
return true;
}
/**
* @dev Allow a user to burn a number of wrapped tokens and withdraw the corresponding number of underlying tokens.
*/
function withdrawTo(address account, uint256 amount) public virtual returns (bool) {
_burn(_msgSender(), amount);
SafeERC20.safeTransfer(underlying, account, amount);
return true;
}
/**
* @dev Mint wrapped token to cover any underlyingTokens that would have been transfered by mistake. Internal
* function that can be exposed with access control if desired.
*/
function _recover(address account) internal virtual returns (uint256) {
uint256 value = underlying.balanceOf(address(this)) - totalSupply();
_mint(account, value);
return value;
}
}
...@@ -60,7 +60,7 @@ library Address { ...@@ -60,7 +60,7 @@ library Address {
/** /**
* @dev Performs a Solidity function call using a low level `call`. A * @dev Performs a Solidity function call using a low level `call`. A
* plain`call` is an unsafe replacement for a function call: use this * plain `call` is an unsafe replacement for a function call: use this
* function instead. * function instead.
* *
* If `target` reverts with a revert reason, it is bubbled up by this * If `target` reverts with a revert reason, it is bubbled up by this
......
...@@ -18,7 +18,6 @@ abstract contract Context { ...@@ -18,7 +18,6 @@ abstract contract Context {
} }
function _msgData() internal view virtual returns (bytes calldata) { function _msgData() internal view virtual returns (bytes calldata) {
this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
return msg.data; return msg.data;
} }
} }
...@@ -6,7 +6,7 @@ pragma solidity ^0.8.0; ...@@ -6,7 +6,7 @@ pragma solidity ^0.8.0;
* @dev String operations. * @dev String operations.
*/ */
library Strings { library Strings {
bytes16 private constant _ALPHABET = "0123456789abcdef"; bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef";
/** /**
* @dev Converts a `uint256` to its ASCII `string` decimal representation. * @dev Converts a `uint256` to its ASCII `string` decimal representation.
...@@ -57,7 +57,7 @@ library Strings { ...@@ -57,7 +57,7 @@ library Strings {
buffer[0] = "0"; buffer[0] = "0";
buffer[1] = "x"; buffer[1] = "x";
for (uint256 i = 2 * length + 1; i > 1; --i) { for (uint256 i = 2 * length + 1; i > 1; --i) {
buffer[i] = _ALPHABET[value & 0xf]; buffer[i] = _HEX_SYMBOLS[value & 0xf];
value >>= 4; value >>= 4;
} }
require(value == 0, "Strings: hex length insufficient"); require(value == 0, "Strings: hex length insufficient");
......
...@@ -28,15 +28,13 @@ library ECDSA { ...@@ -28,15 +28,13 @@ library ECDSA {
* - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers] * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]
*/ */
function recover(bytes32 hash, bytes memory signature) internal pure returns (address) { function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
// Divide the signature in r, s and v variables
bytes32 r;
bytes32 s;
uint8 v;
// Check the signature length // Check the signature length
// - case 65: r,s,v signature (standard) // - case 65: r,s,v signature (standard)
// - case 64: r,vs signature (cf https://eips.ethereum.org/EIPS/eip-2098) _Available since v4.1._ // - case 64: r,vs signature (cf https://eips.ethereum.org/EIPS/eip-2098) _Available since v4.1._
if (signature.length == 65) { if (signature.length == 65) {
bytes32 r;
bytes32 s;
uint8 v;
// ecrecover takes the signature parameters, and the only way to get them // ecrecover takes the signature parameters, and the only way to get them
// currently is to use assembly. // currently is to use assembly.
assembly { assembly {
...@@ -44,25 +42,45 @@ library ECDSA { ...@@ -44,25 +42,45 @@ library ECDSA {
s := mload(add(signature, 0x40)) s := mload(add(signature, 0x40))
v := byte(0, mload(add(signature, 0x60))) v := byte(0, mload(add(signature, 0x60)))
} }
return recover(hash, v, r, s);
} else if (signature.length == 64) { } else if (signature.length == 64) {
bytes32 r;
bytes32 vs;
// ecrecover takes the signature parameters, and the only way to get them // ecrecover takes the signature parameters, and the only way to get them
// currently is to use assembly. // currently is to use assembly.
assembly { assembly {
let vs := mload(add(signature, 0x40))
r := mload(add(signature, 0x20)) r := mload(add(signature, 0x20))
s := and(vs, 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff) vs := mload(add(signature, 0x40))
v := add(shr(255, vs), 27)
} }
return recover(hash, r, vs);
} else { } else {
revert("ECDSA: invalid signature length"); revert("ECDSA: invalid signature length");
} }
}
/**
* @dev Overload of {ECDSA-recover} that receives the `r` and `vs` short-signature fields separately.
*
* See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures]
*
* _Available since v4.2._
*/
function recover(
bytes32 hash,
bytes32 r,
bytes32 vs
) internal pure returns (address) {
bytes32 s;
uint8 v;
assembly {
s := and(vs, 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)
v := add(shr(255, vs), 27)
}
return recover(hash, v, r, s); return recover(hash, v, r, s);
} }
/** /**
* @dev Overload of {ECDSA-recover} that receives the `v`, * @dev Overload of {ECDSA-recover} that receives the `v`, `r` and `s` signature fields separately.
* `r` and `s` signature fields separately.
*/ */
function recover( function recover(
bytes32 hash, bytes32 hash,
......
...@@ -59,10 +59,10 @@ abstract contract EIP712 { ...@@ -59,10 +59,10 @@ abstract contract EIP712 {
function _buildDomainSeparator( function _buildDomainSeparator(
bytes32 typeHash, bytes32 typeHash,
bytes32 name, bytes32 nameHash,
bytes32 version bytes32 versionHash
) private view returns (bytes32) { ) private view returns (bytes32) {
return keccak256(abi.encode(typeHash, name, version, block.chainid, address(this))); return keccak256(abi.encode(typeHash, nameHash, versionHash, block.chainid, address(this)));
} }
/** /**
......
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -29,6 +29,7 @@ ...@@ -29,6 +29,7 @@
"release": "scripts/release/release.sh", "release": "scripts/release/release.sh",
"version": "scripts/release/version.sh", "version": "scripts/release/version.sh",
"test": "hardhat test", "test": "hardhat test",
"test:inheritance": "node scripts/inheritanceOrdering artifacts/build-info/*",
"gas-report": "env ENABLE_GAS_REPORT=true npm run test" "gas-report": "env ENABLE_GAS_REPORT=true npm run test"
}, },
"repository": { "repository": {
...@@ -65,6 +66,7 @@ ...@@ -65,6 +66,7 @@
"eth-sig-util": "^3.0.0", "eth-sig-util": "^3.0.0",
"ethereumjs-util": "^7.0.7", "ethereumjs-util": "^7.0.7",
"ethereumjs-wallet": "^1.0.1", "ethereumjs-wallet": "^1.0.1",
"graphlib": "^2.1.8",
"hardhat": "^2.0.6", "hardhat": "^2.0.6",
"hardhat-gas-reporter": "^1.0.4", "hardhat-gas-reporter": "^1.0.4",
"keccak256": "^1.0.2", "keccak256": "^1.0.2",
...@@ -72,11 +74,11 @@ ...@@ -72,11 +74,11 @@
"lodash.zip": "^4.2.0", "lodash.zip": "^4.2.0",
"merkletreejs": "^0.2.13", "merkletreejs": "^0.2.13",
"micromatch": "^4.0.2", "micromatch": "^4.0.2",
"mocha": "^8.0.1",
"prettier": "^2.3.0", "prettier": "^2.3.0",
"prettier-plugin-solidity": "^1.0.0-beta.13", "prettier-plugin-solidity": "^1.0.0-beta.13",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"solhint": "^3.3.6", "solhint": "^3.3.6",
"solidity-ast": "^0.4.25",
"solidity-coverage": "^0.7.11", "solidity-coverage": "^0.7.11",
"solidity-docgen": "^0.5.3", "solidity-docgen": "^0.5.3",
"web3": "^1.3.0", "web3": "^1.3.0",
......
const path = require('path');
const graphlib = require('graphlib');
const { findAll } = require('solidity-ast/utils');
const { _: artifacts } = require('yargs').argv;
for (const artifact of artifacts) {
const { output: solcOutput } = require(path.resolve(__dirname, '..', artifact));
const graph = new graphlib.Graph({ directed: true });
const names = {};
const linearized = [];
for (const source in solcOutput.contracts) {
for (const contractDef of findAll('ContractDefinition', solcOutput.sources[source].ast)) {
names[contractDef.id] = contractDef.name;
linearized.push(contractDef.linearizedBaseContracts);
contractDef.linearizedBaseContracts.forEach((c1, i, contracts) => contracts.slice(i + 1).forEach(c2 => {
graph.setEdge(c1, c2);
}));
}
}
graphlib.alg.findCycles(graph).forEach(([ c1, c2 ]) => {
console.log(`Conflict between ${names[c1]} and ${names[c2]} detected in the following dependency chains:`);
linearized
.filter(chain => chain.includes(parseInt(c1)) && chain.includes(parseInt(c2)))
.forEach(chain => {
const comp = chain.indexOf(c1) < chain.indexOf(c2) ? '>' : '<';
console.log(`- ${names[c1]} ${comp} ${names[c2]}: ${chain.reverse().map(id => names[id]).join(', ')}`);
});
process.exitCode = 1;
});
}
if (!process.exitCode) {
console.log('Contract ordering is consistent.');
}
...@@ -306,6 +306,9 @@ contract('ERC20Votes', function (accounts) { ...@@ -306,6 +306,9 @@ contract('ERC20Votes', function (accounts) {
expectEvent(receipt, 'Transfer', { from: holder, to: recipient, value: '1' }); expectEvent(receipt, 'Transfer', { from: holder, to: recipient, value: '1' });
expectEvent(receipt, 'DelegateVotesChanged', { delegate: holder, previousBalance: supply, newBalance: supply.subn(1) }); expectEvent(receipt, 'DelegateVotesChanged', { delegate: holder, previousBalance: supply, newBalance: supply.subn(1) });
const { logIndex: transferLogIndex } = receipt.logs.find(({ event }) => event == 'Transfer');
expect(receipt.logs.filter(({ event }) => event == 'DelegateVotesChanged').every(({ logIndex }) => transferLogIndex < logIndex)).to.be.equal(true);
this.holderVotes = supply.subn(1); this.holderVotes = supply.subn(1);
this.recipientVotes = '0'; this.recipientVotes = '0';
}); });
...@@ -317,6 +320,9 @@ contract('ERC20Votes', function (accounts) { ...@@ -317,6 +320,9 @@ contract('ERC20Votes', function (accounts) {
expectEvent(receipt, 'Transfer', { from: holder, to: recipient, value: '1' }); expectEvent(receipt, 'Transfer', { from: holder, to: recipient, value: '1' });
expectEvent(receipt, 'DelegateVotesChanged', { delegate: recipient, previousBalance: '0', newBalance: '1' }); expectEvent(receipt, 'DelegateVotesChanged', { delegate: recipient, previousBalance: '0', newBalance: '1' });
const { logIndex: transferLogIndex } = receipt.logs.find(({ event }) => event == 'Transfer');
expect(receipt.logs.filter(({ event }) => event == 'DelegateVotesChanged').every(({ logIndex }) => transferLogIndex < logIndex)).to.be.equal(true);
this.holderVotes = '0'; this.holderVotes = '0';
this.recipientVotes = '1'; this.recipientVotes = '1';
}); });
...@@ -330,6 +336,9 @@ contract('ERC20Votes', function (accounts) { ...@@ -330,6 +336,9 @@ contract('ERC20Votes', function (accounts) {
expectEvent(receipt, 'DelegateVotesChanged', { delegate: holder, previousBalance: supply, newBalance: supply.subn(1) }); expectEvent(receipt, 'DelegateVotesChanged', { delegate: holder, previousBalance: supply, newBalance: supply.subn(1) });
expectEvent(receipt, 'DelegateVotesChanged', { delegate: recipient, previousBalance: '0', newBalance: '1' }); expectEvent(receipt, 'DelegateVotesChanged', { delegate: recipient, previousBalance: '0', newBalance: '1' });
const { logIndex: transferLogIndex } = receipt.logs.find(({ event }) => event == 'Transfer');
expect(receipt.logs.filter(({ event }) => event == 'DelegateVotesChanged').every(({ logIndex }) => transferLogIndex < logIndex)).to.be.equal(true);
this.holderVotes = supply.subn(1); this.holderVotes = supply.subn(1);
this.recipientVotes = '1'; this.recipientVotes = '1';
}); });
......
const { BN, constants, expectEvent, expectRevert } = require('@openzeppelin/test-helpers');
const { expect } = require('chai');
const { ZERO_ADDRESS, MAX_UINT256 } = constants;
const { shouldBehaveLikeERC20 } = require('../ERC20.behavior');
const ERC20Mock = artifacts.require('ERC20Mock');
const ERC20WrapperMock = artifacts.require('ERC20WrapperMock');
contract('ERC20', function (accounts) {
const [ initialHolder, recipient, anotherAccount ] = accounts;
const name = 'My Token';
const symbol = 'MTKN';
const initialSupply = new BN(100);
beforeEach(async function () {
this.underlying = await ERC20Mock.new(name, symbol, initialHolder, initialSupply);
this.token = await ERC20WrapperMock.new(this.underlying.address, `Wrapped ${name}`, `W${symbol}`);
});
afterEach(async function () {
expect(await this.underlying.balanceOf(this.token.address)).to.be.bignumber.equal(await this.token.totalSupply());
});
it('has a name', async function () {
expect(await this.token.name()).to.equal(`Wrapped ${name}`);
});
it('has a symbol', async function () {
expect(await this.token.symbol()).to.equal(`W${symbol}`);
});
it('has 18 decimals', async function () {
expect(await this.token.decimals()).to.be.bignumber.equal('18');
});
it('has underlying', async function () {
expect(await this.token.underlying()).to.be.bignumber.equal(this.underlying.address);
});
describe('deposit', function () {
it('valid', async function () {
await this.underlying.approve(this.token.address, initialSupply, { from: initialHolder });
const { tx } = await this.token.depositFor(initialHolder, initialSupply, { from: initialHolder });
expectEvent.inTransaction(tx, this.underlying, 'Transfer', {
from: initialHolder,
to: this.token.address,
value: initialSupply,
});
expectEvent.inTransaction(tx, this.token, 'Transfer', {
from: ZERO_ADDRESS,
to: initialHolder,
value: initialSupply,
});
});
it('missing approval', async function () {
await expectRevert(
this.token.depositFor(initialHolder, initialSupply, { from: initialHolder }),
'ERC20: transfer amount exceeds allowance',
);
});
it('missing balance', async function () {
await this.underlying.approve(this.token.address, MAX_UINT256, { from: initialHolder });
await expectRevert(
this.token.depositFor(initialHolder, MAX_UINT256, { from: initialHolder }),
'ERC20: transfer amount exceeds balance',
);
});
it('to other account', async function () {
await this.underlying.approve(this.token.address, initialSupply, { from: initialHolder });
const { tx } = await this.token.depositFor(anotherAccount, initialSupply, { from: initialHolder });
expectEvent.inTransaction(tx, this.underlying, 'Transfer', {
from: initialHolder,
to: this.token.address,
value: initialSupply,
});
expectEvent.inTransaction(tx, this.token, 'Transfer', {
from: ZERO_ADDRESS,
to: anotherAccount,
value: initialSupply,
});
});
});
describe('withdraw', function () {
beforeEach(async function () {
await this.underlying.approve(this.token.address, initialSupply, { from: initialHolder });
await this.token.depositFor(initialHolder, initialSupply, { from: initialHolder });
});
it('missing balance', async function () {
await expectRevert(
this.token.withdrawTo(initialHolder, MAX_UINT256, { from: initialHolder }),
'ERC20: burn amount exceeds balance',
);
});
it('valid', async function () {
const value = new BN(42);
const { tx } = await this.token.withdrawTo(initialHolder, value, { from: initialHolder });
expectEvent.inTransaction(tx, this.underlying, 'Transfer', {
from: this.token.address,
to: initialHolder,
value: value,
});
expectEvent.inTransaction(tx, this.token, 'Transfer', {
from: initialHolder,
to: ZERO_ADDRESS,
value: value,
});
});
it('entire balance', async function () {
const { tx } = await this.token.withdrawTo(initialHolder, initialSupply, { from: initialHolder });
expectEvent.inTransaction(tx, this.underlying, 'Transfer', {
from: this.token.address,
to: initialHolder,
value: initialSupply,
});
expectEvent.inTransaction(tx, this.token, 'Transfer', {
from: initialHolder,
to: ZERO_ADDRESS,
value: initialSupply,
});
});
it('to other account', async function () {
const { tx } = await this.token.withdrawTo(anotherAccount, initialSupply, { from: initialHolder });
expectEvent.inTransaction(tx, this.underlying, 'Transfer', {
from: this.token.address,
to: anotherAccount,
value: initialSupply,
});
expectEvent.inTransaction(tx, this.token, 'Transfer', {
from: initialHolder,
to: ZERO_ADDRESS,
value: initialSupply,
});
});
});
describe('recover', function () {
it('nothing to recover', async function () {
await this.underlying.approve(this.token.address, initialSupply, { from: initialHolder });
await this.token.depositFor(initialHolder, initialSupply, { from: initialHolder });
const { tx } = await this.token.recover(anotherAccount);
expectEvent.inTransaction(tx, this.token, 'Transfer', {
from: ZERO_ADDRESS,
to: anotherAccount,
value: '0',
});
});
it('something to recover', async function () {
await this.underlying.transfer(this.token.address, initialSupply, { from: initialHolder });
const { tx } = await this.token.recover(anotherAccount);
expectEvent.inTransaction(tx, this.token, 'Transfer', {
from: ZERO_ADDRESS,
to: anotherAccount,
value: initialSupply,
});
});
});
describe('erc20 behaviour', function () {
beforeEach(async function () {
await this.underlying.approve(this.token.address, initialSupply, { from: initialHolder });
await this.token.depositFor(initialHolder, initialSupply, { from: initialHolder });
});
shouldBehaveLikeERC20('ERC20', initialSupply, initialHolder, recipient, anotherAccount);
});
});
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