mirror of
https://github.com/th30d4y/OpenLearnX.git
synced 2026-05-26 19:26:33 +00:00
Fix .gitignore: stop tracking ignored files
This commit is contained in:
@@ -0,0 +1,361 @@
|
||||
const { balance, constants, ether, expectRevert, send, expectEvent } = require('@openzeppelin/test-helpers');
|
||||
const { expect } = require('chai');
|
||||
|
||||
const Address = artifacts.require('$Address');
|
||||
const EtherReceiver = artifacts.require('EtherReceiverMock');
|
||||
const CallReceiverMock = artifacts.require('CallReceiverMock');
|
||||
|
||||
contract('Address', function (accounts) {
|
||||
const [recipient, other] = accounts;
|
||||
|
||||
beforeEach(async function () {
|
||||
this.mock = await Address.new();
|
||||
});
|
||||
|
||||
describe('isContract', function () {
|
||||
it('returns false for account address', async function () {
|
||||
expect(await this.mock.$isContract(other)).to.equal(false);
|
||||
});
|
||||
|
||||
it('returns true for contract address', async function () {
|
||||
expect(await this.mock.$isContract(this.mock.address)).to.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('sendValue', function () {
|
||||
beforeEach(async function () {
|
||||
this.recipientTracker = await balance.tracker(recipient);
|
||||
});
|
||||
|
||||
context('when sender contract has no funds', function () {
|
||||
it('sends 0 wei', async function () {
|
||||
await this.mock.$sendValue(other, 0);
|
||||
|
||||
expect(await this.recipientTracker.delta()).to.be.bignumber.equal('0');
|
||||
});
|
||||
|
||||
it('reverts when sending non-zero amounts', async function () {
|
||||
await expectRevert(this.mock.$sendValue(other, 1), 'Address: insufficient balance');
|
||||
});
|
||||
});
|
||||
|
||||
context('when sender contract has funds', function () {
|
||||
const funds = ether('1');
|
||||
beforeEach(async function () {
|
||||
await send.ether(other, this.mock.address, funds);
|
||||
});
|
||||
|
||||
it('sends 0 wei', async function () {
|
||||
await this.mock.$sendValue(recipient, 0);
|
||||
expect(await this.recipientTracker.delta()).to.be.bignumber.equal('0');
|
||||
});
|
||||
|
||||
it('sends non-zero amounts', async function () {
|
||||
await this.mock.$sendValue(recipient, funds.subn(1));
|
||||
expect(await this.recipientTracker.delta()).to.be.bignumber.equal(funds.subn(1));
|
||||
});
|
||||
|
||||
it('sends the whole balance', async function () {
|
||||
await this.mock.$sendValue(recipient, funds);
|
||||
expect(await this.recipientTracker.delta()).to.be.bignumber.equal(funds);
|
||||
expect(await balance.current(this.mock.address)).to.be.bignumber.equal('0');
|
||||
});
|
||||
|
||||
it('reverts when sending more than the balance', async function () {
|
||||
await expectRevert(this.mock.$sendValue(recipient, funds.addn(1)), 'Address: insufficient balance');
|
||||
});
|
||||
|
||||
context('with contract recipient', function () {
|
||||
beforeEach(async function () {
|
||||
this.target = await EtherReceiver.new();
|
||||
});
|
||||
|
||||
it('sends funds', async function () {
|
||||
const tracker = await balance.tracker(this.target.address);
|
||||
|
||||
await this.target.setAcceptEther(true);
|
||||
await this.mock.$sendValue(this.target.address, funds);
|
||||
|
||||
expect(await tracker.delta()).to.be.bignumber.equal(funds);
|
||||
});
|
||||
|
||||
it('reverts on recipient revert', async function () {
|
||||
await this.target.setAcceptEther(false);
|
||||
await expectRevert(
|
||||
this.mock.$sendValue(this.target.address, funds),
|
||||
'Address: unable to send value, recipient may have reverted',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('functionCall', function () {
|
||||
beforeEach(async function () {
|
||||
this.target = await CallReceiverMock.new();
|
||||
});
|
||||
|
||||
context('with valid contract receiver', function () {
|
||||
it('calls the requested function', async function () {
|
||||
const abiEncodedCall = this.target.contract.methods.mockFunction().encodeABI();
|
||||
|
||||
const receipt = await this.mock.$functionCall(this.target.address, abiEncodedCall);
|
||||
|
||||
expectEvent(receipt, 'return$functionCall_address_bytes', {
|
||||
ret0: web3.eth.abi.encodeParameters(['string'], ['0x1234']),
|
||||
});
|
||||
await expectEvent.inTransaction(receipt.tx, CallReceiverMock, 'MockFunctionCalled');
|
||||
});
|
||||
|
||||
it('calls the requested empty return function', async function () {
|
||||
const abiEncodedCall = this.target.contract.methods.mockFunctionEmptyReturn().encodeABI();
|
||||
|
||||
const receipt = await this.mock.$functionCall(this.target.address, abiEncodedCall);
|
||||
|
||||
await expectEvent.inTransaction(receipt.tx, CallReceiverMock, 'MockFunctionCalled');
|
||||
});
|
||||
|
||||
it('reverts when the called function reverts with no reason', async function () {
|
||||
const abiEncodedCall = this.target.contract.methods.mockFunctionRevertsNoReason().encodeABI();
|
||||
|
||||
await expectRevert(
|
||||
this.mock.$functionCall(this.target.address, abiEncodedCall),
|
||||
'Address: low-level call failed',
|
||||
);
|
||||
});
|
||||
|
||||
it('reverts when the called function reverts, bubbling up the revert reason', async function () {
|
||||
const abiEncodedCall = this.target.contract.methods.mockFunctionRevertsReason().encodeABI();
|
||||
|
||||
await expectRevert(this.mock.$functionCall(this.target.address, abiEncodedCall), 'CallReceiverMock: reverting');
|
||||
});
|
||||
|
||||
it('reverts when the called function runs out of gas', async function () {
|
||||
const abiEncodedCall = this.target.contract.methods.mockFunctionOutOfGas().encodeABI();
|
||||
|
||||
await expectRevert(
|
||||
this.mock.$functionCall(this.target.address, abiEncodedCall, { gas: '120000' }),
|
||||
'Address: low-level call failed',
|
||||
);
|
||||
});
|
||||
|
||||
it('reverts when the called function throws', async function () {
|
||||
const abiEncodedCall = this.target.contract.methods.mockFunctionThrows().encodeABI();
|
||||
|
||||
await expectRevert.unspecified(this.mock.$functionCall(this.target.address, abiEncodedCall));
|
||||
});
|
||||
|
||||
it('bubbles up error message if specified', async function () {
|
||||
const errorMsg = 'Address: expected error';
|
||||
await expectRevert(this.mock.$functionCall(this.target.address, '0x12345678', errorMsg), errorMsg);
|
||||
});
|
||||
|
||||
it('reverts when function does not exist', async function () {
|
||||
const abiEncodedCall = web3.eth.abi.encodeFunctionCall(
|
||||
{
|
||||
name: 'mockFunctionDoesNotExist',
|
||||
type: 'function',
|
||||
inputs: [],
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
await expectRevert(
|
||||
this.mock.$functionCall(this.target.address, abiEncodedCall),
|
||||
'Address: low-level call failed',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
context('with non-contract receiver', function () {
|
||||
it('reverts when address is not a contract', async function () {
|
||||
const [recipient] = accounts;
|
||||
const abiEncodedCall = this.target.contract.methods.mockFunction().encodeABI();
|
||||
|
||||
await expectRevert(this.mock.$functionCall(recipient, abiEncodedCall), 'Address: call to non-contract');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('functionCallWithValue', function () {
|
||||
beforeEach(async function () {
|
||||
this.target = await CallReceiverMock.new();
|
||||
});
|
||||
|
||||
context('with zero value', function () {
|
||||
it('calls the requested function', async function () {
|
||||
const abiEncodedCall = this.target.contract.methods.mockFunction().encodeABI();
|
||||
|
||||
const receipt = await this.mock.$functionCallWithValue(this.target.address, abiEncodedCall, 0);
|
||||
expectEvent(receipt, 'return$functionCallWithValue_address_bytes_uint256', {
|
||||
ret0: web3.eth.abi.encodeParameters(['string'], ['0x1234']),
|
||||
});
|
||||
await expectEvent.inTransaction(receipt.tx, CallReceiverMock, 'MockFunctionCalled');
|
||||
});
|
||||
});
|
||||
|
||||
context('with non-zero value', function () {
|
||||
const amount = ether('1.2');
|
||||
|
||||
it('reverts if insufficient sender balance', async function () {
|
||||
const abiEncodedCall = this.target.contract.methods.mockFunction().encodeABI();
|
||||
|
||||
await expectRevert(
|
||||
this.mock.$functionCallWithValue(this.target.address, abiEncodedCall, amount),
|
||||
'Address: insufficient balance for call',
|
||||
);
|
||||
});
|
||||
|
||||
it('calls the requested function with existing value', async function () {
|
||||
const abiEncodedCall = this.target.contract.methods.mockFunction().encodeABI();
|
||||
|
||||
const tracker = await balance.tracker(this.target.address);
|
||||
|
||||
await send.ether(other, this.mock.address, amount);
|
||||
|
||||
const receipt = await this.mock.$functionCallWithValue(this.target.address, abiEncodedCall, amount);
|
||||
expectEvent(receipt, 'return$functionCallWithValue_address_bytes_uint256', {
|
||||
ret0: web3.eth.abi.encodeParameters(['string'], ['0x1234']),
|
||||
});
|
||||
await expectEvent.inTransaction(receipt.tx, CallReceiverMock, 'MockFunctionCalled');
|
||||
|
||||
expect(await tracker.delta()).to.be.bignumber.equal(amount);
|
||||
});
|
||||
|
||||
it('calls the requested function with transaction funds', async function () {
|
||||
const abiEncodedCall = this.target.contract.methods.mockFunction().encodeABI();
|
||||
|
||||
const tracker = await balance.tracker(this.target.address);
|
||||
|
||||
expect(await balance.current(this.mock.address)).to.be.bignumber.equal('0');
|
||||
|
||||
const receipt = await this.mock.$functionCallWithValue(this.target.address, abiEncodedCall, amount, {
|
||||
from: other,
|
||||
value: amount,
|
||||
});
|
||||
expectEvent(receipt, 'return$functionCallWithValue_address_bytes_uint256', {
|
||||
ret0: web3.eth.abi.encodeParameters(['string'], ['0x1234']),
|
||||
});
|
||||
await expectEvent.inTransaction(receipt.tx, CallReceiverMock, 'MockFunctionCalled');
|
||||
|
||||
expect(await tracker.delta()).to.be.bignumber.equal(amount);
|
||||
});
|
||||
|
||||
it('reverts when calling non-payable functions', async function () {
|
||||
const abiEncodedCall = this.target.contract.methods.mockFunctionNonPayable().encodeABI();
|
||||
|
||||
await send.ether(other, this.mock.address, amount);
|
||||
await expectRevert(
|
||||
this.mock.$functionCallWithValue(this.target.address, abiEncodedCall, amount),
|
||||
'Address: low-level call with value failed',
|
||||
);
|
||||
});
|
||||
|
||||
it('bubbles up error message if specified', async function () {
|
||||
const errorMsg = 'Address: expected error';
|
||||
await expectRevert(this.mock.$functionCallWithValue(this.target.address, '0x12345678', 0, errorMsg), errorMsg);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('functionStaticCall', function () {
|
||||
beforeEach(async function () {
|
||||
this.target = await CallReceiverMock.new();
|
||||
});
|
||||
|
||||
it('calls the requested function', async function () {
|
||||
const abiEncodedCall = this.target.contract.methods.mockStaticFunction().encodeABI();
|
||||
|
||||
expect(await this.mock.$functionStaticCall(this.target.address, abiEncodedCall)).to.be.equal(
|
||||
web3.eth.abi.encodeParameters(['string'], ['0x1234']),
|
||||
);
|
||||
});
|
||||
|
||||
it('reverts on a non-static function', async function () {
|
||||
const abiEncodedCall = this.target.contract.methods.mockFunction().encodeABI();
|
||||
|
||||
await expectRevert(
|
||||
this.mock.$functionStaticCall(this.target.address, abiEncodedCall),
|
||||
'Address: low-level static call failed',
|
||||
);
|
||||
});
|
||||
|
||||
it('bubbles up revert reason', async function () {
|
||||
const abiEncodedCall = this.target.contract.methods.mockFunctionRevertsReason().encodeABI();
|
||||
|
||||
await expectRevert(
|
||||
this.mock.$functionStaticCall(this.target.address, abiEncodedCall),
|
||||
'CallReceiverMock: reverting',
|
||||
);
|
||||
});
|
||||
|
||||
it('reverts when address is not a contract', async function () {
|
||||
const [recipient] = accounts;
|
||||
const abiEncodedCall = this.target.contract.methods.mockFunction().encodeABI();
|
||||
|
||||
await expectRevert(this.mock.$functionStaticCall(recipient, abiEncodedCall), 'Address: call to non-contract');
|
||||
});
|
||||
|
||||
it('bubbles up error message if specified', async function () {
|
||||
const errorMsg = 'Address: expected error';
|
||||
await expectRevert(this.mock.$functionCallWithValue(this.target.address, '0x12345678', 0, errorMsg), errorMsg);
|
||||
});
|
||||
});
|
||||
|
||||
describe('functionDelegateCall', function () {
|
||||
beforeEach(async function () {
|
||||
this.target = await CallReceiverMock.new();
|
||||
});
|
||||
|
||||
it('delegate calls the requested function', async function () {
|
||||
// pseudorandom values
|
||||
const slot = '0x93e4c53af435ddf777c3de84bb9a953a777788500e229a468ea1036496ab66a0';
|
||||
const value = '0x6a465d1c49869f71fb65562bcbd7e08c8044074927f0297127203f2a9924ff5b';
|
||||
|
||||
const abiEncodedCall = this.target.contract.methods.mockFunctionWritesStorage(slot, value).encodeABI();
|
||||
|
||||
expect(await web3.eth.getStorageAt(this.mock.address, slot)).to.be.equal(constants.ZERO_BYTES32);
|
||||
|
||||
expectEvent(
|
||||
await this.mock.$functionDelegateCall(this.target.address, abiEncodedCall),
|
||||
'return$functionDelegateCall_address_bytes',
|
||||
{ ret0: web3.eth.abi.encodeParameters(['string'], ['0x1234']) },
|
||||
);
|
||||
|
||||
expect(await web3.eth.getStorageAt(this.mock.address, slot)).to.be.equal(value);
|
||||
});
|
||||
|
||||
it('bubbles up revert reason', async function () {
|
||||
const abiEncodedCall = this.target.contract.methods.mockFunctionRevertsReason().encodeABI();
|
||||
|
||||
await expectRevert(
|
||||
this.mock.$functionDelegateCall(this.target.address, abiEncodedCall),
|
||||
'CallReceiverMock: reverting',
|
||||
);
|
||||
});
|
||||
|
||||
it('reverts when address is not a contract', async function () {
|
||||
const [recipient] = accounts;
|
||||
const abiEncodedCall = this.target.contract.methods.mockFunction().encodeABI();
|
||||
|
||||
await expectRevert(this.mock.$functionDelegateCall(recipient, abiEncodedCall), 'Address: call to non-contract');
|
||||
});
|
||||
|
||||
it('bubbles up error message if specified', async function () {
|
||||
const errorMsg = 'Address: expected error';
|
||||
await expectRevert(this.mock.$functionCallWithValue(this.target.address, '0x12345678', 0, errorMsg), errorMsg);
|
||||
});
|
||||
});
|
||||
|
||||
describe('verifyCallResult', function () {
|
||||
it('returns returndata on success', async function () {
|
||||
const returndata = '0x123abc';
|
||||
expect(await this.mock.$verifyCallResult(true, returndata, '')).to.equal(returndata);
|
||||
});
|
||||
|
||||
it('reverts with return data and error m', async function () {
|
||||
const errorMsg = 'Address: expected error';
|
||||
await expectRevert(this.mock.$verifyCallResult(false, '0x', errorMsg), errorMsg);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,123 @@
|
||||
require('@openzeppelin/test-helpers');
|
||||
|
||||
const { expect } = require('chai');
|
||||
|
||||
const AddressArraysMock = artifacts.require('AddressArraysMock');
|
||||
const Bytes32ArraysMock = artifacts.require('Bytes32ArraysMock');
|
||||
const Uint256ArraysMock = artifacts.require('Uint256ArraysMock');
|
||||
|
||||
contract('Arrays', function () {
|
||||
describe('findUpperBound', function () {
|
||||
context('Even number of elements', function () {
|
||||
const EVEN_ELEMENTS_ARRAY = [11, 12, 13, 14, 15, 16, 17, 18, 19, 20];
|
||||
|
||||
beforeEach(async function () {
|
||||
this.arrays = await Uint256ArraysMock.new(EVEN_ELEMENTS_ARRAY);
|
||||
});
|
||||
|
||||
it('returns correct index for the basic case', async function () {
|
||||
expect(await this.arrays.findUpperBound(16)).to.be.bignumber.equal('5');
|
||||
});
|
||||
|
||||
it('returns 0 for the first element', async function () {
|
||||
expect(await this.arrays.findUpperBound(11)).to.be.bignumber.equal('0');
|
||||
});
|
||||
|
||||
it('returns index of the last element', async function () {
|
||||
expect(await this.arrays.findUpperBound(20)).to.be.bignumber.equal('9');
|
||||
});
|
||||
|
||||
it('returns first index after last element if searched value is over the upper boundary', async function () {
|
||||
expect(await this.arrays.findUpperBound(32)).to.be.bignumber.equal('10');
|
||||
});
|
||||
|
||||
it('returns 0 for the element under the lower boundary', async function () {
|
||||
expect(await this.arrays.findUpperBound(2)).to.be.bignumber.equal('0');
|
||||
});
|
||||
});
|
||||
|
||||
context('Odd number of elements', function () {
|
||||
const ODD_ELEMENTS_ARRAY = [11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21];
|
||||
|
||||
beforeEach(async function () {
|
||||
this.arrays = await Uint256ArraysMock.new(ODD_ELEMENTS_ARRAY);
|
||||
});
|
||||
|
||||
it('returns correct index for the basic case', async function () {
|
||||
expect(await this.arrays.findUpperBound(16)).to.be.bignumber.equal('5');
|
||||
});
|
||||
|
||||
it('returns 0 for the first element', async function () {
|
||||
expect(await this.arrays.findUpperBound(11)).to.be.bignumber.equal('0');
|
||||
});
|
||||
|
||||
it('returns index of the last element', async function () {
|
||||
expect(await this.arrays.findUpperBound(21)).to.be.bignumber.equal('10');
|
||||
});
|
||||
|
||||
it('returns first index after last element if searched value is over the upper boundary', async function () {
|
||||
expect(await this.arrays.findUpperBound(32)).to.be.bignumber.equal('11');
|
||||
});
|
||||
|
||||
it('returns 0 for the element under the lower boundary', async function () {
|
||||
expect(await this.arrays.findUpperBound(2)).to.be.bignumber.equal('0');
|
||||
});
|
||||
});
|
||||
|
||||
context('Array with gap', function () {
|
||||
const WITH_GAP_ARRAY = [11, 12, 13, 14, 15, 20, 21, 22, 23, 24];
|
||||
|
||||
beforeEach(async function () {
|
||||
this.arrays = await Uint256ArraysMock.new(WITH_GAP_ARRAY);
|
||||
});
|
||||
|
||||
it('returns index of first element in next filled range', async function () {
|
||||
expect(await this.arrays.findUpperBound(17)).to.be.bignumber.equal('5');
|
||||
});
|
||||
});
|
||||
|
||||
context('Empty array', function () {
|
||||
beforeEach(async function () {
|
||||
this.arrays = await Uint256ArraysMock.new([]);
|
||||
});
|
||||
|
||||
it('always returns 0 for empty array', async function () {
|
||||
expect(await this.arrays.findUpperBound(10)).to.be.bignumber.equal('0');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('unsafeAccess', function () {
|
||||
for (const { type, artifact, elements } of [
|
||||
{
|
||||
type: 'address',
|
||||
artifact: AddressArraysMock,
|
||||
elements: Array(10)
|
||||
.fill()
|
||||
.map(() => web3.utils.randomHex(20)),
|
||||
},
|
||||
{
|
||||
type: 'bytes32',
|
||||
artifact: Bytes32ArraysMock,
|
||||
elements: Array(10)
|
||||
.fill()
|
||||
.map(() => web3.utils.randomHex(32)),
|
||||
},
|
||||
{
|
||||
type: 'uint256',
|
||||
artifact: Uint256ArraysMock,
|
||||
elements: Array(10)
|
||||
.fill()
|
||||
.map(() => web3.utils.randomHex(32)),
|
||||
},
|
||||
]) {
|
||||
it(type, async function () {
|
||||
const contract = await artifact.new(elements);
|
||||
|
||||
for (const i in elements) {
|
||||
expect(await contract.unsafeAccess(i)).to.be.bignumber.equal(elements[i]);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,33 @@
|
||||
const { expect } = require('chai');
|
||||
|
||||
const Base64 = artifacts.require('$Base64');
|
||||
|
||||
contract('Strings', function () {
|
||||
beforeEach(async function () {
|
||||
this.base64 = await Base64.new();
|
||||
});
|
||||
|
||||
describe('from bytes - base64', function () {
|
||||
it('converts to base64 encoded string with double padding', async function () {
|
||||
const TEST_MESSAGE = 'test';
|
||||
const input = web3.utils.asciiToHex(TEST_MESSAGE);
|
||||
expect(await this.base64.$encode(input)).to.equal('dGVzdA==');
|
||||
});
|
||||
|
||||
it('converts to base64 encoded string with single padding', async function () {
|
||||
const TEST_MESSAGE = 'test1';
|
||||
const input = web3.utils.asciiToHex(TEST_MESSAGE);
|
||||
expect(await this.base64.$encode(input)).to.equal('dGVzdDE=');
|
||||
});
|
||||
|
||||
it('converts to base64 encoded string without padding', async function () {
|
||||
const TEST_MESSAGE = 'test12';
|
||||
const input = web3.utils.asciiToHex(TEST_MESSAGE);
|
||||
expect(await this.base64.$encode(input)).to.equal('dGVzdDEy');
|
||||
});
|
||||
|
||||
it('empty bytes', async function () {
|
||||
expect(await this.base64.$encode([])).to.equal('');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,347 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// This file was procedurally generated from scripts/generate/templates/Checkpoints.t.js.
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import "forge-std/Test.sol";
|
||||
import "../../contracts/utils/Checkpoints.sol";
|
||||
import "../../contracts/utils/math/SafeCast.sol";
|
||||
|
||||
contract CheckpointsHistoryTest is Test {
|
||||
using Checkpoints for Checkpoints.History;
|
||||
|
||||
// Maximum gap between keys used during the fuzzing tests: the `_prepareKeys` function with make sure that
|
||||
// key#n+1 is in the [key#n, key#n + _KEY_MAX_GAP] range.
|
||||
uint8 internal constant _KEY_MAX_GAP = 64;
|
||||
|
||||
Checkpoints.History internal _ckpts;
|
||||
|
||||
// helpers
|
||||
function _boundUint32(uint32 x, uint32 min, uint32 max) internal view returns (uint32) {
|
||||
return SafeCast.toUint32(bound(uint256(x), uint256(min), uint256(max)));
|
||||
}
|
||||
|
||||
function _prepareKeys(uint32[] memory keys, uint32 maxSpread) internal view {
|
||||
uint32 lastKey = 0;
|
||||
for (uint256 i = 0; i < keys.length; ++i) {
|
||||
uint32 key = _boundUint32(keys[i], lastKey, lastKey + maxSpread);
|
||||
keys[i] = key;
|
||||
lastKey = key;
|
||||
}
|
||||
}
|
||||
|
||||
function _assertLatestCheckpoint(bool exist, uint32 key, uint224 value) internal {
|
||||
(bool _exist, uint32 _key, uint224 _value) = _ckpts.latestCheckpoint();
|
||||
assertEq(_exist, exist);
|
||||
assertEq(_key, key);
|
||||
assertEq(_value, value);
|
||||
}
|
||||
|
||||
// tests
|
||||
function testPush(uint32[] memory keys, uint224[] memory values, uint32 pastKey) public {
|
||||
vm.assume(values.length > 0 && values.length <= keys.length);
|
||||
_prepareKeys(keys, _KEY_MAX_GAP);
|
||||
|
||||
// initial state
|
||||
assertEq(_ckpts.length(), 0);
|
||||
assertEq(_ckpts.latest(), 0);
|
||||
_assertLatestCheckpoint(false, 0, 0);
|
||||
|
||||
uint256 duplicates = 0;
|
||||
for (uint256 i = 0; i < keys.length; ++i) {
|
||||
uint32 key = keys[i];
|
||||
uint224 value = values[i % values.length];
|
||||
if (i > 0 && key == keys[i - 1]) ++duplicates;
|
||||
|
||||
// push
|
||||
vm.roll(key);
|
||||
_ckpts.push(value);
|
||||
|
||||
// check length & latest
|
||||
assertEq(_ckpts.length(), i + 1 - duplicates);
|
||||
assertEq(_ckpts.latest(), value);
|
||||
_assertLatestCheckpoint(true, key, value);
|
||||
}
|
||||
|
||||
// Can't push any key in the past
|
||||
if (keys.length > 0) {
|
||||
uint32 lastKey = keys[keys.length - 1];
|
||||
if (lastKey > 0) {
|
||||
pastKey = _boundUint32(pastKey, 0, lastKey - 1);
|
||||
|
||||
vm.roll(pastKey);
|
||||
vm.expectRevert();
|
||||
this.push(values[keys.length % values.length]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// used to test reverts
|
||||
function push(uint224 value) external {
|
||||
_ckpts.push(value);
|
||||
}
|
||||
|
||||
function testLookup(uint32[] memory keys, uint224[] memory values, uint32 lookup) public {
|
||||
vm.assume(keys.length > 0);
|
||||
vm.assume(values.length > 0 && values.length <= keys.length);
|
||||
_prepareKeys(keys, _KEY_MAX_GAP);
|
||||
|
||||
uint32 lastKey = keys[keys.length - 1];
|
||||
vm.assume(lastKey > 0);
|
||||
lookup = _boundUint32(lookup, 0, lastKey - 1);
|
||||
|
||||
uint224 upper = 0;
|
||||
for (uint256 i = 0; i < keys.length; ++i) {
|
||||
uint32 key = keys[i];
|
||||
uint224 value = values[i % values.length];
|
||||
|
||||
// push
|
||||
vm.roll(key);
|
||||
_ckpts.push(value);
|
||||
|
||||
// track expected result of lookups
|
||||
if (key <= lookup) {
|
||||
upper = value;
|
||||
}
|
||||
}
|
||||
|
||||
// check lookup
|
||||
assertEq(_ckpts.getAtBlock(lookup), upper);
|
||||
assertEq(_ckpts.getAtProbablyRecentBlock(lookup), upper);
|
||||
|
||||
vm.expectRevert();
|
||||
this.getAtBlock(lastKey);
|
||||
vm.expectRevert();
|
||||
this.getAtBlock(lastKey + 1);
|
||||
vm.expectRevert();
|
||||
this.getAtProbablyRecentBlock(lastKey);
|
||||
vm.expectRevert();
|
||||
this.getAtProbablyRecentBlock(lastKey + 1);
|
||||
}
|
||||
|
||||
// used to test reverts
|
||||
function getAtBlock(uint32 key) external view {
|
||||
_ckpts.getAtBlock(key);
|
||||
}
|
||||
|
||||
// used to test reverts
|
||||
function getAtProbablyRecentBlock(uint32 key) external view {
|
||||
_ckpts.getAtProbablyRecentBlock(key);
|
||||
}
|
||||
}
|
||||
|
||||
contract CheckpointsTrace224Test is Test {
|
||||
using Checkpoints for Checkpoints.Trace224;
|
||||
|
||||
// Maximum gap between keys used during the fuzzing tests: the `_prepareKeys` function with make sure that
|
||||
// key#n+1 is in the [key#n, key#n + _KEY_MAX_GAP] range.
|
||||
uint8 internal constant _KEY_MAX_GAP = 64;
|
||||
|
||||
Checkpoints.Trace224 internal _ckpts;
|
||||
|
||||
// helpers
|
||||
function _boundUint32(uint32 x, uint32 min, uint32 max) internal view returns (uint32) {
|
||||
return SafeCast.toUint32(bound(uint256(x), uint256(min), uint256(max)));
|
||||
}
|
||||
|
||||
function _prepareKeys(uint32[] memory keys, uint32 maxSpread) internal view {
|
||||
uint32 lastKey = 0;
|
||||
for (uint256 i = 0; i < keys.length; ++i) {
|
||||
uint32 key = _boundUint32(keys[i], lastKey, lastKey + maxSpread);
|
||||
keys[i] = key;
|
||||
lastKey = key;
|
||||
}
|
||||
}
|
||||
|
||||
function _assertLatestCheckpoint(bool exist, uint32 key, uint224 value) internal {
|
||||
(bool _exist, uint32 _key, uint224 _value) = _ckpts.latestCheckpoint();
|
||||
assertEq(_exist, exist);
|
||||
assertEq(_key, key);
|
||||
assertEq(_value, value);
|
||||
}
|
||||
|
||||
// tests
|
||||
function testPush(uint32[] memory keys, uint224[] memory values, uint32 pastKey) public {
|
||||
vm.assume(values.length > 0 && values.length <= keys.length);
|
||||
_prepareKeys(keys, _KEY_MAX_GAP);
|
||||
|
||||
// initial state
|
||||
assertEq(_ckpts.length(), 0);
|
||||
assertEq(_ckpts.latest(), 0);
|
||||
_assertLatestCheckpoint(false, 0, 0);
|
||||
|
||||
uint256 duplicates = 0;
|
||||
for (uint256 i = 0; i < keys.length; ++i) {
|
||||
uint32 key = keys[i];
|
||||
uint224 value = values[i % values.length];
|
||||
if (i > 0 && key == keys[i - 1]) ++duplicates;
|
||||
|
||||
// push
|
||||
_ckpts.push(key, value);
|
||||
|
||||
// check length & latest
|
||||
assertEq(_ckpts.length(), i + 1 - duplicates);
|
||||
assertEq(_ckpts.latest(), value);
|
||||
_assertLatestCheckpoint(true, key, value);
|
||||
}
|
||||
|
||||
if (keys.length > 0) {
|
||||
uint32 lastKey = keys[keys.length - 1];
|
||||
if (lastKey > 0) {
|
||||
pastKey = _boundUint32(pastKey, 0, lastKey - 1);
|
||||
|
||||
vm.expectRevert();
|
||||
this.push(pastKey, values[keys.length % values.length]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// used to test reverts
|
||||
function push(uint32 key, uint224 value) external {
|
||||
_ckpts.push(key, value);
|
||||
}
|
||||
|
||||
function testLookup(uint32[] memory keys, uint224[] memory values, uint32 lookup) public {
|
||||
vm.assume(values.length > 0 && values.length <= keys.length);
|
||||
_prepareKeys(keys, _KEY_MAX_GAP);
|
||||
|
||||
uint32 lastKey = keys.length == 0 ? 0 : keys[keys.length - 1];
|
||||
lookup = _boundUint32(lookup, 0, lastKey + _KEY_MAX_GAP);
|
||||
|
||||
uint224 upper = 0;
|
||||
uint224 lower = 0;
|
||||
uint32 lowerKey = type(uint32).max;
|
||||
for (uint256 i = 0; i < keys.length; ++i) {
|
||||
uint32 key = keys[i];
|
||||
uint224 value = values[i % values.length];
|
||||
|
||||
// push
|
||||
_ckpts.push(key, value);
|
||||
|
||||
// track expected result of lookups
|
||||
if (key <= lookup) {
|
||||
upper = value;
|
||||
}
|
||||
// find the first key that is not smaller than the lookup key
|
||||
if (key >= lookup && (i == 0 || keys[i - 1] < lookup)) {
|
||||
lowerKey = key;
|
||||
}
|
||||
if (key == lowerKey) {
|
||||
lower = value;
|
||||
}
|
||||
}
|
||||
|
||||
// check lookup
|
||||
assertEq(_ckpts.lowerLookup(lookup), lower);
|
||||
assertEq(_ckpts.upperLookup(lookup), upper);
|
||||
assertEq(_ckpts.upperLookupRecent(lookup), upper);
|
||||
}
|
||||
}
|
||||
|
||||
contract CheckpointsTrace160Test is Test {
|
||||
using Checkpoints for Checkpoints.Trace160;
|
||||
|
||||
// Maximum gap between keys used during the fuzzing tests: the `_prepareKeys` function with make sure that
|
||||
// key#n+1 is in the [key#n, key#n + _KEY_MAX_GAP] range.
|
||||
uint8 internal constant _KEY_MAX_GAP = 64;
|
||||
|
||||
Checkpoints.Trace160 internal _ckpts;
|
||||
|
||||
// helpers
|
||||
function _boundUint96(uint96 x, uint96 min, uint96 max) internal view returns (uint96) {
|
||||
return SafeCast.toUint96(bound(uint256(x), uint256(min), uint256(max)));
|
||||
}
|
||||
|
||||
function _prepareKeys(uint96[] memory keys, uint96 maxSpread) internal view {
|
||||
uint96 lastKey = 0;
|
||||
for (uint256 i = 0; i < keys.length; ++i) {
|
||||
uint96 key = _boundUint96(keys[i], lastKey, lastKey + maxSpread);
|
||||
keys[i] = key;
|
||||
lastKey = key;
|
||||
}
|
||||
}
|
||||
|
||||
function _assertLatestCheckpoint(bool exist, uint96 key, uint160 value) internal {
|
||||
(bool _exist, uint96 _key, uint160 _value) = _ckpts.latestCheckpoint();
|
||||
assertEq(_exist, exist);
|
||||
assertEq(_key, key);
|
||||
assertEq(_value, value);
|
||||
}
|
||||
|
||||
// tests
|
||||
function testPush(uint96[] memory keys, uint160[] memory values, uint96 pastKey) public {
|
||||
vm.assume(values.length > 0 && values.length <= keys.length);
|
||||
_prepareKeys(keys, _KEY_MAX_GAP);
|
||||
|
||||
// initial state
|
||||
assertEq(_ckpts.length(), 0);
|
||||
assertEq(_ckpts.latest(), 0);
|
||||
_assertLatestCheckpoint(false, 0, 0);
|
||||
|
||||
uint256 duplicates = 0;
|
||||
for (uint256 i = 0; i < keys.length; ++i) {
|
||||
uint96 key = keys[i];
|
||||
uint160 value = values[i % values.length];
|
||||
if (i > 0 && key == keys[i - 1]) ++duplicates;
|
||||
|
||||
// push
|
||||
_ckpts.push(key, value);
|
||||
|
||||
// check length & latest
|
||||
assertEq(_ckpts.length(), i + 1 - duplicates);
|
||||
assertEq(_ckpts.latest(), value);
|
||||
_assertLatestCheckpoint(true, key, value);
|
||||
}
|
||||
|
||||
if (keys.length > 0) {
|
||||
uint96 lastKey = keys[keys.length - 1];
|
||||
if (lastKey > 0) {
|
||||
pastKey = _boundUint96(pastKey, 0, lastKey - 1);
|
||||
|
||||
vm.expectRevert();
|
||||
this.push(pastKey, values[keys.length % values.length]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// used to test reverts
|
||||
function push(uint96 key, uint160 value) external {
|
||||
_ckpts.push(key, value);
|
||||
}
|
||||
|
||||
function testLookup(uint96[] memory keys, uint160[] memory values, uint96 lookup) public {
|
||||
vm.assume(values.length > 0 && values.length <= keys.length);
|
||||
_prepareKeys(keys, _KEY_MAX_GAP);
|
||||
|
||||
uint96 lastKey = keys.length == 0 ? 0 : keys[keys.length - 1];
|
||||
lookup = _boundUint96(lookup, 0, lastKey + _KEY_MAX_GAP);
|
||||
|
||||
uint160 upper = 0;
|
||||
uint160 lower = 0;
|
||||
uint96 lowerKey = type(uint96).max;
|
||||
for (uint256 i = 0; i < keys.length; ++i) {
|
||||
uint96 key = keys[i];
|
||||
uint160 value = values[i % values.length];
|
||||
|
||||
// push
|
||||
_ckpts.push(key, value);
|
||||
|
||||
// track expected result of lookups
|
||||
if (key <= lookup) {
|
||||
upper = value;
|
||||
}
|
||||
// find the first key that is not smaller than the lookup key
|
||||
if (key >= lookup && (i == 0 || keys[i - 1] < lookup)) {
|
||||
lowerKey = key;
|
||||
}
|
||||
if (key == lowerKey) {
|
||||
lower = value;
|
||||
}
|
||||
}
|
||||
|
||||
// check lookup
|
||||
assertEq(_ckpts.lowerLookup(lookup), lower);
|
||||
assertEq(_ckpts.upperLookup(lookup), upper);
|
||||
assertEq(_ckpts.upperLookupRecent(lookup), upper);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,255 @@
|
||||
const { expectRevert, time } = require('@openzeppelin/test-helpers');
|
||||
|
||||
const { expect } = require('chai');
|
||||
|
||||
const { batchInBlock } = require('../helpers/txpool');
|
||||
|
||||
const $Checkpoints = artifacts.require('$Checkpoints');
|
||||
|
||||
// The library name may be 'Checkpoints' or 'CheckpointsUpgradeable'
|
||||
const libraryName = $Checkpoints._json.contractName.replace(/^\$/, '');
|
||||
|
||||
const traceLengths = [160, 224];
|
||||
|
||||
const first = array => (array.length ? array[0] : undefined);
|
||||
const last = array => (array.length ? array[array.length - 1] : undefined);
|
||||
|
||||
contract('Checkpoints', function () {
|
||||
beforeEach(async function () {
|
||||
this.mock = await $Checkpoints.new();
|
||||
|
||||
this.methods = { trace: {} };
|
||||
this.methods.history = {
|
||||
latest: (...args) => this.mock.methods[`$latest_${libraryName}_History(uint256)`](0, ...args),
|
||||
latestCheckpoint: (...args) => this.mock.methods[`$latestCheckpoint_${libraryName}_History(uint256)`](0, ...args),
|
||||
length: (...args) => this.mock.methods[`$length_${libraryName}_History(uint256)`](0, ...args),
|
||||
push: (...args) => this.mock.methods['$push(uint256,uint256)'](0, ...args),
|
||||
getAtBlock: (...args) => this.mock.$getAtBlock(0, ...args),
|
||||
getAtRecentBlock: (...args) => this.mock.$getAtProbablyRecentBlock(0, ...args),
|
||||
};
|
||||
for (const length of traceLengths) {
|
||||
this.methods.trace[length] = {
|
||||
latest: (...args) => this.mock.methods[`$latest_${libraryName}_Trace${length}(uint256)`](0, ...args),
|
||||
latestCheckpoint: (...args) =>
|
||||
this.mock.methods[`$latestCheckpoint_${libraryName}_Trace${length}(uint256)`](0, ...args),
|
||||
length: (...args) => this.mock.methods[`$length_${libraryName}_Trace${length}(uint256)`](0, ...args),
|
||||
push: (...args) => this.mock.methods[`$push(uint256,uint${256 - length},uint${length})`](0, ...args),
|
||||
lowerLookup: (...args) => this.mock.methods[`$lowerLookup(uint256,uint${256 - length})`](0, ...args),
|
||||
upperLookup: (...args) => this.mock.methods[`$upperLookup(uint256,uint${256 - length})`](0, ...args),
|
||||
upperLookupRecent: (...args) =>
|
||||
this.mock.methods[`$upperLookupRecent(uint256,uint${256 - length})`](0, ...args),
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
describe('History checkpoints', function () {
|
||||
describe('without checkpoints', function () {
|
||||
it('returns zero as latest value', async function () {
|
||||
expect(await this.methods.history.latest()).to.be.bignumber.equal('0');
|
||||
|
||||
const ckpt = await this.methods.history.latestCheckpoint();
|
||||
expect(ckpt[0]).to.be.equal(false);
|
||||
expect(ckpt[1]).to.be.bignumber.equal('0');
|
||||
expect(ckpt[2]).to.be.bignumber.equal('0');
|
||||
});
|
||||
|
||||
it('returns zero as past value', async function () {
|
||||
await time.advanceBlock();
|
||||
expect(await this.methods.history.getAtBlock((await web3.eth.getBlockNumber()) - 1)).to.be.bignumber.equal('0');
|
||||
expect(
|
||||
await this.methods.history.getAtRecentBlock((await web3.eth.getBlockNumber()) - 1),
|
||||
).to.be.bignumber.equal('0');
|
||||
});
|
||||
});
|
||||
|
||||
describe('with checkpoints', function () {
|
||||
beforeEach('pushing checkpoints', async function () {
|
||||
this.tx1 = await this.methods.history.push(1);
|
||||
this.tx2 = await this.methods.history.push(2);
|
||||
await time.advanceBlock();
|
||||
this.tx3 = await this.methods.history.push(3);
|
||||
await time.advanceBlock();
|
||||
await time.advanceBlock();
|
||||
});
|
||||
|
||||
it('returns latest value', async function () {
|
||||
expect(await this.methods.history.latest()).to.be.bignumber.equal('3');
|
||||
|
||||
const ckpt = await this.methods.history.latestCheckpoint();
|
||||
expect(ckpt[0]).to.be.equal(true);
|
||||
expect(ckpt[1]).to.be.bignumber.equal(web3.utils.toBN(this.tx3.receipt.blockNumber));
|
||||
expect(ckpt[2]).to.be.bignumber.equal(web3.utils.toBN('3'));
|
||||
});
|
||||
|
||||
for (const getAtBlockVariant of ['getAtBlock', 'getAtRecentBlock']) {
|
||||
describe(`lookup: ${getAtBlockVariant}`, function () {
|
||||
it('returns past values', async function () {
|
||||
expect(
|
||||
await this.methods.history[getAtBlockVariant](this.tx1.receipt.blockNumber - 1),
|
||||
).to.be.bignumber.equal('0');
|
||||
expect(await this.methods.history[getAtBlockVariant](this.tx1.receipt.blockNumber)).to.be.bignumber.equal(
|
||||
'1',
|
||||
);
|
||||
expect(await this.methods.history[getAtBlockVariant](this.tx2.receipt.blockNumber)).to.be.bignumber.equal(
|
||||
'2',
|
||||
);
|
||||
// Block with no new checkpoints
|
||||
expect(
|
||||
await this.methods.history[getAtBlockVariant](this.tx2.receipt.blockNumber + 1),
|
||||
).to.be.bignumber.equal('2');
|
||||
expect(await this.methods.history[getAtBlockVariant](this.tx3.receipt.blockNumber)).to.be.bignumber.equal(
|
||||
'3',
|
||||
);
|
||||
expect(
|
||||
await this.methods.history[getAtBlockVariant](this.tx3.receipt.blockNumber + 1),
|
||||
).to.be.bignumber.equal('3');
|
||||
});
|
||||
it('reverts if block number >= current block', async function () {
|
||||
await expectRevert(
|
||||
this.methods.history[getAtBlockVariant](await web3.eth.getBlockNumber()),
|
||||
'Checkpoints: block not yet mined',
|
||||
);
|
||||
|
||||
await expectRevert(
|
||||
this.methods.history[getAtBlockVariant]((await web3.eth.getBlockNumber()) + 1),
|
||||
'Checkpoints: block not yet mined',
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
it('multiple checkpoints in the same block', async function () {
|
||||
const lengthBefore = await this.methods.history.length();
|
||||
|
||||
await batchInBlock([
|
||||
() => this.methods.history.push(8, { gas: 100000 }),
|
||||
() => this.methods.history.push(9, { gas: 100000 }),
|
||||
() => this.methods.history.push(10, { gas: 100000 }),
|
||||
]);
|
||||
|
||||
expect(await this.methods.history.length()).to.be.bignumber.equal(lengthBefore.addn(1));
|
||||
expect(await this.methods.history.latest()).to.be.bignumber.equal('10');
|
||||
});
|
||||
|
||||
it('more than 5 checkpoints', async function () {
|
||||
for (let i = 4; i <= 6; i++) {
|
||||
await this.methods.history.push(i);
|
||||
}
|
||||
expect(await this.methods.history.length()).to.be.bignumber.equal('6');
|
||||
const block = await web3.eth.getBlockNumber();
|
||||
// recent
|
||||
expect(await this.methods.history.getAtRecentBlock(block - 1)).to.be.bignumber.equal('5');
|
||||
// non-recent
|
||||
expect(await this.methods.history.getAtRecentBlock(block - 9)).to.be.bignumber.equal('0');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
for (const length of traceLengths) {
|
||||
describe(`Trace${length}`, function () {
|
||||
describe('without checkpoints', function () {
|
||||
it('returns zero as latest value', async function () {
|
||||
expect(await this.methods.trace[length].latest()).to.be.bignumber.equal('0');
|
||||
|
||||
const ckpt = await this.methods.trace[length].latestCheckpoint();
|
||||
expect(ckpt[0]).to.be.equal(false);
|
||||
expect(ckpt[1]).to.be.bignumber.equal('0');
|
||||
expect(ckpt[2]).to.be.bignumber.equal('0');
|
||||
});
|
||||
|
||||
it('lookup returns 0', async function () {
|
||||
expect(await this.methods.trace[length].lowerLookup(0)).to.be.bignumber.equal('0');
|
||||
expect(await this.methods.trace[length].upperLookup(0)).to.be.bignumber.equal('0');
|
||||
expect(await this.methods.trace[length].upperLookupRecent(0)).to.be.bignumber.equal('0');
|
||||
});
|
||||
});
|
||||
|
||||
describe('with checkpoints', function () {
|
||||
beforeEach('pushing checkpoints', async function () {
|
||||
this.checkpoints = [
|
||||
{ key: '2', value: '17' },
|
||||
{ key: '3', value: '42' },
|
||||
{ key: '5', value: '101' },
|
||||
{ key: '7', value: '23' },
|
||||
{ key: '11', value: '99' },
|
||||
];
|
||||
for (const { key, value } of this.checkpoints) {
|
||||
await this.methods.trace[length].push(key, value);
|
||||
}
|
||||
});
|
||||
|
||||
it('length', async function () {
|
||||
expect(await this.methods.trace[length].length()).to.be.bignumber.equal(this.checkpoints.length.toString());
|
||||
});
|
||||
|
||||
it('returns latest value', async function () {
|
||||
expect(await this.methods.trace[length].latest()).to.be.bignumber.equal(last(this.checkpoints).value);
|
||||
|
||||
const ckpt = await this.methods.trace[length].latestCheckpoint();
|
||||
expect(ckpt[0]).to.be.equal(true);
|
||||
expect(ckpt[1]).to.be.bignumber.equal(last(this.checkpoints).key);
|
||||
expect(ckpt[2]).to.be.bignumber.equal(last(this.checkpoints).value);
|
||||
});
|
||||
|
||||
it('cannot push values in the past', async function () {
|
||||
await expectRevert(
|
||||
this.methods.trace[length].push(last(this.checkpoints).key - 1, '0'),
|
||||
'Checkpoint: decreasing keys',
|
||||
);
|
||||
});
|
||||
|
||||
it('can update last value', async function () {
|
||||
const newValue = '42';
|
||||
|
||||
// check length before the update
|
||||
expect(await this.methods.trace[length].length()).to.be.bignumber.equal(this.checkpoints.length.toString());
|
||||
|
||||
// update last key
|
||||
await this.methods.trace[length].push(last(this.checkpoints).key, newValue);
|
||||
expect(await this.methods.trace[length].latest()).to.be.bignumber.equal(newValue);
|
||||
|
||||
// check that length did not change
|
||||
expect(await this.methods.trace[length].length()).to.be.bignumber.equal(this.checkpoints.length.toString());
|
||||
});
|
||||
|
||||
it('lower lookup', async function () {
|
||||
for (let i = 0; i < 14; ++i) {
|
||||
const value = first(this.checkpoints.filter(x => i <= x.key))?.value || '0';
|
||||
|
||||
expect(await this.methods.trace[length].lowerLookup(i)).to.be.bignumber.equal(value);
|
||||
}
|
||||
});
|
||||
|
||||
it('upper lookup & upperLookupRecent', async function () {
|
||||
for (let i = 0; i < 14; ++i) {
|
||||
const value = last(this.checkpoints.filter(x => i >= x.key))?.value || '0';
|
||||
|
||||
expect(await this.methods.trace[length].upperLookup(i)).to.be.bignumber.equal(value);
|
||||
expect(await this.methods.trace[length].upperLookupRecent(i)).to.be.bignumber.equal(value);
|
||||
}
|
||||
});
|
||||
|
||||
it('upperLookupRecent with more than 5 checkpoints', async function () {
|
||||
const moreCheckpoints = [
|
||||
{ key: '12', value: '22' },
|
||||
{ key: '13', value: '131' },
|
||||
{ key: '17', value: '45' },
|
||||
{ key: '19', value: '31452' },
|
||||
{ key: '21', value: '0' },
|
||||
];
|
||||
const allCheckpoints = [].concat(this.checkpoints, moreCheckpoints);
|
||||
|
||||
for (const { key, value } of moreCheckpoints) {
|
||||
await this.methods.trace[length].push(key, value);
|
||||
}
|
||||
|
||||
for (let i = 0; i < 25; ++i) {
|
||||
const value = last(allCheckpoints.filter(x => i >= x.key))?.value || '0';
|
||||
expect(await this.methods.trace[length].upperLookup(i)).to.be.bignumber.equal(value);
|
||||
expect(await this.methods.trace[length].upperLookupRecent(i)).to.be.bignumber.equal(value);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,42 @@
|
||||
const { BN, expectEvent } = require('@openzeppelin/test-helpers');
|
||||
|
||||
const ContextMock = artifacts.require('ContextMock');
|
||||
|
||||
function shouldBehaveLikeRegularContext(sender) {
|
||||
describe('msgSender', function () {
|
||||
it('returns the transaction sender when called from an EOA', async function () {
|
||||
const receipt = await this.context.msgSender({ from: sender });
|
||||
expectEvent(receipt, 'Sender', { sender });
|
||||
});
|
||||
|
||||
it('returns the transaction sender when from another contract', async function () {
|
||||
const { tx } = await this.caller.callSender(this.context.address, { from: sender });
|
||||
await expectEvent.inTransaction(tx, ContextMock, 'Sender', { sender: this.caller.address });
|
||||
});
|
||||
});
|
||||
|
||||
describe('msgData', function () {
|
||||
const integerValue = new BN('42');
|
||||
const stringValue = 'OpenZeppelin';
|
||||
|
||||
let callData;
|
||||
|
||||
beforeEach(async function () {
|
||||
callData = this.context.contract.methods.msgData(integerValue.toString(), stringValue).encodeABI();
|
||||
});
|
||||
|
||||
it('returns the transaction data when called from an EOA', async function () {
|
||||
const receipt = await this.context.msgData(integerValue, stringValue);
|
||||
expectEvent(receipt, 'Data', { data: callData, integerValue, stringValue });
|
||||
});
|
||||
|
||||
it('returns the transaction sender when from another contract', async function () {
|
||||
const { tx } = await this.caller.callData(this.context.address, integerValue, stringValue);
|
||||
await expectEvent.inTransaction(tx, ContextMock, 'Data', { data: callData, integerValue, stringValue });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
shouldBehaveLikeRegularContext,
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
require('@openzeppelin/test-helpers');
|
||||
|
||||
const ContextMock = artifacts.require('ContextMock');
|
||||
const ContextMockCaller = artifacts.require('ContextMockCaller');
|
||||
|
||||
const { shouldBehaveLikeRegularContext } = require('./Context.behavior');
|
||||
|
||||
contract('Context', function (accounts) {
|
||||
const [sender] = accounts;
|
||||
|
||||
beforeEach(async function () {
|
||||
this.context = await ContextMock.new();
|
||||
this.caller = await ContextMockCaller.new();
|
||||
});
|
||||
|
||||
shouldBehaveLikeRegularContext(sender);
|
||||
});
|
||||
@@ -0,0 +1,84 @@
|
||||
const { expectRevert } = require('@openzeppelin/test-helpers');
|
||||
|
||||
const { expect } = require('chai');
|
||||
|
||||
const Counters = artifacts.require('$Counters');
|
||||
|
||||
contract('Counters', function () {
|
||||
beforeEach(async function () {
|
||||
this.counter = await Counters.new();
|
||||
});
|
||||
|
||||
it('starts at zero', async function () {
|
||||
expect(await this.counter.$current(0)).to.be.bignumber.equal('0');
|
||||
});
|
||||
|
||||
describe('increment', function () {
|
||||
context('starting from 0', function () {
|
||||
it('increments the current value by one', async function () {
|
||||
await this.counter.$increment(0);
|
||||
expect(await this.counter.$current(0)).to.be.bignumber.equal('1');
|
||||
});
|
||||
|
||||
it('can be called multiple times', async function () {
|
||||
await this.counter.$increment(0);
|
||||
await this.counter.$increment(0);
|
||||
await this.counter.$increment(0);
|
||||
|
||||
expect(await this.counter.$current(0)).to.be.bignumber.equal('3');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('decrement', function () {
|
||||
beforeEach(async function () {
|
||||
await this.counter.$increment(0);
|
||||
expect(await this.counter.$current(0)).to.be.bignumber.equal('1');
|
||||
});
|
||||
context('starting from 1', function () {
|
||||
it('decrements the current value by one', async function () {
|
||||
await this.counter.$decrement(0);
|
||||
expect(await this.counter.$current(0)).to.be.bignumber.equal('0');
|
||||
});
|
||||
|
||||
it('reverts if the current value is 0', async function () {
|
||||
await this.counter.$decrement(0);
|
||||
await expectRevert(this.counter.$decrement(0), 'Counter: decrement overflow');
|
||||
});
|
||||
});
|
||||
context('after incremented to 3', function () {
|
||||
it('can be called multiple times', async function () {
|
||||
await this.counter.$increment(0);
|
||||
await this.counter.$increment(0);
|
||||
|
||||
expect(await this.counter.$current(0)).to.be.bignumber.equal('3');
|
||||
|
||||
await this.counter.$decrement(0);
|
||||
await this.counter.$decrement(0);
|
||||
await this.counter.$decrement(0);
|
||||
|
||||
expect(await this.counter.$current(0)).to.be.bignumber.equal('0');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('reset', function () {
|
||||
context('null counter', function () {
|
||||
it('does not throw', async function () {
|
||||
await this.counter.$reset(0);
|
||||
expect(await this.counter.$current(0)).to.be.bignumber.equal('0');
|
||||
});
|
||||
});
|
||||
|
||||
context('non null counter', function () {
|
||||
beforeEach(async function () {
|
||||
await this.counter.$increment(0);
|
||||
expect(await this.counter.$current(0)).to.be.bignumber.equal('1');
|
||||
});
|
||||
it('reset to 0', async function () {
|
||||
await this.counter.$reset(0);
|
||||
expect(await this.counter.$current(0)).to.be.bignumber.equal('0');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,89 @@
|
||||
const { balance, ether, expectEvent, expectRevert, send } = require('@openzeppelin/test-helpers');
|
||||
const { computeCreate2Address } = require('../helpers/create2');
|
||||
const { expect } = require('chai');
|
||||
|
||||
const Create2 = artifacts.require('$Create2');
|
||||
const VestingWallet = artifacts.require('VestingWallet');
|
||||
const ERC1820Implementer = artifacts.require('$ERC1820Implementer');
|
||||
|
||||
contract('Create2', function (accounts) {
|
||||
const [deployerAccount, other] = accounts;
|
||||
|
||||
const salt = 'salt message';
|
||||
const saltHex = web3.utils.soliditySha3(salt);
|
||||
|
||||
const encodedParams = web3.eth.abi.encodeParameters(['address', 'uint64', 'uint64'], [other, 0, 0]).slice(2);
|
||||
|
||||
const constructorByteCode = `${VestingWallet.bytecode}${encodedParams}`;
|
||||
|
||||
beforeEach(async function () {
|
||||
this.factory = await Create2.new();
|
||||
});
|
||||
describe('computeAddress', function () {
|
||||
it('computes the correct contract address', async function () {
|
||||
const onChainComputed = await this.factory.$computeAddress(saltHex, web3.utils.keccak256(constructorByteCode));
|
||||
const offChainComputed = computeCreate2Address(saltHex, constructorByteCode, this.factory.address);
|
||||
expect(onChainComputed).to.equal(offChainComputed);
|
||||
});
|
||||
|
||||
it('computes the correct contract address with deployer', async function () {
|
||||
const onChainComputed = await this.factory.$computeAddress(
|
||||
saltHex,
|
||||
web3.utils.keccak256(constructorByteCode),
|
||||
deployerAccount,
|
||||
);
|
||||
const offChainComputed = computeCreate2Address(saltHex, constructorByteCode, deployerAccount);
|
||||
expect(onChainComputed).to.equal(offChainComputed);
|
||||
});
|
||||
});
|
||||
|
||||
describe('deploy', function () {
|
||||
it('deploys a ERC1820Implementer from inline assembly code', async function () {
|
||||
const offChainComputed = computeCreate2Address(saltHex, ERC1820Implementer.bytecode, this.factory.address);
|
||||
|
||||
expectEvent(await this.factory.$deploy(0, saltHex, ERC1820Implementer.bytecode), 'return$deploy', {
|
||||
addr: offChainComputed,
|
||||
});
|
||||
|
||||
expect(ERC1820Implementer.bytecode).to.include((await web3.eth.getCode(offChainComputed)).slice(2));
|
||||
});
|
||||
|
||||
it('deploys a contract with constructor arguments', async function () {
|
||||
const offChainComputed = computeCreate2Address(saltHex, constructorByteCode, this.factory.address);
|
||||
|
||||
expectEvent(await this.factory.$deploy(0, saltHex, constructorByteCode), 'return$deploy', {
|
||||
addr: offChainComputed,
|
||||
});
|
||||
|
||||
expect(await VestingWallet.at(offChainComputed).then(instance => instance.beneficiary())).to.be.equal(other);
|
||||
});
|
||||
|
||||
it('deploys a contract with funds deposited in the factory', async function () {
|
||||
const deposit = ether('2');
|
||||
await send.ether(deployerAccount, this.factory.address, deposit);
|
||||
expect(await balance.current(this.factory.address)).to.be.bignumber.equal(deposit);
|
||||
|
||||
const offChainComputed = computeCreate2Address(saltHex, constructorByteCode, this.factory.address);
|
||||
|
||||
expectEvent(await this.factory.$deploy(deposit, saltHex, constructorByteCode), 'return$deploy', {
|
||||
addr: offChainComputed,
|
||||
});
|
||||
|
||||
expect(await balance.current(offChainComputed)).to.be.bignumber.equal(deposit);
|
||||
});
|
||||
|
||||
it('fails deploying a contract in an existent address', async function () {
|
||||
expectEvent(await this.factory.$deploy(0, saltHex, constructorByteCode), 'return$deploy');
|
||||
|
||||
await expectRevert(this.factory.$deploy(0, saltHex, constructorByteCode), 'Create2: Failed on deploy');
|
||||
});
|
||||
|
||||
it('fails deploying a contract if the bytecode length is zero', async function () {
|
||||
await expectRevert(this.factory.$deploy(0, saltHex, '0x'), 'Create2: bytecode length is zero');
|
||||
});
|
||||
|
||||
it('fails deploying a contract if factory contract does not have sufficient balance', async function () {
|
||||
await expectRevert(this.factory.$deploy(1, saltHex, constructorByteCode), 'Create2: insufficient balance');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,68 @@
|
||||
const { BN, expectRevert } = require('@openzeppelin/test-helpers');
|
||||
|
||||
const ERC20MulticallMock = artifacts.require('$ERC20MulticallMock');
|
||||
|
||||
contract('Multicall', function (accounts) {
|
||||
const [deployer, alice, bob] = accounts;
|
||||
const amount = 12000;
|
||||
|
||||
beforeEach(async function () {
|
||||
this.multicallToken = await ERC20MulticallMock.new('name', 'symbol');
|
||||
await this.multicallToken.$_mint(deployer, amount);
|
||||
});
|
||||
|
||||
it('batches function calls', async function () {
|
||||
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.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.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('returns an array with the result of each call', async function () {
|
||||
const MulticallTest = artifacts.require('MulticallTest');
|
||||
const multicallTest = await MulticallTest.new({ from: deployer });
|
||||
await this.multicallToken.transfer(multicallTest.address, amount, { from: deployer });
|
||||
expect(await this.multicallToken.balanceOf(multicallTest.address)).to.be.bignumber.equal(new BN(amount));
|
||||
|
||||
const recipients = [alice, bob];
|
||||
const amounts = [amount / 2, amount / 3].map(n => new BN(n));
|
||||
|
||||
await multicallTest.checkReturnValues(this.multicallToken.address, recipients, amounts);
|
||||
});
|
||||
|
||||
it('reverts previous calls', async function () {
|
||||
expect(await this.multicallToken.balanceOf(alice)).to.be.bignumber.equal(new BN('0'));
|
||||
|
||||
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');
|
||||
expect(await this.multicallToken.balanceOf(alice)).to.be.bignumber.equal(new BN('0'));
|
||||
});
|
||||
|
||||
it('bubbles up revert reasons', async function () {
|
||||
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');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,55 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import "forge-std/Test.sol";
|
||||
|
||||
import "../../contracts/utils/ShortStrings.sol";
|
||||
|
||||
contract ShortStringsTest is Test {
|
||||
string _fallback;
|
||||
|
||||
function testRoundtripShort(string memory input) external {
|
||||
vm.assume(_isShort(input));
|
||||
ShortString short = ShortStrings.toShortString(input);
|
||||
string memory output = ShortStrings.toString(short);
|
||||
assertEq(input, output);
|
||||
}
|
||||
|
||||
function testRoundtripWithFallback(string memory input, string memory fallbackInitial) external {
|
||||
_fallback = fallbackInitial; // Make sure that the initial value has no effect
|
||||
ShortString short = ShortStrings.toShortStringWithFallback(input, _fallback);
|
||||
string memory output = ShortStrings.toStringWithFallback(short, _fallback);
|
||||
assertEq(input, output);
|
||||
}
|
||||
|
||||
function testRevertLong(string memory input) external {
|
||||
vm.assume(!_isShort(input));
|
||||
vm.expectRevert(abi.encodeWithSelector(ShortStrings.StringTooLong.selector, input));
|
||||
this.toShortString(input);
|
||||
}
|
||||
|
||||
function testLengthShort(string memory input) external {
|
||||
vm.assume(_isShort(input));
|
||||
uint256 inputLength = bytes(input).length;
|
||||
ShortString short = ShortStrings.toShortString(input);
|
||||
uint256 shortLength = ShortStrings.byteLength(short);
|
||||
assertEq(inputLength, shortLength);
|
||||
}
|
||||
|
||||
function testLengthWithFallback(string memory input, string memory fallbackInitial) external {
|
||||
_fallback = fallbackInitial;
|
||||
uint256 inputLength = bytes(input).length;
|
||||
ShortString short = ShortStrings.toShortStringWithFallback(input, _fallback);
|
||||
uint256 shortLength = ShortStrings.byteLengthWithFallback(short, _fallback);
|
||||
assertEq(inputLength, shortLength);
|
||||
}
|
||||
|
||||
function toShortString(string memory input) external pure returns (ShortString) {
|
||||
return ShortStrings.toShortString(input);
|
||||
}
|
||||
|
||||
function _isShort(string memory input) internal pure returns (bool) {
|
||||
return bytes(input).length < 32;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
const { expect } = require('chai');
|
||||
const { expectRevertCustomError } = require('../helpers/customError');
|
||||
|
||||
const ShortStrings = artifacts.require('$ShortStrings');
|
||||
|
||||
function length(sstr) {
|
||||
return parseInt(sstr.slice(64), 16);
|
||||
}
|
||||
|
||||
function decode(sstr) {
|
||||
return web3.utils.toUtf8(sstr).slice(0, length(sstr));
|
||||
}
|
||||
|
||||
contract('ShortStrings', function () {
|
||||
before(async function () {
|
||||
this.mock = await ShortStrings.new();
|
||||
});
|
||||
|
||||
for (const str of [0, 1, 16, 31, 32, 64, 1024].map(length => 'a'.repeat(length))) {
|
||||
describe(`with string length ${str.length}`, function () {
|
||||
it('encode / decode', async function () {
|
||||
if (str.length < 32) {
|
||||
const encoded = await this.mock.$toShortString(str);
|
||||
expect(decode(encoded)).to.be.equal(str);
|
||||
|
||||
const length = await this.mock.$byteLength(encoded);
|
||||
expect(length.toNumber()).to.be.equal(str.length);
|
||||
|
||||
const decoded = await this.mock.$toString(encoded);
|
||||
expect(decoded).to.be.equal(str);
|
||||
} else {
|
||||
await expectRevertCustomError(this.mock.$toShortString(str), `StringTooLong("${str}")`);
|
||||
}
|
||||
});
|
||||
|
||||
it('set / get with fallback', async function () {
|
||||
const { logs } = await this.mock.$toShortStringWithFallback(str, 0);
|
||||
const { ret0 } = logs.find(({ event }) => event == 'return$toShortStringWithFallback').args;
|
||||
|
||||
const promise = this.mock.$toString(ret0);
|
||||
if (str.length < 32) {
|
||||
expect(await promise).to.be.equal(str);
|
||||
} else {
|
||||
await expectRevertCustomError(promise, 'InvalidShortString()');
|
||||
}
|
||||
|
||||
const length = await this.mock.$byteLengthWithFallback(ret0, 0);
|
||||
expect(length.toNumber()).to.be.equal(str.length);
|
||||
|
||||
const recovered = await this.mock.$toStringWithFallback(ret0, 0);
|
||||
expect(recovered).to.be.equal(str);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,210 @@
|
||||
const { constants, BN } = require('@openzeppelin/test-helpers');
|
||||
|
||||
const { expect } = require('chai');
|
||||
|
||||
const StorageSlotMock = artifacts.require('StorageSlotMock');
|
||||
|
||||
const slot = web3.utils.keccak256('some.storage.slot');
|
||||
const otherSlot = web3.utils.keccak256('some.other.storage.slot');
|
||||
|
||||
contract('StorageSlot', function (accounts) {
|
||||
beforeEach(async function () {
|
||||
this.store = await StorageSlotMock.new();
|
||||
});
|
||||
|
||||
describe('boolean storage slot', function () {
|
||||
beforeEach(async function () {
|
||||
this.value = true;
|
||||
});
|
||||
|
||||
it('set', async function () {
|
||||
await this.store.setBoolean(slot, this.value);
|
||||
});
|
||||
|
||||
describe('get', function () {
|
||||
beforeEach(async function () {
|
||||
await this.store.setBoolean(slot, this.value);
|
||||
});
|
||||
|
||||
it('from right slot', async function () {
|
||||
expect(await this.store.getBoolean(slot)).to.be.equal(this.value);
|
||||
});
|
||||
|
||||
it('from other slot', async function () {
|
||||
expect(await this.store.getBoolean(otherSlot)).to.be.equal(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('address storage slot', function () {
|
||||
beforeEach(async function () {
|
||||
this.value = accounts[1];
|
||||
});
|
||||
|
||||
it('set', async function () {
|
||||
await this.store.setAddress(slot, this.value);
|
||||
});
|
||||
|
||||
describe('get', function () {
|
||||
beforeEach(async function () {
|
||||
await this.store.setAddress(slot, this.value);
|
||||
});
|
||||
|
||||
it('from right slot', async function () {
|
||||
expect(await this.store.getAddress(slot)).to.be.equal(this.value);
|
||||
});
|
||||
|
||||
it('from other slot', async function () {
|
||||
expect(await this.store.getAddress(otherSlot)).to.be.equal(constants.ZERO_ADDRESS);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('bytes32 storage slot', function () {
|
||||
beforeEach(async function () {
|
||||
this.value = web3.utils.keccak256('some byte32 value');
|
||||
});
|
||||
|
||||
it('set', async function () {
|
||||
await this.store.setBytes32(slot, this.value);
|
||||
});
|
||||
|
||||
describe('get', function () {
|
||||
beforeEach(async function () {
|
||||
await this.store.setBytes32(slot, this.value);
|
||||
});
|
||||
|
||||
it('from right slot', async function () {
|
||||
expect(await this.store.getBytes32(slot)).to.be.equal(this.value);
|
||||
});
|
||||
|
||||
it('from other slot', async function () {
|
||||
expect(await this.store.getBytes32(otherSlot)).to.be.equal(constants.ZERO_BYTES32);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('uint256 storage slot', function () {
|
||||
beforeEach(async function () {
|
||||
this.value = new BN(1742);
|
||||
});
|
||||
|
||||
it('set', async function () {
|
||||
await this.store.setUint256(slot, this.value);
|
||||
});
|
||||
|
||||
describe('get', function () {
|
||||
beforeEach(async function () {
|
||||
await this.store.setUint256(slot, this.value);
|
||||
});
|
||||
|
||||
it('from right slot', async function () {
|
||||
expect(await this.store.getUint256(slot)).to.be.bignumber.equal(this.value);
|
||||
});
|
||||
|
||||
it('from other slot', async function () {
|
||||
expect(await this.store.getUint256(otherSlot)).to.be.bignumber.equal('0');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('string storage slot', function () {
|
||||
beforeEach(async function () {
|
||||
this.value = 'lorem ipsum';
|
||||
});
|
||||
|
||||
it('set', async function () {
|
||||
await this.store.setString(slot, this.value);
|
||||
});
|
||||
|
||||
describe('get', function () {
|
||||
beforeEach(async function () {
|
||||
await this.store.setString(slot, this.value);
|
||||
});
|
||||
|
||||
it('from right slot', async function () {
|
||||
expect(await this.store.getString(slot)).to.be.equal(this.value);
|
||||
});
|
||||
|
||||
it('from other slot', async function () {
|
||||
expect(await this.store.getString(otherSlot)).to.be.equal('');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('string storage pointer', function () {
|
||||
beforeEach(async function () {
|
||||
this.value = 'lorem ipsum';
|
||||
});
|
||||
|
||||
it('set', async function () {
|
||||
await this.store.setStringStorage(slot, this.value);
|
||||
});
|
||||
|
||||
describe('get', function () {
|
||||
beforeEach(async function () {
|
||||
await this.store.setStringStorage(slot, this.value);
|
||||
});
|
||||
|
||||
it('from right slot', async function () {
|
||||
expect(await this.store.stringMap(slot)).to.be.equal(this.value);
|
||||
expect(await this.store.getStringStorage(slot)).to.be.equal(this.value);
|
||||
});
|
||||
|
||||
it('from other slot', async function () {
|
||||
expect(await this.store.stringMap(otherSlot)).to.be.equal('');
|
||||
expect(await this.store.getStringStorage(otherSlot)).to.be.equal('');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('bytes storage slot', function () {
|
||||
beforeEach(async function () {
|
||||
this.value = web3.utils.randomHex(128);
|
||||
});
|
||||
|
||||
it('set', async function () {
|
||||
await this.store.setBytes(slot, this.value);
|
||||
});
|
||||
|
||||
describe('get', function () {
|
||||
beforeEach(async function () {
|
||||
await this.store.setBytes(slot, this.value);
|
||||
});
|
||||
|
||||
it('from right slot', async function () {
|
||||
expect(await this.store.getBytes(slot)).to.be.equal(this.value);
|
||||
});
|
||||
|
||||
it('from other slot', async function () {
|
||||
expect(await this.store.getBytes(otherSlot)).to.be.equal(null);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('bytes storage pointer', function () {
|
||||
beforeEach(async function () {
|
||||
this.value = web3.utils.randomHex(128);
|
||||
});
|
||||
|
||||
it('set', async function () {
|
||||
await this.store.setBytesStorage(slot, this.value);
|
||||
});
|
||||
|
||||
describe('get', function () {
|
||||
beforeEach(async function () {
|
||||
await this.store.setBytesStorage(slot, this.value);
|
||||
});
|
||||
|
||||
it('from right slot', async function () {
|
||||
expect(await this.store.bytesMap(slot)).to.be.equal(this.value);
|
||||
expect(await this.store.getBytesStorage(slot)).to.be.equal(this.value);
|
||||
});
|
||||
|
||||
it('from other slot', async function () {
|
||||
expect(await this.store.bytesMap(otherSlot)).to.be.equal(null);
|
||||
expect(await this.store.getBytesStorage(otherSlot)).to.be.equal(null);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,150 @@
|
||||
const { BN, constants, expectRevert } = require('@openzeppelin/test-helpers');
|
||||
|
||||
const { expect } = require('chai');
|
||||
|
||||
const Strings = artifacts.require('$Strings');
|
||||
|
||||
contract('Strings', function () {
|
||||
before(async function () {
|
||||
this.strings = await Strings.new();
|
||||
});
|
||||
|
||||
describe('toString', function () {
|
||||
const values = [
|
||||
'0',
|
||||
'7',
|
||||
'10',
|
||||
'99',
|
||||
'100',
|
||||
'101',
|
||||
'123',
|
||||
'4132',
|
||||
'12345',
|
||||
'1234567',
|
||||
'1234567890',
|
||||
'123456789012345',
|
||||
'12345678901234567890',
|
||||
'123456789012345678901234567890',
|
||||
'1234567890123456789012345678901234567890',
|
||||
'12345678901234567890123456789012345678901234567890',
|
||||
'123456789012345678901234567890123456789012345678901234567890',
|
||||
'1234567890123456789012345678901234567890123456789012345678901234567890',
|
||||
];
|
||||
|
||||
describe('uint256', function () {
|
||||
it('converts MAX_UINT256', async function () {
|
||||
const value = constants.MAX_UINT256;
|
||||
expect(await this.strings.methods['$toString(uint256)'](value)).to.equal(value.toString(10));
|
||||
});
|
||||
|
||||
for (const value of values) {
|
||||
it(`converts ${value}`, async function () {
|
||||
expect(await this.strings.methods['$toString(uint256)'](value)).to.equal(value);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe('int256', function () {
|
||||
it('converts MAX_INT256', async function () {
|
||||
const value = constants.MAX_INT256;
|
||||
expect(await this.strings.methods['$toString(int256)'](value)).to.equal(value.toString(10));
|
||||
});
|
||||
|
||||
it('converts MIN_INT256', async function () {
|
||||
const value = constants.MIN_INT256;
|
||||
expect(await this.strings.methods['$toString(int256)'](value)).to.equal(value.toString(10));
|
||||
});
|
||||
|
||||
for (const value of values) {
|
||||
it(`convert ${value}`, async function () {
|
||||
expect(await this.strings.methods['$toString(int256)'](value)).to.equal(value);
|
||||
});
|
||||
|
||||
it(`convert negative ${value}`, async function () {
|
||||
const negated = new BN(value).neg();
|
||||
expect(await this.strings.methods['$toString(int256)'](negated)).to.equal(negated.toString(10));
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('toHexString', function () {
|
||||
it('converts 0', async function () {
|
||||
expect(await this.strings.methods['$toHexString(uint256)'](0)).to.equal('0x00');
|
||||
});
|
||||
|
||||
it('converts a positive number', async function () {
|
||||
expect(await this.strings.methods['$toHexString(uint256)'](0x4132)).to.equal('0x4132');
|
||||
});
|
||||
|
||||
it('converts MAX_UINT256', async function () {
|
||||
expect(await this.strings.methods['$toHexString(uint256)'](constants.MAX_UINT256)).to.equal(
|
||||
web3.utils.toHex(constants.MAX_UINT256),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('toHexString fixed', function () {
|
||||
it('converts a positive number (long)', async function () {
|
||||
expect(await this.strings.methods['$toHexString(uint256,uint256)'](0x4132, 32)).to.equal(
|
||||
'0x0000000000000000000000000000000000000000000000000000000000004132',
|
||||
);
|
||||
});
|
||||
|
||||
it('converts a positive number (short)', async function () {
|
||||
await expectRevert(
|
||||
this.strings.methods['$toHexString(uint256,uint256)'](0x4132, 1),
|
||||
'Strings: hex length insufficient',
|
||||
);
|
||||
});
|
||||
|
||||
it('converts MAX_UINT256', async function () {
|
||||
expect(await this.strings.methods['$toHexString(uint256,uint256)'](constants.MAX_UINT256, 32)).to.equal(
|
||||
web3.utils.toHex(constants.MAX_UINT256),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('toHexString address', function () {
|
||||
it('converts a random address', async function () {
|
||||
const addr = '0xa9036907dccae6a1e0033479b12e837e5cf5a02f';
|
||||
expect(await this.strings.methods['$toHexString(address)'](addr)).to.equal(addr);
|
||||
});
|
||||
|
||||
it('converts an address with leading zeros', async function () {
|
||||
const addr = '0x0000e0ca771e21bd00057f54a68c30d400000000';
|
||||
expect(await this.strings.methods['$toHexString(address)'](addr)).to.equal(addr);
|
||||
});
|
||||
});
|
||||
|
||||
describe('equal', function () {
|
||||
it('compares two empty strings', async function () {
|
||||
expect(await this.strings.methods['$equal(string,string)']('', '')).to.equal(true);
|
||||
});
|
||||
|
||||
it('compares two equal strings', async function () {
|
||||
expect(await this.strings.methods['$equal(string,string)']('a', 'a')).to.equal(true);
|
||||
});
|
||||
|
||||
it('compares two different strings', async function () {
|
||||
expect(await this.strings.methods['$equal(string,string)']('a', 'b')).to.equal(false);
|
||||
});
|
||||
|
||||
it('compares two different strings of different lengths', async function () {
|
||||
expect(await this.strings.methods['$equal(string,string)']('a', 'aa')).to.equal(false);
|
||||
expect(await this.strings.methods['$equal(string,string)']('aa', 'a')).to.equal(false);
|
||||
});
|
||||
|
||||
it('compares two different large strings', async function () {
|
||||
const str1 = 'a'.repeat(201);
|
||||
const str2 = 'a'.repeat(200) + 'b';
|
||||
expect(await this.strings.methods['$equal(string,string)'](str1, str2)).to.equal(false);
|
||||
});
|
||||
|
||||
it('compares two equal large strings', async function () {
|
||||
const str1 = 'a'.repeat(201);
|
||||
const str2 = 'a'.repeat(201);
|
||||
expect(await this.strings.methods['$equal(string,string)'](str1, str2)).to.equal(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,55 @@
|
||||
const { BN, time } = require('@openzeppelin/test-helpers');
|
||||
const { expect } = require('chai');
|
||||
|
||||
const TimersBlockNumberImpl = artifacts.require('TimersBlockNumberImpl');
|
||||
|
||||
contract('TimersBlockNumber', function () {
|
||||
beforeEach(async function () {
|
||||
this.instance = await TimersBlockNumberImpl.new();
|
||||
this.now = await web3.eth.getBlock('latest').then(({ number }) => number);
|
||||
});
|
||||
|
||||
it('unset', async function () {
|
||||
expect(await this.instance.getDeadline()).to.be.bignumber.equal('0');
|
||||
expect(await this.instance.isUnset()).to.be.equal(true);
|
||||
expect(await this.instance.isStarted()).to.be.equal(false);
|
||||
expect(await this.instance.isPending()).to.be.equal(false);
|
||||
expect(await this.instance.isExpired()).to.be.equal(false);
|
||||
});
|
||||
|
||||
it('pending', async function () {
|
||||
await this.instance.setDeadline(this.now + 3);
|
||||
expect(await this.instance.getDeadline()).to.be.bignumber.equal(new BN(this.now + 3));
|
||||
expect(await this.instance.isUnset()).to.be.equal(false);
|
||||
expect(await this.instance.isStarted()).to.be.equal(true);
|
||||
expect(await this.instance.isPending()).to.be.equal(true);
|
||||
expect(await this.instance.isExpired()).to.be.equal(false);
|
||||
});
|
||||
|
||||
it('expired', async function () {
|
||||
await this.instance.setDeadline(this.now - 3);
|
||||
expect(await this.instance.getDeadline()).to.be.bignumber.equal(new BN(this.now - 3));
|
||||
expect(await this.instance.isUnset()).to.be.equal(false);
|
||||
expect(await this.instance.isStarted()).to.be.equal(true);
|
||||
expect(await this.instance.isPending()).to.be.equal(false);
|
||||
expect(await this.instance.isExpired()).to.be.equal(true);
|
||||
});
|
||||
|
||||
it('reset', async function () {
|
||||
await this.instance.reset();
|
||||
expect(await this.instance.getDeadline()).to.be.bignumber.equal(new BN(0));
|
||||
expect(await this.instance.isUnset()).to.be.equal(true);
|
||||
expect(await this.instance.isStarted()).to.be.equal(false);
|
||||
expect(await this.instance.isPending()).to.be.equal(false);
|
||||
expect(await this.instance.isExpired()).to.be.equal(false);
|
||||
});
|
||||
|
||||
it('fast forward', async function () {
|
||||
await this.instance.setDeadline(this.now + 3);
|
||||
expect(await this.instance.isPending()).to.be.equal(true);
|
||||
expect(await this.instance.isExpired()).to.be.equal(false);
|
||||
await time.advanceBlockTo(this.now + 3);
|
||||
expect(await this.instance.isPending()).to.be.equal(false);
|
||||
expect(await this.instance.isExpired()).to.be.equal(true);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,55 @@
|
||||
const { BN, time } = require('@openzeppelin/test-helpers');
|
||||
const { expect } = require('chai');
|
||||
|
||||
const TimersTimestampImpl = artifacts.require('TimersTimestampImpl');
|
||||
|
||||
contract('TimersTimestamp', function () {
|
||||
beforeEach(async function () {
|
||||
this.instance = await TimersTimestampImpl.new();
|
||||
this.now = await web3.eth.getBlock('latest').then(({ timestamp }) => timestamp);
|
||||
});
|
||||
|
||||
it('unset', async function () {
|
||||
expect(await this.instance.getDeadline()).to.be.bignumber.equal('0');
|
||||
expect(await this.instance.isUnset()).to.be.equal(true);
|
||||
expect(await this.instance.isStarted()).to.be.equal(false);
|
||||
expect(await this.instance.isPending()).to.be.equal(false);
|
||||
expect(await this.instance.isExpired()).to.be.equal(false);
|
||||
});
|
||||
|
||||
it('pending', async function () {
|
||||
await this.instance.setDeadline(this.now + 100);
|
||||
expect(await this.instance.getDeadline()).to.be.bignumber.equal(new BN(this.now + 100));
|
||||
expect(await this.instance.isUnset()).to.be.equal(false);
|
||||
expect(await this.instance.isStarted()).to.be.equal(true);
|
||||
expect(await this.instance.isPending()).to.be.equal(true);
|
||||
expect(await this.instance.isExpired()).to.be.equal(false);
|
||||
});
|
||||
|
||||
it('expired', async function () {
|
||||
await this.instance.setDeadline(this.now - 100);
|
||||
expect(await this.instance.getDeadline()).to.be.bignumber.equal(new BN(this.now - 100));
|
||||
expect(await this.instance.isUnset()).to.be.equal(false);
|
||||
expect(await this.instance.isStarted()).to.be.equal(true);
|
||||
expect(await this.instance.isPending()).to.be.equal(false);
|
||||
expect(await this.instance.isExpired()).to.be.equal(true);
|
||||
});
|
||||
|
||||
it('reset', async function () {
|
||||
await this.instance.reset();
|
||||
expect(await this.instance.getDeadline()).to.be.bignumber.equal(new BN(0));
|
||||
expect(await this.instance.isUnset()).to.be.equal(true);
|
||||
expect(await this.instance.isStarted()).to.be.equal(false);
|
||||
expect(await this.instance.isPending()).to.be.equal(false);
|
||||
expect(await this.instance.isExpired()).to.be.equal(false);
|
||||
});
|
||||
|
||||
it('fast forward', async function () {
|
||||
await this.instance.setDeadline(this.now + 100);
|
||||
expect(await this.instance.isPending()).to.be.equal(true);
|
||||
expect(await this.instance.isExpired()).to.be.equal(false);
|
||||
await time.increaseTo(this.now + 100);
|
||||
expect(await this.instance.isPending()).to.be.equal(false);
|
||||
expect(await this.instance.isExpired()).to.be.equal(true);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,260 @@
|
||||
const { expectRevert } = require('@openzeppelin/test-helpers');
|
||||
const { toEthSignedMessageHash, toDataWithIntendedValidatorHash } = require('../../helpers/sign');
|
||||
|
||||
const { expect } = require('chai');
|
||||
|
||||
const ECDSA = artifacts.require('$ECDSA');
|
||||
|
||||
const TEST_MESSAGE = web3.utils.sha3('OpenZeppelin');
|
||||
const WRONG_MESSAGE = web3.utils.sha3('Nope');
|
||||
const NON_HASH_MESSAGE = '0x' + Buffer.from('abcd').toString('hex');
|
||||
const RANDOM_ADDRESS = web3.utils.toChecksumAddress(web3.utils.randomHex(20));
|
||||
|
||||
function to2098Format(signature) {
|
||||
const long = web3.utils.hexToBytes(signature);
|
||||
if (long.length !== 65) {
|
||||
throw new Error('invalid signature length (expected long format)');
|
||||
}
|
||||
if (long[32] >> 7 === 1) {
|
||||
throw new Error("invalid signature 's' value");
|
||||
}
|
||||
const short = long.slice(0, 64);
|
||||
short[32] |= long[64] % 27 << 7; // set the first bit of the 32nd byte to the v parity bit
|
||||
return web3.utils.bytesToHex(short);
|
||||
}
|
||||
|
||||
function split(signature) {
|
||||
const raw = web3.utils.hexToBytes(signature);
|
||||
switch (raw.length) {
|
||||
case 64:
|
||||
return [
|
||||
web3.utils.bytesToHex(raw.slice(0, 32)), // r
|
||||
web3.utils.bytesToHex(raw.slice(32, 64)), // vs
|
||||
];
|
||||
case 65:
|
||||
return [
|
||||
raw[64], // v
|
||||
web3.utils.bytesToHex(raw.slice(0, 32)), // r
|
||||
web3.utils.bytesToHex(raw.slice(32, 64)), // s
|
||||
];
|
||||
default:
|
||||
expect.fail('Invalid signature length, cannot split');
|
||||
}
|
||||
}
|
||||
|
||||
contract('ECDSA', function (accounts) {
|
||||
const [other] = accounts;
|
||||
|
||||
beforeEach(async function () {
|
||||
this.ecdsa = await ECDSA.new();
|
||||
});
|
||||
|
||||
context('recover with invalid signature', function () {
|
||||
it('with short signature', async function () {
|
||||
await expectRevert(this.ecdsa.$recover(TEST_MESSAGE, '0x1234'), 'ECDSA: invalid signature length');
|
||||
});
|
||||
|
||||
it('with long signature', async function () {
|
||||
await expectRevert(
|
||||
// eslint-disable-next-line max-len
|
||||
this.ecdsa.$recover(
|
||||
TEST_MESSAGE,
|
||||
'0x01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789',
|
||||
),
|
||||
'ECDSA: invalid signature length',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
context('recover with valid signature', function () {
|
||||
context('using web3.eth.sign', function () {
|
||||
it('returns signer address with correct signature', async function () {
|
||||
// Create the signature
|
||||
const signature = await web3.eth.sign(TEST_MESSAGE, other);
|
||||
|
||||
// Recover the signer address from the generated message and signature.
|
||||
expect(await this.ecdsa.$recover(toEthSignedMessageHash(TEST_MESSAGE), signature)).to.equal(other);
|
||||
});
|
||||
|
||||
it('returns signer address with correct signature for arbitrary length message', async function () {
|
||||
// Create the signature
|
||||
const signature = await web3.eth.sign(NON_HASH_MESSAGE, other);
|
||||
|
||||
// Recover the signer address from the generated message and signature.
|
||||
expect(await this.ecdsa.$recover(toEthSignedMessageHash(NON_HASH_MESSAGE), signature)).to.equal(other);
|
||||
});
|
||||
|
||||
it('returns a different address', async function () {
|
||||
const signature = await web3.eth.sign(TEST_MESSAGE, other);
|
||||
expect(await this.ecdsa.$recover(WRONG_MESSAGE, signature)).to.not.equal(other);
|
||||
});
|
||||
|
||||
it('reverts with invalid signature', async function () {
|
||||
// eslint-disable-next-line max-len
|
||||
const signature =
|
||||
'0x332ce75a821c982f9127538858900d87d3ec1f9f737338ad67cad133fa48feff48e6fa0c18abc62e42820f05943e47af3e9fbe306ce74d64094bdf1691ee53e01c';
|
||||
await expectRevert(this.ecdsa.$recover(TEST_MESSAGE, signature), 'ECDSA: invalid signature');
|
||||
});
|
||||
});
|
||||
|
||||
context('with v=27 signature', function () {
|
||||
// Signature generated outside ganache with method web3.eth.sign(signer, message)
|
||||
const signer = '0x2cc1166f6212628A0deEf2B33BEFB2187D35b86c';
|
||||
// eslint-disable-next-line max-len
|
||||
const signatureWithoutV =
|
||||
'0x5d99b6f7f6d1f73d1a26497f2b1c89b24c0993913f86e9a2d02cd69887d9c94f3c880358579d811b21dd1b7fd9bb01c1d81d10e69f0384e675c32b39643be892';
|
||||
|
||||
it('works with correct v value', async function () {
|
||||
const v = '1b'; // 27 = 1b.
|
||||
const signature = signatureWithoutV + v;
|
||||
expect(await this.ecdsa.$recover(TEST_MESSAGE, signature)).to.equal(signer);
|
||||
|
||||
expect(
|
||||
await this.ecdsa.methods['$recover(bytes32,uint8,bytes32,bytes32)'](TEST_MESSAGE, ...split(signature)),
|
||||
).to.equal(signer);
|
||||
|
||||
expect(
|
||||
await this.ecdsa.methods['$recover(bytes32,bytes32,bytes32)'](
|
||||
TEST_MESSAGE,
|
||||
...split(to2098Format(signature)),
|
||||
),
|
||||
).to.equal(signer);
|
||||
});
|
||||
|
||||
it('rejects incorrect v value', async function () {
|
||||
const v = '1c'; // 28 = 1c.
|
||||
const signature = signatureWithoutV + v;
|
||||
expect(await this.ecdsa.$recover(TEST_MESSAGE, signature)).to.not.equal(signer);
|
||||
|
||||
expect(
|
||||
await this.ecdsa.methods['$recover(bytes32,uint8,bytes32,bytes32)'](TEST_MESSAGE, ...split(signature)),
|
||||
).to.not.equal(signer);
|
||||
|
||||
expect(
|
||||
await this.ecdsa.methods['$recover(bytes32,bytes32,bytes32)'](
|
||||
TEST_MESSAGE,
|
||||
...split(to2098Format(signature)),
|
||||
),
|
||||
).to.not.equal(signer);
|
||||
});
|
||||
|
||||
it('reverts wrong v values', async function () {
|
||||
for (const v of ['00', '01']) {
|
||||
const signature = signatureWithoutV + v;
|
||||
await expectRevert(this.ecdsa.$recover(TEST_MESSAGE, signature), 'ECDSA: invalid signature');
|
||||
|
||||
await expectRevert(
|
||||
this.ecdsa.methods['$recover(bytes32,uint8,bytes32,bytes32)'](TEST_MESSAGE, ...split(signature)),
|
||||
'ECDSA: invalid signature',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('rejects short EIP2098 format', async function () {
|
||||
const v = '1b'; // 27 = 1b.
|
||||
const signature = signatureWithoutV + v;
|
||||
await expectRevert(
|
||||
this.ecdsa.$recover(TEST_MESSAGE, to2098Format(signature)),
|
||||
'ECDSA: invalid signature length',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
context('with v=28 signature', function () {
|
||||
const signer = '0x1E318623aB09Fe6de3C9b8672098464Aeda9100E';
|
||||
// eslint-disable-next-line max-len
|
||||
const signatureWithoutV =
|
||||
'0x331fe75a821c982f9127538858900d87d3ec1f9f737338ad67cad133fa48feff48e6fa0c18abc62e42820f05943e47af3e9fbe306ce74d64094bdf1691ee53e0';
|
||||
|
||||
it('works with correct v value', async function () {
|
||||
const v = '1c'; // 28 = 1c.
|
||||
const signature = signatureWithoutV + v;
|
||||
expect(await this.ecdsa.$recover(TEST_MESSAGE, signature)).to.equal(signer);
|
||||
|
||||
expect(
|
||||
await this.ecdsa.methods['$recover(bytes32,uint8,bytes32,bytes32)'](TEST_MESSAGE, ...split(signature)),
|
||||
).to.equal(signer);
|
||||
|
||||
expect(
|
||||
await this.ecdsa.methods['$recover(bytes32,bytes32,bytes32)'](
|
||||
TEST_MESSAGE,
|
||||
...split(to2098Format(signature)),
|
||||
),
|
||||
).to.equal(signer);
|
||||
});
|
||||
|
||||
it('rejects incorrect v value', async function () {
|
||||
const v = '1b'; // 27 = 1b.
|
||||
const signature = signatureWithoutV + v;
|
||||
expect(await this.ecdsa.$recover(TEST_MESSAGE, signature)).to.not.equal(signer);
|
||||
|
||||
expect(
|
||||
await this.ecdsa.methods['$recover(bytes32,uint8,bytes32,bytes32)'](TEST_MESSAGE, ...split(signature)),
|
||||
).to.not.equal(signer);
|
||||
|
||||
expect(
|
||||
await this.ecdsa.methods['$recover(bytes32,bytes32,bytes32)'](
|
||||
TEST_MESSAGE,
|
||||
...split(to2098Format(signature)),
|
||||
),
|
||||
).to.not.equal(signer);
|
||||
});
|
||||
|
||||
it('reverts invalid v values', async function () {
|
||||
for (const v of ['00', '01']) {
|
||||
const signature = signatureWithoutV + v;
|
||||
await expectRevert(this.ecdsa.$recover(TEST_MESSAGE, signature), 'ECDSA: invalid signature');
|
||||
|
||||
await expectRevert(
|
||||
this.ecdsa.methods['$recover(bytes32,uint8,bytes32,bytes32)'](TEST_MESSAGE, ...split(signature)),
|
||||
'ECDSA: invalid signature',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('rejects short EIP2098 format', async function () {
|
||||
const v = '1c'; // 27 = 1b.
|
||||
const signature = signatureWithoutV + v;
|
||||
await expectRevert(
|
||||
this.ecdsa.$recover(TEST_MESSAGE, to2098Format(signature)),
|
||||
'ECDSA: invalid signature length',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('reverts with high-s value signature', async function () {
|
||||
const message = '0xb94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9';
|
||||
// eslint-disable-next-line max-len
|
||||
const highSSignature =
|
||||
'0xe742ff452d41413616a5bf43fe15dd88294e983d3d36206c2712f39083d638bde0a0fc89be718fbc1033e1d30d78be1c68081562ed2e97af876f286f3453231d1b';
|
||||
await expectRevert(this.ecdsa.$recover(message, highSSignature), "ECDSA: invalid signature 's' value");
|
||||
await expectRevert(
|
||||
this.ecdsa.methods['$recover(bytes32,uint8,bytes32,bytes32)'](TEST_MESSAGE, ...split(highSSignature)),
|
||||
"ECDSA: invalid signature 's' value",
|
||||
);
|
||||
expect(() => to2098Format(highSSignature)).to.throw("invalid signature 's' value");
|
||||
});
|
||||
});
|
||||
|
||||
context('toEthSignedMessageHash', function () {
|
||||
it('prefixes bytes32 data correctly', async function () {
|
||||
expect(await this.ecdsa.methods['$toEthSignedMessageHash(bytes32)'](TEST_MESSAGE)).to.equal(
|
||||
toEthSignedMessageHash(TEST_MESSAGE),
|
||||
);
|
||||
});
|
||||
|
||||
it('prefixes dynamic length data correctly', async function () {
|
||||
expect(await this.ecdsa.methods['$toEthSignedMessageHash(bytes)'](NON_HASH_MESSAGE)).to.equal(
|
||||
toEthSignedMessageHash(NON_HASH_MESSAGE),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
context('toDataWithIntendedValidatorHash', function () {
|
||||
it('returns the hash correctly', async function () {
|
||||
expect(
|
||||
await this.ecdsa.methods['$toDataWithIntendedValidatorHash(address,bytes)'](RANDOM_ADDRESS, NON_HASH_MESSAGE),
|
||||
).to.equal(toDataWithIntendedValidatorHash(RANDOM_ADDRESS, NON_HASH_MESSAGE));
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,103 @@
|
||||
const ethSigUtil = require('eth-sig-util');
|
||||
const Wallet = require('ethereumjs-wallet').default;
|
||||
|
||||
const { getDomain, domainType, domainSeparator, hashTypedData } = require('../../helpers/eip712');
|
||||
const { getChainId } = require('../../helpers/chainid');
|
||||
const { mapValues } = require('../../helpers/map-values');
|
||||
|
||||
const EIP712Verifier = artifacts.require('$EIP712Verifier');
|
||||
const Clones = artifacts.require('$Clones');
|
||||
|
||||
contract('EIP712', function (accounts) {
|
||||
const [mailTo] = accounts;
|
||||
|
||||
const shortName = 'A Name';
|
||||
const shortVersion = '1';
|
||||
|
||||
const longName = 'A'.repeat(40);
|
||||
const longVersion = 'B'.repeat(40);
|
||||
|
||||
const cases = [
|
||||
['short', shortName, shortVersion],
|
||||
['long', longName, longVersion],
|
||||
];
|
||||
|
||||
for (const [shortOrLong, name, version] of cases) {
|
||||
describe(`with ${shortOrLong} name and version`, function () {
|
||||
beforeEach('deploying', async function () {
|
||||
this.eip712 = await EIP712Verifier.new(name, version);
|
||||
|
||||
this.domain = {
|
||||
name,
|
||||
version,
|
||||
chainId: await getChainId(),
|
||||
verifyingContract: this.eip712.address,
|
||||
};
|
||||
this.domainType = domainType(this.domain);
|
||||
});
|
||||
|
||||
describe('domain separator', function () {
|
||||
it('is internally available', async function () {
|
||||
const expected = await domainSeparator(this.domain);
|
||||
|
||||
expect(await this.eip712.$_domainSeparatorV4()).to.equal(expected);
|
||||
});
|
||||
|
||||
it("can be rebuilt using EIP-5267's eip712Domain", async function () {
|
||||
const rebuildDomain = await getDomain(this.eip712);
|
||||
expect(mapValues(rebuildDomain, String)).to.be.deep.equal(mapValues(this.domain, String));
|
||||
});
|
||||
|
||||
if (shortOrLong === 'short') {
|
||||
// Long strings are in storage, and the proxy will not be properly initialized unless
|
||||
// the upgradeable contract variant is used and the initializer is invoked.
|
||||
|
||||
it('adjusts when behind proxy', async function () {
|
||||
const factory = await Clones.new();
|
||||
const cloneReceipt = await factory.$clone(this.eip712.address);
|
||||
const cloneAddress = cloneReceipt.logs.find(({ event }) => event === 'return$clone').args.instance;
|
||||
const clone = new EIP712Verifier(cloneAddress);
|
||||
|
||||
const cloneDomain = { ...this.domain, verifyingContract: clone.address };
|
||||
|
||||
const reportedDomain = await getDomain(clone);
|
||||
expect(mapValues(reportedDomain, String)).to.be.deep.equal(mapValues(cloneDomain, String));
|
||||
|
||||
const expectedSeparator = await domainSeparator(cloneDomain);
|
||||
expect(await clone.$_domainSeparatorV4()).to.equal(expectedSeparator);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it('hash digest', async function () {
|
||||
const structhash = web3.utils.randomHex(32);
|
||||
expect(await this.eip712.$_hashTypedDataV4(structhash)).to.be.equal(hashTypedData(this.domain, structhash));
|
||||
});
|
||||
|
||||
it('digest', async function () {
|
||||
const message = {
|
||||
to: mailTo,
|
||||
contents: 'very interesting',
|
||||
};
|
||||
|
||||
const data = {
|
||||
types: {
|
||||
EIP712Domain: this.domainType,
|
||||
Mail: [
|
||||
{ name: 'to', type: 'address' },
|
||||
{ name: 'contents', type: 'string' },
|
||||
],
|
||||
},
|
||||
domain: this.domain,
|
||||
primaryType: 'Mail',
|
||||
message,
|
||||
};
|
||||
|
||||
const wallet = Wallet.generate();
|
||||
const signature = ethSigUtil.signTypedMessage(wallet.getPrivateKey(), { data });
|
||||
|
||||
await this.eip712.verify(signature, wallet.getAddressString(), message.to, message.contents);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,180 @@
|
||||
require('@openzeppelin/test-helpers');
|
||||
|
||||
const { expectRevert } = require('@openzeppelin/test-helpers');
|
||||
const { MerkleTree } = require('merkletreejs');
|
||||
const keccak256 = require('keccak256');
|
||||
|
||||
const { expect } = require('chai');
|
||||
|
||||
const MerkleProof = artifacts.require('$MerkleProof');
|
||||
|
||||
contract('MerkleProof', function () {
|
||||
beforeEach(async function () {
|
||||
this.merkleProof = await MerkleProof.new();
|
||||
});
|
||||
|
||||
describe('verify', function () {
|
||||
it('returns true for a valid Merkle proof', async function () {
|
||||
const elements = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='.split('');
|
||||
const merkleTree = new MerkleTree(elements, keccak256, { hashLeaves: true, sortPairs: true });
|
||||
|
||||
const root = merkleTree.getHexRoot();
|
||||
|
||||
const leaf = keccak256(elements[0]);
|
||||
|
||||
const proof = merkleTree.getHexProof(leaf);
|
||||
|
||||
expect(await this.merkleProof.$verify(proof, root, leaf)).to.equal(true);
|
||||
expect(await this.merkleProof.$verifyCalldata(proof, root, leaf)).to.equal(true);
|
||||
|
||||
// For demonstration, it is also possible to create valid proofs for certain 64-byte values *not* in elements:
|
||||
const noSuchLeaf = keccak256(
|
||||
Buffer.concat([keccak256(elements[0]), keccak256(elements[1])].sort(Buffer.compare)),
|
||||
);
|
||||
expect(await this.merkleProof.$verify(proof.slice(1), root, noSuchLeaf)).to.equal(true);
|
||||
expect(await this.merkleProof.$verifyCalldata(proof.slice(1), root, noSuchLeaf)).to.equal(true);
|
||||
});
|
||||
|
||||
it('returns false for an invalid Merkle proof', async function () {
|
||||
const correctElements = ['a', 'b', 'c'];
|
||||
const correctMerkleTree = new MerkleTree(correctElements, keccak256, { hashLeaves: true, sortPairs: true });
|
||||
|
||||
const correctRoot = correctMerkleTree.getHexRoot();
|
||||
|
||||
const correctLeaf = keccak256(correctElements[0]);
|
||||
|
||||
const badElements = ['d', 'e', 'f'];
|
||||
const badMerkleTree = new MerkleTree(badElements);
|
||||
|
||||
const badProof = badMerkleTree.getHexProof(badElements[0]);
|
||||
|
||||
expect(await this.merkleProof.$verify(badProof, correctRoot, correctLeaf)).to.equal(false);
|
||||
expect(await this.merkleProof.$verifyCalldata(badProof, correctRoot, correctLeaf)).to.equal(false);
|
||||
});
|
||||
|
||||
it('returns false for a Merkle proof of invalid length', async function () {
|
||||
const elements = ['a', 'b', 'c'];
|
||||
const merkleTree = new MerkleTree(elements, keccak256, { hashLeaves: true, sortPairs: true });
|
||||
|
||||
const root = merkleTree.getHexRoot();
|
||||
|
||||
const leaf = keccak256(elements[0]);
|
||||
|
||||
const proof = merkleTree.getHexProof(leaf);
|
||||
const badProof = proof.slice(0, proof.length - 5);
|
||||
|
||||
expect(await this.merkleProof.$verify(badProof, root, leaf)).to.equal(false);
|
||||
expect(await this.merkleProof.$verifyCalldata(badProof, root, leaf)).to.equal(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('multiProofVerify', function () {
|
||||
it('returns true for a valid Merkle multi proof', async function () {
|
||||
const leaves = ['a', 'b', 'c', 'd', 'e', 'f'].map(keccak256).sort(Buffer.compare);
|
||||
const merkleTree = new MerkleTree(leaves, keccak256, { sort: true });
|
||||
|
||||
const root = merkleTree.getRoot();
|
||||
const proofLeaves = ['b', 'f', 'd'].map(keccak256).sort(Buffer.compare);
|
||||
const proof = merkleTree.getMultiProof(proofLeaves);
|
||||
const proofFlags = merkleTree.getProofFlags(proofLeaves, proof);
|
||||
|
||||
expect(await this.merkleProof.$multiProofVerify(proof, proofFlags, root, proofLeaves)).to.equal(true);
|
||||
expect(await this.merkleProof.$multiProofVerifyCalldata(proof, proofFlags, root, proofLeaves)).to.equal(true);
|
||||
});
|
||||
|
||||
it('returns false for an invalid Merkle multi proof', async function () {
|
||||
const leaves = ['a', 'b', 'c', 'd', 'e', 'f'].map(keccak256).sort(Buffer.compare);
|
||||
const merkleTree = new MerkleTree(leaves, keccak256, { sort: true });
|
||||
|
||||
const root = merkleTree.getRoot();
|
||||
const badProofLeaves = ['g', 'h', 'i'].map(keccak256).sort(Buffer.compare);
|
||||
const badMerkleTree = new MerkleTree(badProofLeaves);
|
||||
const badProof = badMerkleTree.getMultiProof(badProofLeaves);
|
||||
const badProofFlags = badMerkleTree.getProofFlags(badProofLeaves, badProof);
|
||||
|
||||
expect(await this.merkleProof.$multiProofVerify(badProof, badProofFlags, root, badProofLeaves)).to.equal(false);
|
||||
expect(await this.merkleProof.$multiProofVerifyCalldata(badProof, badProofFlags, root, badProofLeaves)).to.equal(
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
it('revert with invalid multi proof #1', async function () {
|
||||
const fill = Buffer.alloc(32); // This could be anything, we are reconstructing a fake branch
|
||||
const leaves = ['a', 'b', 'c', 'd'].map(keccak256).sort(Buffer.compare);
|
||||
const badLeaf = keccak256('e');
|
||||
const merkleTree = new MerkleTree(leaves, keccak256, { sort: true });
|
||||
|
||||
const root = merkleTree.getRoot();
|
||||
|
||||
await expectRevert(
|
||||
this.merkleProof.$multiProofVerify(
|
||||
[leaves[1], fill, merkleTree.layers[1][1]],
|
||||
[false, false, false],
|
||||
root,
|
||||
[leaves[0], badLeaf], // A, E
|
||||
),
|
||||
'MerkleProof: invalid multiproof',
|
||||
);
|
||||
await expectRevert(
|
||||
this.merkleProof.$multiProofVerifyCalldata(
|
||||
[leaves[1], fill, merkleTree.layers[1][1]],
|
||||
[false, false, false],
|
||||
root,
|
||||
[leaves[0], badLeaf], // A, E
|
||||
),
|
||||
'MerkleProof: invalid multiproof',
|
||||
);
|
||||
});
|
||||
|
||||
it('revert with invalid multi proof #2', async function () {
|
||||
const fill = Buffer.alloc(32); // This could be anything, we are reconstructing a fake branch
|
||||
const leaves = ['a', 'b', 'c', 'd'].map(keccak256).sort(Buffer.compare);
|
||||
const badLeaf = keccak256('e');
|
||||
const merkleTree = new MerkleTree(leaves, keccak256, { sort: true });
|
||||
|
||||
const root = merkleTree.getRoot();
|
||||
|
||||
await expectRevert(
|
||||
this.merkleProof.$multiProofVerify(
|
||||
[leaves[1], fill, merkleTree.layers[1][1]],
|
||||
[false, false, false, false],
|
||||
root,
|
||||
[badLeaf, leaves[0]], // A, E
|
||||
),
|
||||
'reverted with panic code 0x32',
|
||||
);
|
||||
|
||||
await expectRevert(
|
||||
this.merkleProof.$multiProofVerifyCalldata(
|
||||
[leaves[1], fill, merkleTree.layers[1][1]],
|
||||
[false, false, false, false],
|
||||
root,
|
||||
[badLeaf, leaves[0]], // A, E
|
||||
),
|
||||
'reverted with panic code 0x32',
|
||||
);
|
||||
});
|
||||
|
||||
it('limit case: works for tree containing a single leaf', async function () {
|
||||
const leaves = ['a'].map(keccak256).sort(Buffer.compare);
|
||||
const merkleTree = new MerkleTree(leaves, keccak256, { sort: true });
|
||||
|
||||
const root = merkleTree.getRoot();
|
||||
const proofLeaves = ['a'].map(keccak256).sort(Buffer.compare);
|
||||
const proof = merkleTree.getMultiProof(proofLeaves);
|
||||
const proofFlags = merkleTree.getProofFlags(proofLeaves, proof);
|
||||
|
||||
expect(await this.merkleProof.$multiProofVerify(proof, proofFlags, root, proofLeaves)).to.equal(true);
|
||||
expect(await this.merkleProof.$multiProofVerifyCalldata(proof, proofFlags, root, proofLeaves)).to.equal(true);
|
||||
});
|
||||
|
||||
it('limit case: can prove empty leaves', async function () {
|
||||
const leaves = ['a', 'b', 'c', 'd'].map(keccak256).sort(Buffer.compare);
|
||||
const merkleTree = new MerkleTree(leaves, keccak256, { sort: true });
|
||||
|
||||
const root = merkleTree.getRoot();
|
||||
expect(await this.merkleProof.$multiProofVerify([root], [], root, [])).to.equal(true);
|
||||
expect(await this.merkleProof.$multiProofVerifyCalldata([root], [], root, [])).to.equal(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,87 @@
|
||||
const { toEthSignedMessageHash } = require('../../helpers/sign');
|
||||
|
||||
const { expect } = require('chai');
|
||||
|
||||
const SignatureChecker = artifacts.require('$SignatureChecker');
|
||||
const ERC1271WalletMock = artifacts.require('ERC1271WalletMock');
|
||||
const ERC1271MaliciousMock = artifacts.require('ERC1271MaliciousMock');
|
||||
|
||||
const TEST_MESSAGE = web3.utils.sha3('OpenZeppelin');
|
||||
const WRONG_MESSAGE = web3.utils.sha3('Nope');
|
||||
|
||||
contract('SignatureChecker (ERC1271)', function (accounts) {
|
||||
const [signer, other] = accounts;
|
||||
|
||||
before('deploying', async function () {
|
||||
this.signaturechecker = await SignatureChecker.new();
|
||||
this.wallet = await ERC1271WalletMock.new(signer);
|
||||
this.malicious = await ERC1271MaliciousMock.new();
|
||||
this.signature = await web3.eth.sign(TEST_MESSAGE, signer);
|
||||
});
|
||||
|
||||
context('EOA account', function () {
|
||||
it('with matching signer and signature', async function () {
|
||||
expect(
|
||||
await this.signaturechecker.$isValidSignatureNow(signer, toEthSignedMessageHash(TEST_MESSAGE), this.signature),
|
||||
).to.equal(true);
|
||||
});
|
||||
|
||||
it('with invalid signer', async function () {
|
||||
expect(
|
||||
await this.signaturechecker.$isValidSignatureNow(other, toEthSignedMessageHash(TEST_MESSAGE), this.signature),
|
||||
).to.equal(false);
|
||||
});
|
||||
|
||||
it('with invalid signature', async function () {
|
||||
expect(
|
||||
await this.signaturechecker.$isValidSignatureNow(signer, toEthSignedMessageHash(WRONG_MESSAGE), this.signature),
|
||||
).to.equal(false);
|
||||
});
|
||||
});
|
||||
|
||||
context('ERC1271 wallet', function () {
|
||||
for (const signature of ['isValidERC1271SignatureNow', 'isValidSignatureNow']) {
|
||||
context(signature, function () {
|
||||
it('with matching signer and signature', async function () {
|
||||
expect(
|
||||
await this.signaturechecker[`$${signature}`](
|
||||
this.wallet.address,
|
||||
toEthSignedMessageHash(TEST_MESSAGE),
|
||||
this.signature,
|
||||
),
|
||||
).to.equal(true);
|
||||
});
|
||||
|
||||
it('with invalid signer', async function () {
|
||||
expect(
|
||||
await this.signaturechecker[`$${signature}`](
|
||||
this.signaturechecker.address,
|
||||
toEthSignedMessageHash(TEST_MESSAGE),
|
||||
this.signature,
|
||||
),
|
||||
).to.equal(false);
|
||||
});
|
||||
|
||||
it('with invalid signature', async function () {
|
||||
expect(
|
||||
await this.signaturechecker[`$${signature}`](
|
||||
this.wallet.address,
|
||||
toEthSignedMessageHash(WRONG_MESSAGE),
|
||||
this.signature,
|
||||
),
|
||||
).to.equal(false);
|
||||
});
|
||||
|
||||
it('with malicious wallet', async function () {
|
||||
expect(
|
||||
await this.signaturechecker[`$${signature}`](
|
||||
this.malicious.address,
|
||||
toEthSignedMessageHash(TEST_MESSAGE),
|
||||
this.signature,
|
||||
),
|
||||
).to.equal(false);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,37 @@
|
||||
const { ether, expectRevert } = require('@openzeppelin/test-helpers');
|
||||
const { shouldBehaveLikeEscrow } = require('./Escrow.behavior');
|
||||
|
||||
const ConditionalEscrowMock = artifacts.require('ConditionalEscrowMock');
|
||||
|
||||
contract('ConditionalEscrow', function (accounts) {
|
||||
const [owner, payee, ...otherAccounts] = accounts;
|
||||
|
||||
beforeEach(async function () {
|
||||
this.escrow = await ConditionalEscrowMock.new({ from: owner });
|
||||
});
|
||||
|
||||
context('when withdrawal is allowed', function () {
|
||||
beforeEach(async function () {
|
||||
await Promise.all(otherAccounts.map(payee => this.escrow.setAllowed(payee, true)));
|
||||
});
|
||||
|
||||
shouldBehaveLikeEscrow(owner, otherAccounts);
|
||||
});
|
||||
|
||||
context('when withdrawal is disallowed', function () {
|
||||
const amount = ether('23');
|
||||
|
||||
beforeEach(async function () {
|
||||
await this.escrow.setAllowed(payee, false);
|
||||
});
|
||||
|
||||
it('reverts on withdrawals', async function () {
|
||||
await this.escrow.deposit(payee, { from: owner, value: amount });
|
||||
|
||||
await expectRevert(
|
||||
this.escrow.withdraw(payee, { from: owner }),
|
||||
'ConditionalEscrow: payee is not allowed to withdraw',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,90 @@
|
||||
const { balance, ether, expectEvent, expectRevert } = require('@openzeppelin/test-helpers');
|
||||
|
||||
const { expect } = require('chai');
|
||||
|
||||
function shouldBehaveLikeEscrow(owner, [payee1, payee2]) {
|
||||
const amount = ether('42');
|
||||
|
||||
describe('as an escrow', function () {
|
||||
describe('deposits', function () {
|
||||
it('can accept a single deposit', async function () {
|
||||
await this.escrow.deposit(payee1, { from: owner, value: amount });
|
||||
|
||||
expect(await balance.current(this.escrow.address)).to.be.bignumber.equal(amount);
|
||||
|
||||
expect(await this.escrow.depositsOf(payee1)).to.be.bignumber.equal(amount);
|
||||
});
|
||||
|
||||
it('can accept an empty deposit', async function () {
|
||||
await this.escrow.deposit(payee1, { from: owner, value: 0 });
|
||||
});
|
||||
|
||||
it('only the owner can deposit', async function () {
|
||||
await expectRevert(this.escrow.deposit(payee1, { from: payee2 }), 'Ownable: caller is not the owner');
|
||||
});
|
||||
|
||||
it('emits a deposited event', async function () {
|
||||
const receipt = await this.escrow.deposit(payee1, { from: owner, value: amount });
|
||||
expectEvent(receipt, 'Deposited', {
|
||||
payee: payee1,
|
||||
weiAmount: amount,
|
||||
});
|
||||
});
|
||||
|
||||
it('can add multiple deposits on a single account', async function () {
|
||||
await this.escrow.deposit(payee1, { from: owner, value: amount });
|
||||
await this.escrow.deposit(payee1, { from: owner, value: amount.muln(2) });
|
||||
|
||||
expect(await balance.current(this.escrow.address)).to.be.bignumber.equal(amount.muln(3));
|
||||
|
||||
expect(await this.escrow.depositsOf(payee1)).to.be.bignumber.equal(amount.muln(3));
|
||||
});
|
||||
|
||||
it('can track deposits to multiple accounts', async function () {
|
||||
await this.escrow.deposit(payee1, { from: owner, value: amount });
|
||||
await this.escrow.deposit(payee2, { from: owner, value: amount.muln(2) });
|
||||
|
||||
expect(await balance.current(this.escrow.address)).to.be.bignumber.equal(amount.muln(3));
|
||||
|
||||
expect(await this.escrow.depositsOf(payee1)).to.be.bignumber.equal(amount);
|
||||
|
||||
expect(await this.escrow.depositsOf(payee2)).to.be.bignumber.equal(amount.muln(2));
|
||||
});
|
||||
});
|
||||
|
||||
describe('withdrawals', async function () {
|
||||
it('can withdraw payments', async function () {
|
||||
const balanceTracker = await balance.tracker(payee1);
|
||||
|
||||
await this.escrow.deposit(payee1, { from: owner, value: amount });
|
||||
await this.escrow.withdraw(payee1, { from: owner });
|
||||
|
||||
expect(await balanceTracker.delta()).to.be.bignumber.equal(amount);
|
||||
|
||||
expect(await balance.current(this.escrow.address)).to.be.bignumber.equal('0');
|
||||
expect(await this.escrow.depositsOf(payee1)).to.be.bignumber.equal('0');
|
||||
});
|
||||
|
||||
it('can do an empty withdrawal', async function () {
|
||||
await this.escrow.withdraw(payee1, { from: owner });
|
||||
});
|
||||
|
||||
it('only the owner can withdraw', async function () {
|
||||
await expectRevert(this.escrow.withdraw(payee1, { from: payee1 }), 'Ownable: caller is not the owner');
|
||||
});
|
||||
|
||||
it('emits a withdrawn event', async function () {
|
||||
await this.escrow.deposit(payee1, { from: owner, value: amount });
|
||||
const receipt = await this.escrow.withdraw(payee1, { from: owner });
|
||||
expectEvent(receipt, 'Withdrawn', {
|
||||
payee: payee1,
|
||||
weiAmount: amount,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
shouldBehaveLikeEscrow,
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
require('@openzeppelin/test-helpers');
|
||||
const { shouldBehaveLikeEscrow } = require('./Escrow.behavior');
|
||||
|
||||
const Escrow = artifacts.require('Escrow');
|
||||
|
||||
contract('Escrow', function (accounts) {
|
||||
const [owner, ...otherAccounts] = accounts;
|
||||
|
||||
beforeEach(async function () {
|
||||
this.escrow = await Escrow.new({ from: owner });
|
||||
});
|
||||
|
||||
shouldBehaveLikeEscrow(owner, otherAccounts);
|
||||
});
|
||||
@@ -0,0 +1,143 @@
|
||||
const { balance, constants, ether, expectEvent, expectRevert } = require('@openzeppelin/test-helpers');
|
||||
const { ZERO_ADDRESS } = constants;
|
||||
|
||||
const { expect } = require('chai');
|
||||
|
||||
const RefundEscrow = artifacts.require('RefundEscrow');
|
||||
|
||||
contract('RefundEscrow', function (accounts) {
|
||||
const [owner, beneficiary, refundee1, refundee2] = accounts;
|
||||
|
||||
const amount = ether('54');
|
||||
const refundees = [refundee1, refundee2];
|
||||
|
||||
it('requires a non-null beneficiary', async function () {
|
||||
await expectRevert(
|
||||
RefundEscrow.new(ZERO_ADDRESS, { from: owner }),
|
||||
'RefundEscrow: beneficiary is the zero address',
|
||||
);
|
||||
});
|
||||
|
||||
context('once deployed', function () {
|
||||
beforeEach(async function () {
|
||||
this.escrow = await RefundEscrow.new(beneficiary, { from: owner });
|
||||
});
|
||||
|
||||
context('active state', function () {
|
||||
it('has beneficiary and state', async function () {
|
||||
expect(await this.escrow.beneficiary()).to.equal(beneficiary);
|
||||
expect(await this.escrow.state()).to.be.bignumber.equal('0');
|
||||
});
|
||||
|
||||
it('accepts deposits', async function () {
|
||||
await this.escrow.deposit(refundee1, { from: owner, value: amount });
|
||||
|
||||
expect(await this.escrow.depositsOf(refundee1)).to.be.bignumber.equal(amount);
|
||||
});
|
||||
|
||||
it('does not refund refundees', async function () {
|
||||
await this.escrow.deposit(refundee1, { from: owner, value: amount });
|
||||
await expectRevert(this.escrow.withdraw(refundee1), 'ConditionalEscrow: payee is not allowed to withdraw');
|
||||
});
|
||||
|
||||
it('does not allow beneficiary withdrawal', async function () {
|
||||
await this.escrow.deposit(refundee1, { from: owner, value: amount });
|
||||
await expectRevert(
|
||||
this.escrow.beneficiaryWithdraw(),
|
||||
'RefundEscrow: beneficiary can only withdraw while closed',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('only the owner can enter closed state', async function () {
|
||||
await expectRevert(this.escrow.close({ from: beneficiary }), 'Ownable: caller is not the owner');
|
||||
|
||||
const receipt = await this.escrow.close({ from: owner });
|
||||
expectEvent(receipt, 'RefundsClosed');
|
||||
});
|
||||
|
||||
context('closed state', function () {
|
||||
beforeEach(async function () {
|
||||
await Promise.all(refundees.map(refundee => this.escrow.deposit(refundee, { from: owner, value: amount })));
|
||||
|
||||
await this.escrow.close({ from: owner });
|
||||
});
|
||||
|
||||
it('rejects deposits', async function () {
|
||||
await expectRevert(
|
||||
this.escrow.deposit(refundee1, { from: owner, value: amount }),
|
||||
'RefundEscrow: can only deposit while active',
|
||||
);
|
||||
});
|
||||
|
||||
it('does not refund refundees', async function () {
|
||||
await expectRevert(this.escrow.withdraw(refundee1), 'ConditionalEscrow: payee is not allowed to withdraw');
|
||||
});
|
||||
|
||||
it('allows beneficiary withdrawal', async function () {
|
||||
const balanceTracker = await balance.tracker(beneficiary);
|
||||
await this.escrow.beneficiaryWithdraw();
|
||||
expect(await balanceTracker.delta()).to.be.bignumber.equal(amount.muln(refundees.length));
|
||||
});
|
||||
|
||||
it('prevents entering the refund state', async function () {
|
||||
await expectRevert(
|
||||
this.escrow.enableRefunds({ from: owner }),
|
||||
'RefundEscrow: can only enable refunds while active',
|
||||
);
|
||||
});
|
||||
|
||||
it('prevents re-entering the closed state', async function () {
|
||||
await expectRevert(this.escrow.close({ from: owner }), 'RefundEscrow: can only close while active');
|
||||
});
|
||||
});
|
||||
|
||||
it('only the owner can enter refund state', async function () {
|
||||
await expectRevert(this.escrow.enableRefunds({ from: beneficiary }), 'Ownable: caller is not the owner');
|
||||
|
||||
const receipt = await this.escrow.enableRefunds({ from: owner });
|
||||
expectEvent(receipt, 'RefundsEnabled');
|
||||
});
|
||||
|
||||
context('refund state', function () {
|
||||
beforeEach(async function () {
|
||||
await Promise.all(refundees.map(refundee => this.escrow.deposit(refundee, { from: owner, value: amount })));
|
||||
|
||||
await this.escrow.enableRefunds({ from: owner });
|
||||
});
|
||||
|
||||
it('rejects deposits', async function () {
|
||||
await expectRevert(
|
||||
this.escrow.deposit(refundee1, { from: owner, value: amount }),
|
||||
'RefundEscrow: can only deposit while active',
|
||||
);
|
||||
});
|
||||
|
||||
it('refunds refundees', async function () {
|
||||
for (const refundee of [refundee1, refundee2]) {
|
||||
const balanceTracker = await balance.tracker(refundee);
|
||||
await this.escrow.withdraw(refundee, { from: owner });
|
||||
expect(await balanceTracker.delta()).to.be.bignumber.equal(amount);
|
||||
}
|
||||
});
|
||||
|
||||
it('does not allow beneficiary withdrawal', async function () {
|
||||
await expectRevert(
|
||||
this.escrow.beneficiaryWithdraw(),
|
||||
'RefundEscrow: beneficiary can only withdraw while closed',
|
||||
);
|
||||
});
|
||||
|
||||
it('prevents entering the closed state', async function () {
|
||||
await expectRevert(this.escrow.close({ from: owner }), 'RefundEscrow: can only close while active');
|
||||
});
|
||||
|
||||
it('prevents re-entering the refund state', async function () {
|
||||
await expectRevert(
|
||||
this.escrow.enableRefunds({ from: owner }),
|
||||
'RefundEscrow: can only enable refunds while active',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
const { shouldSupportInterfaces } = require('./SupportsInterface.behavior');
|
||||
|
||||
const ERC165 = artifacts.require('$ERC165');
|
||||
|
||||
contract('ERC165', function () {
|
||||
beforeEach(async function () {
|
||||
this.mock = await ERC165.new();
|
||||
});
|
||||
|
||||
shouldSupportInterfaces(['ERC165']);
|
||||
});
|
||||
@@ -0,0 +1,302 @@
|
||||
require('@openzeppelin/test-helpers');
|
||||
|
||||
const { expect } = require('chai');
|
||||
|
||||
const ERC165Checker = artifacts.require('$ERC165Checker');
|
||||
const ERC165Storage = artifacts.require('$ERC165Storage');
|
||||
const ERC165MissingData = artifacts.require('ERC165MissingData');
|
||||
const ERC165MaliciousData = artifacts.require('ERC165MaliciousData');
|
||||
const ERC165NotSupported = artifacts.require('ERC165NotSupported');
|
||||
const ERC165ReturnBombMock = artifacts.require('ERC165ReturnBombMock');
|
||||
|
||||
const DUMMY_ID = '0xdeadbeef';
|
||||
const DUMMY_ID_2 = '0xcafebabe';
|
||||
const DUMMY_ID_3 = '0xdecafbad';
|
||||
const DUMMY_UNSUPPORTED_ID = '0xbaddcafe';
|
||||
const DUMMY_UNSUPPORTED_ID_2 = '0xbaadcafe';
|
||||
const DUMMY_ACCOUNT = '0x1111111111111111111111111111111111111111';
|
||||
|
||||
contract('ERC165Checker', function () {
|
||||
beforeEach(async function () {
|
||||
this.mock = await ERC165Checker.new();
|
||||
});
|
||||
|
||||
context('ERC165 missing return data', function () {
|
||||
beforeEach(async function () {
|
||||
this.target = await ERC165MissingData.new();
|
||||
});
|
||||
|
||||
it('does not support ERC165', async function () {
|
||||
const supported = await this.mock.$supportsERC165(this.target.address);
|
||||
expect(supported).to.equal(false);
|
||||
});
|
||||
|
||||
it('does not support mock interface via supportsInterface', async function () {
|
||||
const supported = await this.mock.$supportsInterface(this.target.address, DUMMY_ID);
|
||||
expect(supported).to.equal(false);
|
||||
});
|
||||
|
||||
it('does not support mock interface via supportsAllInterfaces', async function () {
|
||||
const supported = await this.mock.$supportsAllInterfaces(this.target.address, [DUMMY_ID]);
|
||||
expect(supported).to.equal(false);
|
||||
});
|
||||
|
||||
it('does not support mock interface via getSupportedInterfaces', async function () {
|
||||
const supported = await this.mock.$getSupportedInterfaces(this.target.address, [DUMMY_ID]);
|
||||
expect(supported.length).to.equal(1);
|
||||
expect(supported[0]).to.equal(false);
|
||||
});
|
||||
|
||||
it('does not support mock interface via supportsERC165InterfaceUnchecked', async function () {
|
||||
const supported = await this.mock.$supportsERC165InterfaceUnchecked(this.target.address, DUMMY_ID);
|
||||
expect(supported).to.equal(false);
|
||||
});
|
||||
});
|
||||
|
||||
context('ERC165 malicious return data', function () {
|
||||
beforeEach(async function () {
|
||||
this.target = await ERC165MaliciousData.new();
|
||||
});
|
||||
|
||||
it('does not support ERC165', async function () {
|
||||
const supported = await this.mock.$supportsERC165(this.target.address);
|
||||
expect(supported).to.equal(false);
|
||||
});
|
||||
|
||||
it('does not support mock interface via supportsInterface', async function () {
|
||||
const supported = await this.mock.$supportsInterface(this.target.address, DUMMY_ID);
|
||||
expect(supported).to.equal(false);
|
||||
});
|
||||
|
||||
it('does not support mock interface via supportsAllInterfaces', async function () {
|
||||
const supported = await this.mock.$supportsAllInterfaces(this.target.address, [DUMMY_ID]);
|
||||
expect(supported).to.equal(false);
|
||||
});
|
||||
|
||||
it('does not support mock interface via getSupportedInterfaces', async function () {
|
||||
const supported = await this.mock.$getSupportedInterfaces(this.target.address, [DUMMY_ID]);
|
||||
expect(supported.length).to.equal(1);
|
||||
expect(supported[0]).to.equal(false);
|
||||
});
|
||||
|
||||
it('does not support mock interface via supportsERC165InterfaceUnchecked', async function () {
|
||||
const supported = await this.mock.$supportsERC165InterfaceUnchecked(this.target.address, DUMMY_ID);
|
||||
expect(supported).to.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
context('ERC165 not supported', function () {
|
||||
beforeEach(async function () {
|
||||
this.target = await ERC165NotSupported.new();
|
||||
});
|
||||
|
||||
it('does not support ERC165', async function () {
|
||||
const supported = await this.mock.$supportsERC165(this.target.address);
|
||||
expect(supported).to.equal(false);
|
||||
});
|
||||
|
||||
it('does not support mock interface via supportsInterface', async function () {
|
||||
const supported = await this.mock.$supportsInterface(this.target.address, DUMMY_ID);
|
||||
expect(supported).to.equal(false);
|
||||
});
|
||||
|
||||
it('does not support mock interface via supportsAllInterfaces', async function () {
|
||||
const supported = await this.mock.$supportsAllInterfaces(this.target.address, [DUMMY_ID]);
|
||||
expect(supported).to.equal(false);
|
||||
});
|
||||
|
||||
it('does not support mock interface via getSupportedInterfaces', async function () {
|
||||
const supported = await this.mock.$getSupportedInterfaces(this.target.address, [DUMMY_ID]);
|
||||
expect(supported.length).to.equal(1);
|
||||
expect(supported[0]).to.equal(false);
|
||||
});
|
||||
|
||||
it('does not support mock interface via supportsERC165InterfaceUnchecked', async function () {
|
||||
const supported = await this.mock.$supportsERC165InterfaceUnchecked(this.target.address, DUMMY_ID);
|
||||
expect(supported).to.equal(false);
|
||||
});
|
||||
});
|
||||
|
||||
context('ERC165 supported', function () {
|
||||
beforeEach(async function () {
|
||||
this.target = await ERC165Storage.new();
|
||||
});
|
||||
|
||||
it('supports ERC165', async function () {
|
||||
const supported = await this.mock.$supportsERC165(this.target.address);
|
||||
expect(supported).to.equal(true);
|
||||
});
|
||||
|
||||
it('does not support mock interface via supportsInterface', async function () {
|
||||
const supported = await this.mock.$supportsInterface(this.target.address, DUMMY_ID);
|
||||
expect(supported).to.equal(false);
|
||||
});
|
||||
|
||||
it('does not support mock interface via supportsAllInterfaces', async function () {
|
||||
const supported = await this.mock.$supportsAllInterfaces(this.target.address, [DUMMY_ID]);
|
||||
expect(supported).to.equal(false);
|
||||
});
|
||||
|
||||
it('does not support mock interface via getSupportedInterfaces', async function () {
|
||||
const supported = await this.mock.$getSupportedInterfaces(this.target.address, [DUMMY_ID]);
|
||||
expect(supported.length).to.equal(1);
|
||||
expect(supported[0]).to.equal(false);
|
||||
});
|
||||
|
||||
it('does not support mock interface via supportsERC165InterfaceUnchecked', async function () {
|
||||
const supported = await this.mock.$supportsERC165InterfaceUnchecked(this.target.address, DUMMY_ID);
|
||||
expect(supported).to.equal(false);
|
||||
});
|
||||
});
|
||||
|
||||
context('ERC165 and single interface supported', function () {
|
||||
beforeEach(async function () {
|
||||
this.target = await ERC165Storage.new();
|
||||
await this.target.$_registerInterface(DUMMY_ID);
|
||||
});
|
||||
|
||||
it('supports ERC165', async function () {
|
||||
const supported = await this.mock.$supportsERC165(this.target.address);
|
||||
expect(supported).to.equal(true);
|
||||
});
|
||||
|
||||
it('supports mock interface via supportsInterface', async function () {
|
||||
const supported = await this.mock.$supportsInterface(this.target.address, DUMMY_ID);
|
||||
expect(supported).to.equal(true);
|
||||
});
|
||||
|
||||
it('supports mock interface via supportsAllInterfaces', async function () {
|
||||
const supported = await this.mock.$supportsAllInterfaces(this.target.address, [DUMMY_ID]);
|
||||
expect(supported).to.equal(true);
|
||||
});
|
||||
|
||||
it('supports mock interface via getSupportedInterfaces', async function () {
|
||||
const supported = await this.mock.$getSupportedInterfaces(this.target.address, [DUMMY_ID]);
|
||||
expect(supported.length).to.equal(1);
|
||||
expect(supported[0]).to.equal(true);
|
||||
});
|
||||
|
||||
it('supports mock interface via supportsERC165InterfaceUnchecked', async function () {
|
||||
const supported = await this.mock.$supportsERC165InterfaceUnchecked(this.target.address, DUMMY_ID);
|
||||
expect(supported).to.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
context('ERC165 and many interfaces supported', function () {
|
||||
beforeEach(async function () {
|
||||
this.supportedInterfaces = [DUMMY_ID, DUMMY_ID_2, DUMMY_ID_3];
|
||||
this.target = await ERC165Storage.new();
|
||||
await Promise.all(this.supportedInterfaces.map(interfaceId => this.target.$_registerInterface(interfaceId)));
|
||||
});
|
||||
|
||||
it('supports ERC165', async function () {
|
||||
const supported = await this.mock.$supportsERC165(this.target.address);
|
||||
expect(supported).to.equal(true);
|
||||
});
|
||||
|
||||
it('supports each interfaceId via supportsInterface', async function () {
|
||||
for (const interfaceId of this.supportedInterfaces) {
|
||||
const supported = await this.mock.$supportsInterface(this.target.address, interfaceId);
|
||||
expect(supported).to.equal(true);
|
||||
}
|
||||
});
|
||||
|
||||
it('supports all interfaceIds via supportsAllInterfaces', async function () {
|
||||
const supported = await this.mock.$supportsAllInterfaces(this.target.address, this.supportedInterfaces);
|
||||
expect(supported).to.equal(true);
|
||||
});
|
||||
|
||||
it('supports none of the interfaces queried via supportsAllInterfaces', async function () {
|
||||
const interfaceIdsToTest = [DUMMY_UNSUPPORTED_ID, DUMMY_UNSUPPORTED_ID_2];
|
||||
|
||||
const supported = await this.mock.$supportsAllInterfaces(this.target.address, interfaceIdsToTest);
|
||||
expect(supported).to.equal(false);
|
||||
});
|
||||
|
||||
it('supports not all of the interfaces queried via supportsAllInterfaces', async function () {
|
||||
const interfaceIdsToTest = [...this.supportedInterfaces, DUMMY_UNSUPPORTED_ID];
|
||||
|
||||
const supported = await this.mock.$supportsAllInterfaces(this.target.address, interfaceIdsToTest);
|
||||
expect(supported).to.equal(false);
|
||||
});
|
||||
|
||||
it('supports all interfaceIds via getSupportedInterfaces', async function () {
|
||||
const supported = await this.mock.$getSupportedInterfaces(this.target.address, this.supportedInterfaces);
|
||||
expect(supported.length).to.equal(3);
|
||||
expect(supported[0]).to.equal(true);
|
||||
expect(supported[1]).to.equal(true);
|
||||
expect(supported[2]).to.equal(true);
|
||||
});
|
||||
|
||||
it('supports none of the interfaces queried via getSupportedInterfaces', async function () {
|
||||
const interfaceIdsToTest = [DUMMY_UNSUPPORTED_ID, DUMMY_UNSUPPORTED_ID_2];
|
||||
|
||||
const supported = await this.mock.$getSupportedInterfaces(this.target.address, interfaceIdsToTest);
|
||||
expect(supported.length).to.equal(2);
|
||||
expect(supported[0]).to.equal(false);
|
||||
expect(supported[1]).to.equal(false);
|
||||
});
|
||||
|
||||
it('supports not all of the interfaces queried via getSupportedInterfaces', async function () {
|
||||
const interfaceIdsToTest = [...this.supportedInterfaces, DUMMY_UNSUPPORTED_ID];
|
||||
|
||||
const supported = await this.mock.$getSupportedInterfaces(this.target.address, interfaceIdsToTest);
|
||||
expect(supported.length).to.equal(4);
|
||||
expect(supported[0]).to.equal(true);
|
||||
expect(supported[1]).to.equal(true);
|
||||
expect(supported[2]).to.equal(true);
|
||||
expect(supported[3]).to.equal(false);
|
||||
});
|
||||
|
||||
it('supports each interfaceId via supportsERC165InterfaceUnchecked', async function () {
|
||||
for (const interfaceId of this.supportedInterfaces) {
|
||||
const supported = await this.mock.$supportsERC165InterfaceUnchecked(this.target.address, interfaceId);
|
||||
expect(supported).to.equal(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
context('account address does not support ERC165', function () {
|
||||
it('does not support ERC165', async function () {
|
||||
const supported = await this.mock.$supportsERC165(DUMMY_ACCOUNT);
|
||||
expect(supported).to.equal(false);
|
||||
});
|
||||
|
||||
it('does not support mock interface via supportsInterface', async function () {
|
||||
const supported = await this.mock.$supportsInterface(DUMMY_ACCOUNT, DUMMY_ID);
|
||||
expect(supported).to.equal(false);
|
||||
});
|
||||
|
||||
it('does not support mock interface via supportsAllInterfaces', async function () {
|
||||
const supported = await this.mock.$supportsAllInterfaces(DUMMY_ACCOUNT, [DUMMY_ID]);
|
||||
expect(supported).to.equal(false);
|
||||
});
|
||||
|
||||
it('does not support mock interface via getSupportedInterfaces', async function () {
|
||||
const supported = await this.mock.$getSupportedInterfaces(DUMMY_ACCOUNT, [DUMMY_ID]);
|
||||
expect(supported.length).to.equal(1);
|
||||
expect(supported[0]).to.equal(false);
|
||||
});
|
||||
|
||||
it('does not support mock interface via supportsERC165InterfaceUnchecked', async function () {
|
||||
const supported = await this.mock.$supportsERC165InterfaceUnchecked(DUMMY_ACCOUNT, DUMMY_ID);
|
||||
expect(supported).to.equal(false);
|
||||
});
|
||||
});
|
||||
|
||||
it('Return bomb resistance', async function () {
|
||||
this.target = await ERC165ReturnBombMock.new();
|
||||
|
||||
const tx1 = await this.mock.$supportsInterface.sendTransaction(this.target.address, DUMMY_ID);
|
||||
expect(tx1.receipt.gasUsed).to.be.lessThan(120000); // 3*30k + 21k + some margin
|
||||
|
||||
const tx2 = await this.mock.$getSupportedInterfaces.sendTransaction(this.target.address, [
|
||||
DUMMY_ID,
|
||||
DUMMY_ID_2,
|
||||
DUMMY_ID_3,
|
||||
DUMMY_UNSUPPORTED_ID,
|
||||
DUMMY_UNSUPPORTED_ID_2,
|
||||
]);
|
||||
expect(tx2.receipt.gasUsed).to.be.lessThan(250000); // (2+5)*30k + 21k + some margin
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,23 @@
|
||||
const { expectRevert } = require('@openzeppelin/test-helpers');
|
||||
|
||||
const { shouldSupportInterfaces } = require('./SupportsInterface.behavior');
|
||||
|
||||
const ERC165Storage = artifacts.require('$ERC165Storage');
|
||||
|
||||
contract('ERC165Storage', function () {
|
||||
beforeEach(async function () {
|
||||
this.mock = await ERC165Storage.new();
|
||||
});
|
||||
|
||||
it('register interface', async function () {
|
||||
expect(await this.mock.supportsInterface('0x00000001')).to.be.equal(false);
|
||||
await this.mock.$_registerInterface('0x00000001');
|
||||
expect(await this.mock.supportsInterface('0x00000001')).to.be.equal(true);
|
||||
});
|
||||
|
||||
it('does not allow 0xffffffff', async function () {
|
||||
await expectRevert(this.mock.$_registerInterface('0xffffffff'), 'ERC165: invalid interface id');
|
||||
});
|
||||
|
||||
shouldSupportInterfaces(['ERC165']);
|
||||
});
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
const { expectRevert, singletons } = require('@openzeppelin/test-helpers');
|
||||
const { bufferToHex, keccakFromString } = require('ethereumjs-util');
|
||||
|
||||
const { expect } = require('chai');
|
||||
|
||||
const ERC1820Implementer = artifacts.require('$ERC1820Implementer');
|
||||
|
||||
contract('ERC1820Implementer', function (accounts) {
|
||||
const [registryFunder, implementee, other] = accounts;
|
||||
|
||||
const ERC1820_ACCEPT_MAGIC = bufferToHex(keccakFromString('ERC1820_ACCEPT_MAGIC'));
|
||||
|
||||
beforeEach(async function () {
|
||||
this.implementer = await ERC1820Implementer.new();
|
||||
this.registry = await singletons.ERC1820Registry(registryFunder);
|
||||
|
||||
this.interfaceA = bufferToHex(keccakFromString('interfaceA'));
|
||||
this.interfaceB = bufferToHex(keccakFromString('interfaceB'));
|
||||
});
|
||||
|
||||
context('with no registered interfaces', function () {
|
||||
it('returns false when interface implementation is queried', async function () {
|
||||
expect(await this.implementer.canImplementInterfaceForAddress(this.interfaceA, implementee)).to.not.equal(
|
||||
ERC1820_ACCEPT_MAGIC,
|
||||
);
|
||||
});
|
||||
|
||||
it('reverts when attempting to set as implementer in the registry', async function () {
|
||||
await expectRevert(
|
||||
this.registry.setInterfaceImplementer(implementee, this.interfaceA, this.implementer.address, {
|
||||
from: implementee,
|
||||
}),
|
||||
'Does not implement the interface',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
context('with registered interfaces', function () {
|
||||
beforeEach(async function () {
|
||||
await this.implementer.$_registerInterfaceForAddress(this.interfaceA, implementee);
|
||||
});
|
||||
|
||||
it('returns true when interface implementation is queried', async function () {
|
||||
expect(await this.implementer.canImplementInterfaceForAddress(this.interfaceA, implementee)).to.equal(
|
||||
ERC1820_ACCEPT_MAGIC,
|
||||
);
|
||||
});
|
||||
|
||||
it('returns false when interface implementation for non-supported interfaces is queried', async function () {
|
||||
expect(await this.implementer.canImplementInterfaceForAddress(this.interfaceB, implementee)).to.not.equal(
|
||||
ERC1820_ACCEPT_MAGIC,
|
||||
);
|
||||
});
|
||||
|
||||
it('returns false when interface implementation for non-supported addresses is queried', async function () {
|
||||
expect(await this.implementer.canImplementInterfaceForAddress(this.interfaceA, other)).to.not.equal(
|
||||
ERC1820_ACCEPT_MAGIC,
|
||||
);
|
||||
});
|
||||
|
||||
it('can be set as an implementer for supported interfaces in the registry', async function () {
|
||||
await this.registry.setInterfaceImplementer(implementee, this.interfaceA, this.implementer.address, {
|
||||
from: implementee,
|
||||
});
|
||||
|
||||
expect(await this.registry.getInterfaceImplementer(implementee, this.interfaceA)).to.equal(
|
||||
this.implementer.address,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
const { makeInterfaceId } = require('@openzeppelin/test-helpers');
|
||||
|
||||
const { expect } = require('chai');
|
||||
|
||||
const INTERFACES = {
|
||||
ERC165: ['supportsInterface(bytes4)'],
|
||||
ERC721: [
|
||||
'balanceOf(address)',
|
||||
'ownerOf(uint256)',
|
||||
'approve(address,uint256)',
|
||||
'getApproved(uint256)',
|
||||
'setApprovalForAll(address,bool)',
|
||||
'isApprovedForAll(address,address)',
|
||||
'transferFrom(address,address,uint256)',
|
||||
'safeTransferFrom(address,address,uint256)',
|
||||
'safeTransferFrom(address,address,uint256,bytes)',
|
||||
],
|
||||
ERC721Enumerable: ['totalSupply()', 'tokenOfOwnerByIndex(address,uint256)', 'tokenByIndex(uint256)'],
|
||||
ERC721Metadata: ['name()', 'symbol()', 'tokenURI(uint256)'],
|
||||
ERC1155: [
|
||||
'balanceOf(address,uint256)',
|
||||
'balanceOfBatch(address[],uint256[])',
|
||||
'setApprovalForAll(address,bool)',
|
||||
'isApprovedForAll(address,address)',
|
||||
'safeTransferFrom(address,address,uint256,uint256,bytes)',
|
||||
'safeBatchTransferFrom(address,address,uint256[],uint256[],bytes)',
|
||||
],
|
||||
ERC1155Receiver: [
|
||||
'onERC1155Received(address,address,uint256,uint256,bytes)',
|
||||
'onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)',
|
||||
],
|
||||
AccessControl: [
|
||||
'hasRole(bytes32,address)',
|
||||
'getRoleAdmin(bytes32)',
|
||||
'grantRole(bytes32,address)',
|
||||
'revokeRole(bytes32,address)',
|
||||
'renounceRole(bytes32,address)',
|
||||
],
|
||||
AccessControlEnumerable: ['getRoleMember(bytes32,uint256)', 'getRoleMemberCount(bytes32)'],
|
||||
AccessControlDefaultAdminRules: [
|
||||
'defaultAdminDelay()',
|
||||
'pendingDefaultAdminDelay()',
|
||||
'defaultAdmin()',
|
||||
'pendingDefaultAdmin()',
|
||||
'defaultAdminDelayIncreaseWait()',
|
||||
'changeDefaultAdminDelay(uint48)',
|
||||
'rollbackDefaultAdminDelay()',
|
||||
'beginDefaultAdminTransfer(address)',
|
||||
'acceptDefaultAdminTransfer()',
|
||||
'cancelDefaultAdminTransfer()',
|
||||
],
|
||||
Governor: [
|
||||
'name()',
|
||||
'version()',
|
||||
'COUNTING_MODE()',
|
||||
'hashProposal(address[],uint256[],bytes[],bytes32)',
|
||||
'state(uint256)',
|
||||
'proposalSnapshot(uint256)',
|
||||
'proposalDeadline(uint256)',
|
||||
'votingDelay()',
|
||||
'votingPeriod()',
|
||||
'quorum(uint256)',
|
||||
'getVotes(address,uint256)',
|
||||
'hasVoted(uint256,address)',
|
||||
'propose(address[],uint256[],bytes[],string)',
|
||||
'execute(address[],uint256[],bytes[],bytes32)',
|
||||
'castVote(uint256,uint8)',
|
||||
'castVoteWithReason(uint256,uint8,string)',
|
||||
'castVoteBySig(uint256,uint8,uint8,bytes32,bytes32)',
|
||||
],
|
||||
GovernorWithParams: [
|
||||
'name()',
|
||||
'version()',
|
||||
'COUNTING_MODE()',
|
||||
'hashProposal(address[],uint256[],bytes[],bytes32)',
|
||||
'state(uint256)',
|
||||
'proposalSnapshot(uint256)',
|
||||
'proposalDeadline(uint256)',
|
||||
'votingDelay()',
|
||||
'votingPeriod()',
|
||||
'quorum(uint256)',
|
||||
'getVotes(address,uint256)',
|
||||
'getVotesWithParams(address,uint256,bytes)',
|
||||
'hasVoted(uint256,address)',
|
||||
'propose(address[],uint256[],bytes[],string)',
|
||||
'execute(address[],uint256[],bytes[],bytes32)',
|
||||
'castVote(uint256,uint8)',
|
||||
'castVoteWithReason(uint256,uint8,string)',
|
||||
'castVoteWithReasonAndParams(uint256,uint8,string,bytes)',
|
||||
'castVoteBySig(uint256,uint8,uint8,bytes32,bytes32)',
|
||||
'castVoteWithReasonAndParamsBySig(uint256,uint8,string,bytes,uint8,bytes32,bytes32)',
|
||||
],
|
||||
GovernorCancel: ['proposalProposer(uint256)', 'cancel(address[],uint256[],bytes[],bytes32)'],
|
||||
GovernorTimelock: ['timelock()', 'proposalEta(uint256)', 'queue(address[],uint256[],bytes[],bytes32)'],
|
||||
ERC2981: ['royaltyInfo(uint256,uint256)'],
|
||||
};
|
||||
|
||||
const INTERFACE_IDS = {};
|
||||
const FN_SIGNATURES = {};
|
||||
for (const k of Object.getOwnPropertyNames(INTERFACES)) {
|
||||
INTERFACE_IDS[k] = makeInterfaceId.ERC165(INTERFACES[k]);
|
||||
for (const fnName of INTERFACES[k]) {
|
||||
// the interface id of a single function is equivalent to its function signature
|
||||
FN_SIGNATURES[fnName] = makeInterfaceId.ERC165([fnName]);
|
||||
}
|
||||
}
|
||||
|
||||
function shouldSupportInterfaces(interfaces = []) {
|
||||
describe('ERC165', function () {
|
||||
beforeEach(function () {
|
||||
this.contractUnderTest = this.mock || this.token || this.holder || this.accessControl;
|
||||
});
|
||||
|
||||
it('supportsInterface uses less than 30k gas', async function () {
|
||||
for (const k of interfaces) {
|
||||
const interfaceId = INTERFACE_IDS[k] ?? k;
|
||||
expect(await this.contractUnderTest.supportsInterface.estimateGas(interfaceId)).to.be.lte(30000);
|
||||
}
|
||||
});
|
||||
|
||||
it('all interfaces are reported as supported', async function () {
|
||||
for (const k of interfaces) {
|
||||
const interfaceId = INTERFACE_IDS[k] ?? k;
|
||||
expect(await this.contractUnderTest.supportsInterface(interfaceId)).to.equal(true, `does not support ${k}`);
|
||||
}
|
||||
});
|
||||
|
||||
it('all interface functions are in ABI', async function () {
|
||||
for (const k of interfaces) {
|
||||
// skip interfaces for which we don't have a function list
|
||||
if (INTERFACES[k] === undefined) continue;
|
||||
for (const fnName of INTERFACES[k]) {
|
||||
const fnSig = FN_SIGNATURES[fnName];
|
||||
expect(this.contractUnderTest.abi.filter(fn => fn.signature === fnSig).length).to.equal(
|
||||
1,
|
||||
`did not find ${fnName}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
shouldSupportInterfaces,
|
||||
};
|
||||
@@ -0,0 +1,217 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import "forge-std/Test.sol";
|
||||
|
||||
import "../../../contracts/utils/math/Math.sol";
|
||||
import "../../../contracts/utils/math/SafeMath.sol";
|
||||
|
||||
contract MathTest is Test {
|
||||
// CEILDIV
|
||||
function testCeilDiv(uint256 a, uint256 b) public {
|
||||
vm.assume(b > 0);
|
||||
|
||||
uint256 result = Math.ceilDiv(a, b);
|
||||
|
||||
if (result == 0) {
|
||||
assertEq(a, 0);
|
||||
} else {
|
||||
uint256 maxdiv = UINT256_MAX / b;
|
||||
bool overflow = maxdiv * b < a;
|
||||
assertTrue(a > b * (result - 1));
|
||||
assertTrue(overflow ? result == maxdiv + 1 : a <= b * result);
|
||||
}
|
||||
}
|
||||
|
||||
// SQRT
|
||||
function testSqrt(uint256 input, uint8 r) public {
|
||||
Math.Rounding rounding = _asRounding(r);
|
||||
|
||||
uint256 result = Math.sqrt(input, rounding);
|
||||
|
||||
// square of result is bigger than input
|
||||
if (_squareBigger(result, input)) {
|
||||
assertTrue(rounding == Math.Rounding.Up);
|
||||
assertTrue(_squareSmaller(result - 1, input));
|
||||
}
|
||||
// square of result is smaller than input
|
||||
else if (_squareSmaller(result, input)) {
|
||||
assertFalse(rounding == Math.Rounding.Up);
|
||||
assertTrue(_squareBigger(result + 1, input));
|
||||
}
|
||||
// input is perfect square
|
||||
else {
|
||||
assertEq(result * result, input);
|
||||
}
|
||||
}
|
||||
|
||||
function _squareBigger(uint256 value, uint256 ref) private pure returns (bool) {
|
||||
(bool noOverflow, uint256 square) = SafeMath.tryMul(value, value);
|
||||
return !noOverflow || square > ref;
|
||||
}
|
||||
|
||||
function _squareSmaller(uint256 value, uint256 ref) private pure returns (bool) {
|
||||
return value * value < ref;
|
||||
}
|
||||
|
||||
// LOG2
|
||||
function testLog2(uint256 input, uint8 r) public {
|
||||
Math.Rounding rounding = _asRounding(r);
|
||||
|
||||
uint256 result = Math.log2(input, rounding);
|
||||
|
||||
if (input == 0) {
|
||||
assertEq(result, 0);
|
||||
} else if (_powerOf2Bigger(result, input)) {
|
||||
assertTrue(rounding == Math.Rounding.Up);
|
||||
assertTrue(_powerOf2Smaller(result - 1, input));
|
||||
} else if (_powerOf2Smaller(result, input)) {
|
||||
assertFalse(rounding == Math.Rounding.Up);
|
||||
assertTrue(_powerOf2Bigger(result + 1, input));
|
||||
} else {
|
||||
assertEq(2 ** result, input);
|
||||
}
|
||||
}
|
||||
|
||||
function _powerOf2Bigger(uint256 value, uint256 ref) private pure returns (bool) {
|
||||
return value >= 256 || 2 ** value > ref; // 2**256 overflows uint256
|
||||
}
|
||||
|
||||
function _powerOf2Smaller(uint256 value, uint256 ref) private pure returns (bool) {
|
||||
return 2 ** value < ref;
|
||||
}
|
||||
|
||||
// LOG10
|
||||
function testLog10(uint256 input, uint8 r) public {
|
||||
Math.Rounding rounding = _asRounding(r);
|
||||
|
||||
uint256 result = Math.log10(input, rounding);
|
||||
|
||||
if (input == 0) {
|
||||
assertEq(result, 0);
|
||||
} else if (_powerOf10Bigger(result, input)) {
|
||||
assertTrue(rounding == Math.Rounding.Up);
|
||||
assertTrue(_powerOf10Smaller(result - 1, input));
|
||||
} else if (_powerOf10Smaller(result, input)) {
|
||||
assertFalse(rounding == Math.Rounding.Up);
|
||||
assertTrue(_powerOf10Bigger(result + 1, input));
|
||||
} else {
|
||||
assertEq(10 ** result, input);
|
||||
}
|
||||
}
|
||||
|
||||
function _powerOf10Bigger(uint256 value, uint256 ref) private pure returns (bool) {
|
||||
return value >= 78 || 10 ** value > ref; // 10**78 overflows uint256
|
||||
}
|
||||
|
||||
function _powerOf10Smaller(uint256 value, uint256 ref) private pure returns (bool) {
|
||||
return 10 ** value < ref;
|
||||
}
|
||||
|
||||
// LOG256
|
||||
function testLog256(uint256 input, uint8 r) public {
|
||||
Math.Rounding rounding = _asRounding(r);
|
||||
|
||||
uint256 result = Math.log256(input, rounding);
|
||||
|
||||
if (input == 0) {
|
||||
assertEq(result, 0);
|
||||
} else if (_powerOf256Bigger(result, input)) {
|
||||
assertTrue(rounding == Math.Rounding.Up);
|
||||
assertTrue(_powerOf256Smaller(result - 1, input));
|
||||
} else if (_powerOf256Smaller(result, input)) {
|
||||
assertFalse(rounding == Math.Rounding.Up);
|
||||
assertTrue(_powerOf256Bigger(result + 1, input));
|
||||
} else {
|
||||
assertEq(256 ** result, input);
|
||||
}
|
||||
}
|
||||
|
||||
function _powerOf256Bigger(uint256 value, uint256 ref) private pure returns (bool) {
|
||||
return value >= 32 || 256 ** value > ref; // 256**32 overflows uint256
|
||||
}
|
||||
|
||||
function _powerOf256Smaller(uint256 value, uint256 ref) private pure returns (bool) {
|
||||
return 256 ** value < ref;
|
||||
}
|
||||
|
||||
// MULDIV
|
||||
function testMulDiv(uint256 x, uint256 y, uint256 d) public {
|
||||
// Full precision for x * y
|
||||
(uint256 xyHi, uint256 xyLo) = _mulHighLow(x, y);
|
||||
|
||||
// Assume result won't overflow (see {testMulDivDomain})
|
||||
// This also checks that `d` is positive
|
||||
vm.assume(xyHi < d);
|
||||
|
||||
// Perform muldiv
|
||||
uint256 q = Math.mulDiv(x, y, d);
|
||||
|
||||
// Full precision for q * d
|
||||
(uint256 qdHi, uint256 qdLo) = _mulHighLow(q, d);
|
||||
// Add remainder of x * y / d (computed as rem = (x * y % d))
|
||||
(uint256 qdRemLo, uint256 c) = _addCarry(qdLo, _mulmod(x, y, d));
|
||||
uint256 qdRemHi = qdHi + c;
|
||||
|
||||
// Full precision check that x * y = q * d + rem
|
||||
assertEq(xyHi, qdRemHi);
|
||||
assertEq(xyLo, qdRemLo);
|
||||
}
|
||||
|
||||
function testMulDivDomain(uint256 x, uint256 y, uint256 d) public {
|
||||
(uint256 xyHi, ) = _mulHighLow(x, y);
|
||||
|
||||
// Violate {testMulDiv} assumption (covers d is 0 and result overflow)
|
||||
vm.assume(xyHi >= d);
|
||||
|
||||
// we are outside the scope of {testMulDiv}, we expect muldiv to revert
|
||||
try this.muldiv(x, y, d) returns (uint256) {
|
||||
fail();
|
||||
} catch {}
|
||||
}
|
||||
|
||||
// External call
|
||||
function muldiv(uint256 x, uint256 y, uint256 d) external pure returns (uint256) {
|
||||
return Math.mulDiv(x, y, d);
|
||||
}
|
||||
|
||||
// Helpers
|
||||
function _asRounding(uint8 r) private pure returns (Math.Rounding) {
|
||||
vm.assume(r < uint8(type(Math.Rounding).max));
|
||||
return Math.Rounding(r);
|
||||
}
|
||||
|
||||
function _mulmod(uint256 x, uint256 y, uint256 z) private pure returns (uint256 r) {
|
||||
assembly {
|
||||
r := mulmod(x, y, z)
|
||||
}
|
||||
}
|
||||
|
||||
function _mulHighLow(uint256 x, uint256 y) private pure returns (uint256 high, uint256 low) {
|
||||
(uint256 x0, uint256 x1) = (x & type(uint128).max, x >> 128);
|
||||
(uint256 y0, uint256 y1) = (y & type(uint128).max, y >> 128);
|
||||
|
||||
// Karatsuba algorithm
|
||||
// https://en.wikipedia.org/wiki/Karatsuba_algorithm
|
||||
uint256 z2 = x1 * y1;
|
||||
uint256 z1a = x1 * y0;
|
||||
uint256 z1b = x0 * y1;
|
||||
uint256 z0 = x0 * y0;
|
||||
|
||||
uint256 carry = ((z1a & type(uint128).max) + (z1b & type(uint128).max) + (z0 >> 128)) >> 128;
|
||||
|
||||
high = z2 + (z1a >> 128) + (z1b >> 128) + carry;
|
||||
|
||||
unchecked {
|
||||
low = x * y;
|
||||
}
|
||||
}
|
||||
|
||||
function _addCarry(uint256 x, uint256 y) private pure returns (uint256 res, uint256 carry) {
|
||||
unchecked {
|
||||
res = x + y;
|
||||
}
|
||||
carry = res < x ? 1 : 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,289 @@
|
||||
const { BN, constants, expectRevert } = require('@openzeppelin/test-helpers');
|
||||
const { expect } = require('chai');
|
||||
const { MAX_UINT256 } = constants;
|
||||
const { Rounding } = require('../../helpers/enums.js');
|
||||
|
||||
const Math = artifacts.require('$Math');
|
||||
|
||||
contract('Math', function () {
|
||||
const min = new BN('1234');
|
||||
const max = new BN('5678');
|
||||
const MAX_UINT256_SUB1 = MAX_UINT256.sub(new BN('1'));
|
||||
const MAX_UINT256_SUB2 = MAX_UINT256.sub(new BN('2'));
|
||||
|
||||
beforeEach(async function () {
|
||||
this.math = await Math.new();
|
||||
});
|
||||
|
||||
describe('max', function () {
|
||||
it('is correctly detected in first argument position', async function () {
|
||||
expect(await this.math.$max(max, min)).to.be.bignumber.equal(max);
|
||||
});
|
||||
|
||||
it('is correctly detected in second argument position', async function () {
|
||||
expect(await this.math.$max(min, max)).to.be.bignumber.equal(max);
|
||||
});
|
||||
});
|
||||
|
||||
describe('min', function () {
|
||||
it('is correctly detected in first argument position', async function () {
|
||||
expect(await this.math.$min(min, max)).to.be.bignumber.equal(min);
|
||||
});
|
||||
|
||||
it('is correctly detected in second argument position', async function () {
|
||||
expect(await this.math.$min(max, min)).to.be.bignumber.equal(min);
|
||||
});
|
||||
});
|
||||
|
||||
describe('average', function () {
|
||||
function bnAverage(a, b) {
|
||||
return a.add(b).divn(2);
|
||||
}
|
||||
|
||||
it('is correctly calculated with two odd numbers', async function () {
|
||||
const a = new BN('57417');
|
||||
const b = new BN('95431');
|
||||
expect(await this.math.$average(a, b)).to.be.bignumber.equal(bnAverage(a, b));
|
||||
});
|
||||
|
||||
it('is correctly calculated with two even numbers', async function () {
|
||||
const a = new BN('42304');
|
||||
const b = new BN('84346');
|
||||
expect(await this.math.$average(a, b)).to.be.bignumber.equal(bnAverage(a, b));
|
||||
});
|
||||
|
||||
it('is correctly calculated with one even and one odd number', async function () {
|
||||
const a = new BN('57417');
|
||||
const b = new BN('84346');
|
||||
expect(await this.math.$average(a, b)).to.be.bignumber.equal(bnAverage(a, b));
|
||||
});
|
||||
|
||||
it('is correctly calculated with two max uint256 numbers', async function () {
|
||||
const a = MAX_UINT256;
|
||||
expect(await this.math.$average(a, a)).to.be.bignumber.equal(bnAverage(a, a));
|
||||
});
|
||||
});
|
||||
|
||||
describe('ceilDiv', function () {
|
||||
it('does not round up on exact division', async function () {
|
||||
const a = new BN('10');
|
||||
const b = new BN('5');
|
||||
expect(await this.math.$ceilDiv(a, b)).to.be.bignumber.equal('2');
|
||||
});
|
||||
|
||||
it('rounds up on division with remainders', async function () {
|
||||
const a = new BN('42');
|
||||
const b = new BN('13');
|
||||
expect(await this.math.$ceilDiv(a, b)).to.be.bignumber.equal('4');
|
||||
});
|
||||
|
||||
it('does not overflow', async function () {
|
||||
const b = new BN('2');
|
||||
const result = new BN('1').shln(255);
|
||||
expect(await this.math.$ceilDiv(MAX_UINT256, b)).to.be.bignumber.equal(result);
|
||||
});
|
||||
|
||||
it('correctly computes max uint256 divided by 1', async function () {
|
||||
const b = new BN('1');
|
||||
expect(await this.math.$ceilDiv(MAX_UINT256, b)).to.be.bignumber.equal(MAX_UINT256);
|
||||
});
|
||||
});
|
||||
|
||||
describe('muldiv', function () {
|
||||
it('divide by 0', async function () {
|
||||
await expectRevert.unspecified(this.math.$mulDiv(1, 1, 0, Rounding.Down));
|
||||
});
|
||||
|
||||
describe('does round down', async function () {
|
||||
it('small values', async function () {
|
||||
expect(await this.math.$mulDiv('3', '4', '5', Rounding.Down)).to.be.bignumber.equal('2');
|
||||
expect(await this.math.$mulDiv('3', '5', '5', Rounding.Down)).to.be.bignumber.equal('3');
|
||||
});
|
||||
|
||||
it('large values', async function () {
|
||||
expect(
|
||||
await this.math.$mulDiv(new BN('42'), MAX_UINT256_SUB1, MAX_UINT256, Rounding.Down),
|
||||
).to.be.bignumber.equal(new BN('41'));
|
||||
|
||||
expect(await this.math.$mulDiv(new BN('17'), MAX_UINT256, MAX_UINT256, Rounding.Down)).to.be.bignumber.equal(
|
||||
new BN('17'),
|
||||
);
|
||||
|
||||
expect(
|
||||
await this.math.$mulDiv(MAX_UINT256_SUB1, MAX_UINT256_SUB1, MAX_UINT256, Rounding.Down),
|
||||
).to.be.bignumber.equal(MAX_UINT256_SUB2);
|
||||
|
||||
expect(
|
||||
await this.math.$mulDiv(MAX_UINT256, MAX_UINT256_SUB1, MAX_UINT256, Rounding.Down),
|
||||
).to.be.bignumber.equal(MAX_UINT256_SUB1);
|
||||
|
||||
expect(await this.math.$mulDiv(MAX_UINT256, MAX_UINT256, MAX_UINT256, Rounding.Down)).to.be.bignumber.equal(
|
||||
MAX_UINT256,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('does round up', async function () {
|
||||
it('small values', async function () {
|
||||
expect(await this.math.$mulDiv('3', '4', '5', Rounding.Up)).to.be.bignumber.equal('3');
|
||||
expect(await this.math.$mulDiv('3', '5', '5', Rounding.Up)).to.be.bignumber.equal('3');
|
||||
});
|
||||
|
||||
it('large values', async function () {
|
||||
expect(await this.math.$mulDiv(new BN('42'), MAX_UINT256_SUB1, MAX_UINT256, Rounding.Up)).to.be.bignumber.equal(
|
||||
new BN('42'),
|
||||
);
|
||||
|
||||
expect(await this.math.$mulDiv(new BN('17'), MAX_UINT256, MAX_UINT256, Rounding.Up)).to.be.bignumber.equal(
|
||||
new BN('17'),
|
||||
);
|
||||
|
||||
expect(
|
||||
await this.math.$mulDiv(MAX_UINT256_SUB1, MAX_UINT256_SUB1, MAX_UINT256, Rounding.Up),
|
||||
).to.be.bignumber.equal(MAX_UINT256_SUB1);
|
||||
|
||||
expect(await this.math.$mulDiv(MAX_UINT256, MAX_UINT256_SUB1, MAX_UINT256, Rounding.Up)).to.be.bignumber.equal(
|
||||
MAX_UINT256_SUB1,
|
||||
);
|
||||
|
||||
expect(await this.math.$mulDiv(MAX_UINT256, MAX_UINT256, MAX_UINT256, Rounding.Up)).to.be.bignumber.equal(
|
||||
MAX_UINT256,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('sqrt', function () {
|
||||
it('rounds down', async function () {
|
||||
expect(await this.math.$sqrt('0', Rounding.Down)).to.be.bignumber.equal('0');
|
||||
expect(await this.math.$sqrt('1', Rounding.Down)).to.be.bignumber.equal('1');
|
||||
expect(await this.math.$sqrt('2', Rounding.Down)).to.be.bignumber.equal('1');
|
||||
expect(await this.math.$sqrt('3', Rounding.Down)).to.be.bignumber.equal('1');
|
||||
expect(await this.math.$sqrt('4', Rounding.Down)).to.be.bignumber.equal('2');
|
||||
expect(await this.math.$sqrt('144', Rounding.Down)).to.be.bignumber.equal('12');
|
||||
expect(await this.math.$sqrt('999999', Rounding.Down)).to.be.bignumber.equal('999');
|
||||
expect(await this.math.$sqrt('1000000', Rounding.Down)).to.be.bignumber.equal('1000');
|
||||
expect(await this.math.$sqrt('1000001', Rounding.Down)).to.be.bignumber.equal('1000');
|
||||
expect(await this.math.$sqrt('1002000', Rounding.Down)).to.be.bignumber.equal('1000');
|
||||
expect(await this.math.$sqrt('1002001', Rounding.Down)).to.be.bignumber.equal('1001');
|
||||
expect(await this.math.$sqrt(MAX_UINT256, Rounding.Down)).to.be.bignumber.equal(
|
||||
'340282366920938463463374607431768211455',
|
||||
);
|
||||
});
|
||||
|
||||
it('rounds up', async function () {
|
||||
expect(await this.math.$sqrt('0', Rounding.Up)).to.be.bignumber.equal('0');
|
||||
expect(await this.math.$sqrt('1', Rounding.Up)).to.be.bignumber.equal('1');
|
||||
expect(await this.math.$sqrt('2', Rounding.Up)).to.be.bignumber.equal('2');
|
||||
expect(await this.math.$sqrt('3', Rounding.Up)).to.be.bignumber.equal('2');
|
||||
expect(await this.math.$sqrt('4', Rounding.Up)).to.be.bignumber.equal('2');
|
||||
expect(await this.math.$sqrt('144', Rounding.Up)).to.be.bignumber.equal('12');
|
||||
expect(await this.math.$sqrt('999999', Rounding.Up)).to.be.bignumber.equal('1000');
|
||||
expect(await this.math.$sqrt('1000000', Rounding.Up)).to.be.bignumber.equal('1000');
|
||||
expect(await this.math.$sqrt('1000001', Rounding.Up)).to.be.bignumber.equal('1001');
|
||||
expect(await this.math.$sqrt('1002000', Rounding.Up)).to.be.bignumber.equal('1001');
|
||||
expect(await this.math.$sqrt('1002001', Rounding.Up)).to.be.bignumber.equal('1001');
|
||||
expect(await this.math.$sqrt(MAX_UINT256, Rounding.Up)).to.be.bignumber.equal(
|
||||
'340282366920938463463374607431768211456',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('log', function () {
|
||||
describe('log2', function () {
|
||||
it('rounds down', async function () {
|
||||
// For some reason calling .$log2() directly fails
|
||||
expect(await this.math.methods['$log2(uint256,uint8)']('0', Rounding.Down)).to.be.bignumber.equal('0');
|
||||
expect(await this.math.methods['$log2(uint256,uint8)']('1', Rounding.Down)).to.be.bignumber.equal('0');
|
||||
expect(await this.math.methods['$log2(uint256,uint8)']('2', Rounding.Down)).to.be.bignumber.equal('1');
|
||||
expect(await this.math.methods['$log2(uint256,uint8)']('3', Rounding.Down)).to.be.bignumber.equal('1');
|
||||
expect(await this.math.methods['$log2(uint256,uint8)']('4', Rounding.Down)).to.be.bignumber.equal('2');
|
||||
expect(await this.math.methods['$log2(uint256,uint8)']('5', Rounding.Down)).to.be.bignumber.equal('2');
|
||||
expect(await this.math.methods['$log2(uint256,uint8)']('6', Rounding.Down)).to.be.bignumber.equal('2');
|
||||
expect(await this.math.methods['$log2(uint256,uint8)']('7', Rounding.Down)).to.be.bignumber.equal('2');
|
||||
expect(await this.math.methods['$log2(uint256,uint8)']('8', Rounding.Down)).to.be.bignumber.equal('3');
|
||||
expect(await this.math.methods['$log2(uint256,uint8)']('9', Rounding.Down)).to.be.bignumber.equal('3');
|
||||
expect(await this.math.methods['$log2(uint256,uint8)'](MAX_UINT256, Rounding.Down)).to.be.bignumber.equal(
|
||||
'255',
|
||||
);
|
||||
});
|
||||
|
||||
it('rounds up', async function () {
|
||||
// For some reason calling .$log2() directly fails
|
||||
expect(await this.math.methods['$log2(uint256,uint8)']('0', Rounding.Up)).to.be.bignumber.equal('0');
|
||||
expect(await this.math.methods['$log2(uint256,uint8)']('1', Rounding.Up)).to.be.bignumber.equal('0');
|
||||
expect(await this.math.methods['$log2(uint256,uint8)']('2', Rounding.Up)).to.be.bignumber.equal('1');
|
||||
expect(await this.math.methods['$log2(uint256,uint8)']('3', Rounding.Up)).to.be.bignumber.equal('2');
|
||||
expect(await this.math.methods['$log2(uint256,uint8)']('4', Rounding.Up)).to.be.bignumber.equal('2');
|
||||
expect(await this.math.methods['$log2(uint256,uint8)']('5', Rounding.Up)).to.be.bignumber.equal('3');
|
||||
expect(await this.math.methods['$log2(uint256,uint8)']('6', Rounding.Up)).to.be.bignumber.equal('3');
|
||||
expect(await this.math.methods['$log2(uint256,uint8)']('7', Rounding.Up)).to.be.bignumber.equal('3');
|
||||
expect(await this.math.methods['$log2(uint256,uint8)']('8', Rounding.Up)).to.be.bignumber.equal('3');
|
||||
expect(await this.math.methods['$log2(uint256,uint8)']('9', Rounding.Up)).to.be.bignumber.equal('4');
|
||||
expect(await this.math.methods['$log2(uint256,uint8)'](MAX_UINT256, Rounding.Up)).to.be.bignumber.equal('256');
|
||||
});
|
||||
});
|
||||
|
||||
describe('log10', function () {
|
||||
it('rounds down', async function () {
|
||||
expect(await this.math.$log10('0', Rounding.Down)).to.be.bignumber.equal('0');
|
||||
expect(await this.math.$log10('1', Rounding.Down)).to.be.bignumber.equal('0');
|
||||
expect(await this.math.$log10('2', Rounding.Down)).to.be.bignumber.equal('0');
|
||||
expect(await this.math.$log10('9', Rounding.Down)).to.be.bignumber.equal('0');
|
||||
expect(await this.math.$log10('10', Rounding.Down)).to.be.bignumber.equal('1');
|
||||
expect(await this.math.$log10('11', Rounding.Down)).to.be.bignumber.equal('1');
|
||||
expect(await this.math.$log10('99', Rounding.Down)).to.be.bignumber.equal('1');
|
||||
expect(await this.math.$log10('100', Rounding.Down)).to.be.bignumber.equal('2');
|
||||
expect(await this.math.$log10('101', Rounding.Down)).to.be.bignumber.equal('2');
|
||||
expect(await this.math.$log10('999', Rounding.Down)).to.be.bignumber.equal('2');
|
||||
expect(await this.math.$log10('1000', Rounding.Down)).to.be.bignumber.equal('3');
|
||||
expect(await this.math.$log10('1001', Rounding.Down)).to.be.bignumber.equal('3');
|
||||
expect(await this.math.$log10(MAX_UINT256, Rounding.Down)).to.be.bignumber.equal('77');
|
||||
});
|
||||
|
||||
it('rounds up', async function () {
|
||||
expect(await this.math.$log10('0', Rounding.Up)).to.be.bignumber.equal('0');
|
||||
expect(await this.math.$log10('1', Rounding.Up)).to.be.bignumber.equal('0');
|
||||
expect(await this.math.$log10('2', Rounding.Up)).to.be.bignumber.equal('1');
|
||||
expect(await this.math.$log10('9', Rounding.Up)).to.be.bignumber.equal('1');
|
||||
expect(await this.math.$log10('10', Rounding.Up)).to.be.bignumber.equal('1');
|
||||
expect(await this.math.$log10('11', Rounding.Up)).to.be.bignumber.equal('2');
|
||||
expect(await this.math.$log10('99', Rounding.Up)).to.be.bignumber.equal('2');
|
||||
expect(await this.math.$log10('100', Rounding.Up)).to.be.bignumber.equal('2');
|
||||
expect(await this.math.$log10('101', Rounding.Up)).to.be.bignumber.equal('3');
|
||||
expect(await this.math.$log10('999', Rounding.Up)).to.be.bignumber.equal('3');
|
||||
expect(await this.math.$log10('1000', Rounding.Up)).to.be.bignumber.equal('3');
|
||||
expect(await this.math.$log10('1001', Rounding.Up)).to.be.bignumber.equal('4');
|
||||
expect(await this.math.$log10(MAX_UINT256, Rounding.Up)).to.be.bignumber.equal('78');
|
||||
});
|
||||
});
|
||||
|
||||
describe('log256', function () {
|
||||
it('rounds down', async function () {
|
||||
expect(await this.math.$log256('0', Rounding.Down)).to.be.bignumber.equal('0');
|
||||
expect(await this.math.$log256('1', Rounding.Down)).to.be.bignumber.equal('0');
|
||||
expect(await this.math.$log256('2', Rounding.Down)).to.be.bignumber.equal('0');
|
||||
expect(await this.math.$log256('255', Rounding.Down)).to.be.bignumber.equal('0');
|
||||
expect(await this.math.$log256('256', Rounding.Down)).to.be.bignumber.equal('1');
|
||||
expect(await this.math.$log256('257', Rounding.Down)).to.be.bignumber.equal('1');
|
||||
expect(await this.math.$log256('65535', Rounding.Down)).to.be.bignumber.equal('1');
|
||||
expect(await this.math.$log256('65536', Rounding.Down)).to.be.bignumber.equal('2');
|
||||
expect(await this.math.$log256('65537', Rounding.Down)).to.be.bignumber.equal('2');
|
||||
expect(await this.math.$log256(MAX_UINT256, Rounding.Down)).to.be.bignumber.equal('31');
|
||||
});
|
||||
|
||||
it('rounds up', async function () {
|
||||
expect(await this.math.$log256('0', Rounding.Up)).to.be.bignumber.equal('0');
|
||||
expect(await this.math.$log256('1', Rounding.Up)).to.be.bignumber.equal('0');
|
||||
expect(await this.math.$log256('2', Rounding.Up)).to.be.bignumber.equal('1');
|
||||
expect(await this.math.$log256('255', Rounding.Up)).to.be.bignumber.equal('1');
|
||||
expect(await this.math.$log256('256', Rounding.Up)).to.be.bignumber.equal('1');
|
||||
expect(await this.math.$log256('257', Rounding.Up)).to.be.bignumber.equal('2');
|
||||
expect(await this.math.$log256('65535', Rounding.Up)).to.be.bignumber.equal('2');
|
||||
expect(await this.math.$log256('65536', Rounding.Up)).to.be.bignumber.equal('2');
|
||||
expect(await this.math.$log256('65537', Rounding.Up)).to.be.bignumber.equal('3');
|
||||
expect(await this.math.$log256(MAX_UINT256, Rounding.Up)).to.be.bignumber.equal('32');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,152 @@
|
||||
const { BN, expectRevert } = require('@openzeppelin/test-helpers');
|
||||
const { expect } = require('chai');
|
||||
const { range } = require('../../../scripts/helpers');
|
||||
|
||||
const SafeCast = artifacts.require('$SafeCast');
|
||||
|
||||
contract('SafeCast', async function () {
|
||||
beforeEach(async function () {
|
||||
this.safeCast = await SafeCast.new();
|
||||
});
|
||||
|
||||
function testToUint(bits) {
|
||||
describe(`toUint${bits}`, () => {
|
||||
const maxValue = new BN('2').pow(new BN(bits)).subn(1);
|
||||
|
||||
it('downcasts 0', async function () {
|
||||
expect(await this.safeCast[`$toUint${bits}`](0)).to.be.bignumber.equal('0');
|
||||
});
|
||||
|
||||
it('downcasts 1', async function () {
|
||||
expect(await this.safeCast[`$toUint${bits}`](1)).to.be.bignumber.equal('1');
|
||||
});
|
||||
|
||||
it(`downcasts 2^${bits} - 1 (${maxValue})`, async function () {
|
||||
expect(await this.safeCast[`$toUint${bits}`](maxValue)).to.be.bignumber.equal(maxValue);
|
||||
});
|
||||
|
||||
it(`reverts when downcasting 2^${bits} (${maxValue.addn(1)})`, async function () {
|
||||
await expectRevert(
|
||||
this.safeCast[`$toUint${bits}`](maxValue.addn(1)),
|
||||
`SafeCast: value doesn't fit in ${bits} bits`,
|
||||
);
|
||||
});
|
||||
|
||||
it(`reverts when downcasting 2^${bits} + 1 (${maxValue.addn(2)})`, async function () {
|
||||
await expectRevert(
|
||||
this.safeCast[`$toUint${bits}`](maxValue.addn(2)),
|
||||
`SafeCast: value doesn't fit in ${bits} bits`,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
range(8, 256, 8).forEach(bits => testToUint(bits));
|
||||
|
||||
describe('toUint256', () => {
|
||||
const maxInt256 = new BN('2').pow(new BN(255)).subn(1);
|
||||
const minInt256 = new BN('2').pow(new BN(255)).neg();
|
||||
|
||||
it('casts 0', async function () {
|
||||
expect(await this.safeCast.$toUint256(0)).to.be.bignumber.equal('0');
|
||||
});
|
||||
|
||||
it('casts 1', async function () {
|
||||
expect(await this.safeCast.$toUint256(1)).to.be.bignumber.equal('1');
|
||||
});
|
||||
|
||||
it(`casts INT256_MAX (${maxInt256})`, async function () {
|
||||
expect(await this.safeCast.$toUint256(maxInt256)).to.be.bignumber.equal(maxInt256);
|
||||
});
|
||||
|
||||
it('reverts when casting -1', async function () {
|
||||
await expectRevert(this.safeCast.$toUint256(-1), 'SafeCast: value must be positive');
|
||||
});
|
||||
|
||||
it(`reverts when casting INT256_MIN (${minInt256})`, async function () {
|
||||
await expectRevert(this.safeCast.$toUint256(minInt256), 'SafeCast: value must be positive');
|
||||
});
|
||||
});
|
||||
|
||||
function testToInt(bits) {
|
||||
describe(`toInt${bits}`, () => {
|
||||
const minValue = new BN('-2').pow(new BN(bits - 1));
|
||||
const maxValue = new BN('2').pow(new BN(bits - 1)).subn(1);
|
||||
|
||||
it('downcasts 0', async function () {
|
||||
expect(await this.safeCast[`$toInt${bits}`](0)).to.be.bignumber.equal('0');
|
||||
});
|
||||
|
||||
it('downcasts 1', async function () {
|
||||
expect(await this.safeCast[`$toInt${bits}`](1)).to.be.bignumber.equal('1');
|
||||
});
|
||||
|
||||
it('downcasts -1', async function () {
|
||||
expect(await this.safeCast[`$toInt${bits}`](-1)).to.be.bignumber.equal('-1');
|
||||
});
|
||||
|
||||
it(`downcasts -2^${bits - 1} (${minValue})`, async function () {
|
||||
expect(await this.safeCast[`$toInt${bits}`](minValue)).to.be.bignumber.equal(minValue);
|
||||
});
|
||||
|
||||
it(`downcasts 2^${bits - 1} - 1 (${maxValue})`, async function () {
|
||||
expect(await this.safeCast[`$toInt${bits}`](maxValue)).to.be.bignumber.equal(maxValue);
|
||||
});
|
||||
|
||||
it(`reverts when downcasting -2^${bits - 1} - 1 (${minValue.subn(1)})`, async function () {
|
||||
await expectRevert(
|
||||
this.safeCast[`$toInt${bits}`](minValue.subn(1)),
|
||||
`SafeCast: value doesn't fit in ${bits} bits`,
|
||||
);
|
||||
});
|
||||
|
||||
it(`reverts when downcasting -2^${bits - 1} - 2 (${minValue.subn(2)})`, async function () {
|
||||
await expectRevert(
|
||||
this.safeCast[`$toInt${bits}`](minValue.subn(2)),
|
||||
`SafeCast: value doesn't fit in ${bits} bits`,
|
||||
);
|
||||
});
|
||||
|
||||
it(`reverts when downcasting 2^${bits - 1} (${maxValue.addn(1)})`, async function () {
|
||||
await expectRevert(
|
||||
this.safeCast[`$toInt${bits}`](maxValue.addn(1)),
|
||||
`SafeCast: value doesn't fit in ${bits} bits`,
|
||||
);
|
||||
});
|
||||
|
||||
it(`reverts when downcasting 2^${bits - 1} + 1 (${maxValue.addn(2)})`, async function () {
|
||||
await expectRevert(
|
||||
this.safeCast[`$toInt${bits}`](maxValue.addn(2)),
|
||||
`SafeCast: value doesn't fit in ${bits} bits`,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
range(8, 256, 8).forEach(bits => testToInt(bits));
|
||||
|
||||
describe('toInt256', () => {
|
||||
const maxUint256 = new BN('2').pow(new BN(256)).subn(1);
|
||||
const maxInt256 = new BN('2').pow(new BN(255)).subn(1);
|
||||
|
||||
it('casts 0', async function () {
|
||||
expect(await this.safeCast.$toInt256(0)).to.be.bignumber.equal('0');
|
||||
});
|
||||
|
||||
it('casts 1', async function () {
|
||||
expect(await this.safeCast.$toInt256(1)).to.be.bignumber.equal('1');
|
||||
});
|
||||
|
||||
it(`casts INT256_MAX (${maxInt256})`, async function () {
|
||||
expect(await this.safeCast.$toInt256(maxInt256)).to.be.bignumber.equal(maxInt256);
|
||||
});
|
||||
|
||||
it(`reverts when casting INT256_MAX + 1 (${maxInt256.addn(1)})`, async function () {
|
||||
await expectRevert(this.safeCast.$toInt256(maxInt256.addn(1)), "SafeCast: value doesn't fit in an int256");
|
||||
});
|
||||
|
||||
it(`reverts when casting UINT256_MAX (${maxUint256})`, async function () {
|
||||
await expectRevert(this.safeCast.$toInt256(maxUint256), "SafeCast: value doesn't fit in an int256");
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,433 @@
|
||||
const { BN, constants, expectRevert } = require('@openzeppelin/test-helpers');
|
||||
const { MAX_UINT256 } = constants;
|
||||
|
||||
const { expect } = require('chai');
|
||||
|
||||
const SafeMath = artifacts.require('$SafeMath');
|
||||
const SafeMathMemoryCheck = artifacts.require('$SafeMathMemoryCheck');
|
||||
|
||||
function expectStruct(value, expected) {
|
||||
for (const key in expected) {
|
||||
if (BN.isBN(value[key])) {
|
||||
expect(value[key]).to.be.bignumber.equal(expected[key]);
|
||||
} else {
|
||||
expect(value[key]).to.be.equal(expected[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
contract('SafeMath', function () {
|
||||
beforeEach(async function () {
|
||||
this.safeMath = await SafeMath.new();
|
||||
});
|
||||
|
||||
async function testCommutative(fn, lhs, rhs, expected, ...extra) {
|
||||
expect(await fn(lhs, rhs, ...extra)).to.be.bignumber.equal(expected);
|
||||
expect(await fn(rhs, lhs, ...extra)).to.be.bignumber.equal(expected);
|
||||
}
|
||||
|
||||
async function testFailsCommutative(fn, lhs, rhs, reason, ...extra) {
|
||||
if (reason === undefined) {
|
||||
await expectRevert.unspecified(fn(lhs, rhs, ...extra));
|
||||
await expectRevert.unspecified(fn(rhs, lhs, ...extra));
|
||||
} else {
|
||||
await expectRevert(fn(lhs, rhs, ...extra), reason);
|
||||
await expectRevert(fn(rhs, lhs, ...extra), reason);
|
||||
}
|
||||
}
|
||||
|
||||
async function testCommutativeIterable(fn, lhs, rhs, expected, ...extra) {
|
||||
expectStruct(await fn(lhs, rhs, ...extra), expected);
|
||||
expectStruct(await fn(rhs, lhs, ...extra), expected);
|
||||
}
|
||||
|
||||
describe('with flag', function () {
|
||||
describe('add', function () {
|
||||
it('adds correctly', async function () {
|
||||
const a = new BN('5678');
|
||||
const b = new BN('1234');
|
||||
|
||||
testCommutativeIterable(this.safeMath.$tryAdd, a, b, [true, a.add(b)]);
|
||||
});
|
||||
|
||||
it('reverts on addition overflow', async function () {
|
||||
const a = MAX_UINT256;
|
||||
const b = new BN('1');
|
||||
|
||||
testCommutativeIterable(this.safeMath.$tryAdd, a, b, [false, '0']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('sub', function () {
|
||||
it('subtracts correctly', async function () {
|
||||
const a = new BN('5678');
|
||||
const b = new BN('1234');
|
||||
|
||||
expectStruct(await this.safeMath.$trySub(a, b), [true, a.sub(b)]);
|
||||
});
|
||||
|
||||
it('reverts if subtraction result would be negative', async function () {
|
||||
const a = new BN('1234');
|
||||
const b = new BN('5678');
|
||||
|
||||
expectStruct(await this.safeMath.$trySub(a, b), [false, '0']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('mul', function () {
|
||||
it('multiplies correctly', async function () {
|
||||
const a = new BN('1234');
|
||||
const b = new BN('5678');
|
||||
|
||||
testCommutativeIterable(this.safeMath.$tryMul, a, b, [true, a.mul(b)]);
|
||||
});
|
||||
|
||||
it('multiplies by zero correctly', async function () {
|
||||
const a = new BN('0');
|
||||
const b = new BN('5678');
|
||||
|
||||
testCommutativeIterable(this.safeMath.$tryMul, a, b, [true, a.mul(b)]);
|
||||
});
|
||||
|
||||
it('reverts on multiplication overflow', async function () {
|
||||
const a = MAX_UINT256;
|
||||
const b = new BN('2');
|
||||
|
||||
testCommutativeIterable(this.safeMath.$tryMul, a, b, [false, '0']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('div', function () {
|
||||
it('divides correctly', async function () {
|
||||
const a = new BN('5678');
|
||||
const b = new BN('5678');
|
||||
|
||||
expectStruct(await this.safeMath.$tryDiv(a, b), [true, a.div(b)]);
|
||||
});
|
||||
|
||||
it('divides zero correctly', async function () {
|
||||
const a = new BN('0');
|
||||
const b = new BN('5678');
|
||||
|
||||
expectStruct(await this.safeMath.$tryDiv(a, b), [true, a.div(b)]);
|
||||
});
|
||||
|
||||
it('returns complete number result on non-even division', async function () {
|
||||
const a = new BN('7000');
|
||||
const b = new BN('5678');
|
||||
|
||||
expectStruct(await this.safeMath.$tryDiv(a, b), [true, a.div(b)]);
|
||||
});
|
||||
|
||||
it('reverts on division by zero', async function () {
|
||||
const a = new BN('5678');
|
||||
const b = new BN('0');
|
||||
|
||||
expectStruct(await this.safeMath.$tryDiv(a, b), [false, '0']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('mod', function () {
|
||||
describe('modulos correctly', async function () {
|
||||
it('when the dividend is smaller than the divisor', async function () {
|
||||
const a = new BN('284');
|
||||
const b = new BN('5678');
|
||||
|
||||
expectStruct(await this.safeMath.$tryMod(a, b), [true, a.mod(b)]);
|
||||
});
|
||||
|
||||
it('when the dividend is equal to the divisor', async function () {
|
||||
const a = new BN('5678');
|
||||
const b = new BN('5678');
|
||||
|
||||
expectStruct(await this.safeMath.$tryMod(a, b), [true, a.mod(b)]);
|
||||
});
|
||||
|
||||
it('when the dividend is larger than the divisor', async function () {
|
||||
const a = new BN('7000');
|
||||
const b = new BN('5678');
|
||||
|
||||
expectStruct(await this.safeMath.$tryMod(a, b), [true, a.mod(b)]);
|
||||
});
|
||||
|
||||
it('when the dividend is a multiple of the divisor', async function () {
|
||||
const a = new BN('17034'); // 17034 == 5678 * 3
|
||||
const b = new BN('5678');
|
||||
|
||||
expectStruct(await this.safeMath.$tryMod(a, b), [true, a.mod(b)]);
|
||||
});
|
||||
});
|
||||
|
||||
it('reverts with a 0 divisor', async function () {
|
||||
const a = new BN('5678');
|
||||
const b = new BN('0');
|
||||
|
||||
expectStruct(await this.safeMath.$tryMod(a, b), [false, '0']);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('with default revert message', function () {
|
||||
describe('add', function () {
|
||||
it('adds correctly', async function () {
|
||||
const a = new BN('5678');
|
||||
const b = new BN('1234');
|
||||
|
||||
await testCommutative(this.safeMath.$add, a, b, a.add(b));
|
||||
});
|
||||
|
||||
it('reverts on addition overflow', async function () {
|
||||
const a = MAX_UINT256;
|
||||
const b = new BN('1');
|
||||
|
||||
await testFailsCommutative(this.safeMath.$add, a, b, undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe('sub', function () {
|
||||
it('subtracts correctly', async function () {
|
||||
const a = new BN('5678');
|
||||
const b = new BN('1234');
|
||||
|
||||
expect(await this.safeMath.$sub(a, b)).to.be.bignumber.equal(a.sub(b));
|
||||
});
|
||||
|
||||
it('reverts if subtraction result would be negative', async function () {
|
||||
const a = new BN('1234');
|
||||
const b = new BN('5678');
|
||||
|
||||
await expectRevert.unspecified(this.safeMath.$sub(a, b));
|
||||
});
|
||||
});
|
||||
|
||||
describe('mul', function () {
|
||||
it('multiplies correctly', async function () {
|
||||
const a = new BN('1234');
|
||||
const b = new BN('5678');
|
||||
|
||||
await testCommutative(this.safeMath.$mul, a, b, a.mul(b));
|
||||
});
|
||||
|
||||
it('multiplies by zero correctly', async function () {
|
||||
const a = new BN('0');
|
||||
const b = new BN('5678');
|
||||
|
||||
await testCommutative(this.safeMath.$mul, a, b, '0');
|
||||
});
|
||||
|
||||
it('reverts on multiplication overflow', async function () {
|
||||
const a = MAX_UINT256;
|
||||
const b = new BN('2');
|
||||
|
||||
await testFailsCommutative(this.safeMath.$mul, a, b, undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe('div', function () {
|
||||
it('divides correctly', async function () {
|
||||
const a = new BN('5678');
|
||||
const b = new BN('5678');
|
||||
|
||||
expect(await this.safeMath.$div(a, b)).to.be.bignumber.equal(a.div(b));
|
||||
});
|
||||
|
||||
it('divides zero correctly', async function () {
|
||||
const a = new BN('0');
|
||||
const b = new BN('5678');
|
||||
|
||||
expect(await this.safeMath.$div(a, b)).to.be.bignumber.equal('0');
|
||||
});
|
||||
|
||||
it('returns complete number result on non-even division', async function () {
|
||||
const a = new BN('7000');
|
||||
const b = new BN('5678');
|
||||
|
||||
expect(await this.safeMath.$div(a, b)).to.be.bignumber.equal('1');
|
||||
});
|
||||
|
||||
it('reverts on division by zero', async function () {
|
||||
const a = new BN('5678');
|
||||
const b = new BN('0');
|
||||
|
||||
await expectRevert.unspecified(this.safeMath.$div(a, b));
|
||||
});
|
||||
});
|
||||
|
||||
describe('mod', function () {
|
||||
describe('modulos correctly', async function () {
|
||||
it('when the dividend is smaller than the divisor', async function () {
|
||||
const a = new BN('284');
|
||||
const b = new BN('5678');
|
||||
|
||||
expect(await this.safeMath.$mod(a, b)).to.be.bignumber.equal(a.mod(b));
|
||||
});
|
||||
|
||||
it('when the dividend is equal to the divisor', async function () {
|
||||
const a = new BN('5678');
|
||||
const b = new BN('5678');
|
||||
|
||||
expect(await this.safeMath.$mod(a, b)).to.be.bignumber.equal(a.mod(b));
|
||||
});
|
||||
|
||||
it('when the dividend is larger than the divisor', async function () {
|
||||
const a = new BN('7000');
|
||||
const b = new BN('5678');
|
||||
|
||||
expect(await this.safeMath.$mod(a, b)).to.be.bignumber.equal(a.mod(b));
|
||||
});
|
||||
|
||||
it('when the dividend is a multiple of the divisor', async function () {
|
||||
const a = new BN('17034'); // 17034 == 5678 * 3
|
||||
const b = new BN('5678');
|
||||
|
||||
expect(await this.safeMath.$mod(a, b)).to.be.bignumber.equal(a.mod(b));
|
||||
});
|
||||
});
|
||||
|
||||
it('reverts with a 0 divisor', async function () {
|
||||
const a = new BN('5678');
|
||||
const b = new BN('0');
|
||||
|
||||
await expectRevert.unspecified(this.safeMath.$mod(a, b));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('with custom revert message', function () {
|
||||
describe('sub', function () {
|
||||
it('subtracts correctly', async function () {
|
||||
const a = new BN('5678');
|
||||
const b = new BN('1234');
|
||||
|
||||
expect(
|
||||
await this.safeMath.methods['$sub(uint256,uint256,string)'](a, b, 'MyErrorMessage'),
|
||||
).to.be.bignumber.equal(a.sub(b));
|
||||
});
|
||||
|
||||
it('reverts if subtraction result would be negative', async function () {
|
||||
const a = new BN('1234');
|
||||
const b = new BN('5678');
|
||||
|
||||
await expectRevert(
|
||||
this.safeMath.methods['$sub(uint256,uint256,string)'](a, b, 'MyErrorMessage'),
|
||||
'MyErrorMessage',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('div', function () {
|
||||
it('divides correctly', async function () {
|
||||
const a = new BN('5678');
|
||||
const b = new BN('5678');
|
||||
|
||||
expect(
|
||||
await this.safeMath.methods['$div(uint256,uint256,string)'](a, b, 'MyErrorMessage'),
|
||||
).to.be.bignumber.equal(a.div(b));
|
||||
});
|
||||
|
||||
it('divides zero correctly', async function () {
|
||||
const a = new BN('0');
|
||||
const b = new BN('5678');
|
||||
|
||||
expect(
|
||||
await this.safeMath.methods['$div(uint256,uint256,string)'](a, b, 'MyErrorMessage'),
|
||||
).to.be.bignumber.equal('0');
|
||||
});
|
||||
|
||||
it('returns complete number result on non-even division', async function () {
|
||||
const a = new BN('7000');
|
||||
const b = new BN('5678');
|
||||
|
||||
expect(
|
||||
await this.safeMath.methods['$div(uint256,uint256,string)'](a, b, 'MyErrorMessage'),
|
||||
).to.be.bignumber.equal('1');
|
||||
});
|
||||
|
||||
it('reverts on division by zero', async function () {
|
||||
const a = new BN('5678');
|
||||
const b = new BN('0');
|
||||
|
||||
await expectRevert(
|
||||
this.safeMath.methods['$div(uint256,uint256,string)'](a, b, 'MyErrorMessage'),
|
||||
'MyErrorMessage',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('mod', function () {
|
||||
describe('modulos correctly', async function () {
|
||||
it('when the dividend is smaller than the divisor', async function () {
|
||||
const a = new BN('284');
|
||||
const b = new BN('5678');
|
||||
|
||||
expect(
|
||||
await this.safeMath.methods['$mod(uint256,uint256,string)'](a, b, 'MyErrorMessage'),
|
||||
).to.be.bignumber.equal(a.mod(b));
|
||||
});
|
||||
|
||||
it('when the dividend is equal to the divisor', async function () {
|
||||
const a = new BN('5678');
|
||||
const b = new BN('5678');
|
||||
|
||||
expect(
|
||||
await this.safeMath.methods['$mod(uint256,uint256,string)'](a, b, 'MyErrorMessage'),
|
||||
).to.be.bignumber.equal(a.mod(b));
|
||||
});
|
||||
|
||||
it('when the dividend is larger than the divisor', async function () {
|
||||
const a = new BN('7000');
|
||||
const b = new BN('5678');
|
||||
|
||||
expect(
|
||||
await this.safeMath.methods['$mod(uint256,uint256,string)'](a, b, 'MyErrorMessage'),
|
||||
).to.be.bignumber.equal(a.mod(b));
|
||||
});
|
||||
|
||||
it('when the dividend is a multiple of the divisor', async function () {
|
||||
const a = new BN('17034'); // 17034 == 5678 * 3
|
||||
const b = new BN('5678');
|
||||
|
||||
expect(
|
||||
await this.safeMath.methods['$mod(uint256,uint256,string)'](a, b, 'MyErrorMessage'),
|
||||
).to.be.bignumber.equal(a.mod(b));
|
||||
});
|
||||
});
|
||||
|
||||
it('reverts with a 0 divisor', async function () {
|
||||
const a = new BN('5678');
|
||||
const b = new BN('0');
|
||||
|
||||
await expectRevert(
|
||||
this.safeMath.methods['$mod(uint256,uint256,string)'](a, b, 'MyErrorMessage'),
|
||||
'MyErrorMessage',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('memory leakage', function () {
|
||||
beforeEach(async function () {
|
||||
this.safeMathMemoryCheck = await SafeMathMemoryCheck.new();
|
||||
});
|
||||
|
||||
it('add', async function () {
|
||||
expect(await this.safeMathMemoryCheck.$addMemoryCheck()).to.be.bignumber.equal('0');
|
||||
});
|
||||
|
||||
it('sub', async function () {
|
||||
expect(await this.safeMathMemoryCheck.$subMemoryCheck()).to.be.bignumber.equal('0');
|
||||
});
|
||||
|
||||
it('mul', async function () {
|
||||
expect(await this.safeMathMemoryCheck.$mulMemoryCheck()).to.be.bignumber.equal('0');
|
||||
});
|
||||
|
||||
it('div', async function () {
|
||||
expect(await this.safeMathMemoryCheck.$divMemoryCheck()).to.be.bignumber.equal('0');
|
||||
});
|
||||
|
||||
it('mod', async function () {
|
||||
expect(await this.safeMathMemoryCheck.$modMemoryCheck()).to.be.bignumber.equal('0');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,95 @@
|
||||
const { BN, constants } = require('@openzeppelin/test-helpers');
|
||||
const { expect } = require('chai');
|
||||
const { MIN_INT256, MAX_INT256 } = constants;
|
||||
|
||||
const SignedMath = artifacts.require('$SignedMath');
|
||||
|
||||
contract('SignedMath', function () {
|
||||
const min = new BN('-1234');
|
||||
const max = new BN('5678');
|
||||
|
||||
beforeEach(async function () {
|
||||
this.math = await SignedMath.new();
|
||||
});
|
||||
|
||||
describe('max', function () {
|
||||
it('is correctly detected in first argument position', async function () {
|
||||
expect(await this.math.$max(max, min)).to.be.bignumber.equal(max);
|
||||
});
|
||||
|
||||
it('is correctly detected in second argument position', async function () {
|
||||
expect(await this.math.$max(min, max)).to.be.bignumber.equal(max);
|
||||
});
|
||||
});
|
||||
|
||||
describe('min', function () {
|
||||
it('is correctly detected in first argument position', async function () {
|
||||
expect(await this.math.$min(min, max)).to.be.bignumber.equal(min);
|
||||
});
|
||||
|
||||
it('is correctly detected in second argument position', async function () {
|
||||
expect(await this.math.$min(max, min)).to.be.bignumber.equal(min);
|
||||
});
|
||||
});
|
||||
|
||||
describe('average', function () {
|
||||
function bnAverage(a, b) {
|
||||
return a.add(b).divn(2);
|
||||
}
|
||||
|
||||
it('is correctly calculated with various input', async function () {
|
||||
const valuesX = [
|
||||
new BN('0'),
|
||||
new BN('3'),
|
||||
new BN('-3'),
|
||||
new BN('4'),
|
||||
new BN('-4'),
|
||||
new BN('57417'),
|
||||
new BN('-57417'),
|
||||
new BN('42304'),
|
||||
new BN('-42304'),
|
||||
MIN_INT256,
|
||||
MAX_INT256,
|
||||
];
|
||||
|
||||
const valuesY = [
|
||||
new BN('0'),
|
||||
new BN('5'),
|
||||
new BN('-5'),
|
||||
new BN('2'),
|
||||
new BN('-2'),
|
||||
new BN('57417'),
|
||||
new BN('-57417'),
|
||||
new BN('42304'),
|
||||
new BN('-42304'),
|
||||
MIN_INT256,
|
||||
MAX_INT256,
|
||||
];
|
||||
|
||||
for (const x of valuesX) {
|
||||
for (const y of valuesY) {
|
||||
expect(await this.math.$average(x, y)).to.be.bignumber.equal(
|
||||
bnAverage(x, y),
|
||||
`Bad result for average(${x}, ${y})`,
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('abs', function () {
|
||||
for (const n of [
|
||||
MIN_INT256,
|
||||
MIN_INT256.addn(1),
|
||||
new BN('-1'),
|
||||
new BN('0'),
|
||||
new BN('1'),
|
||||
MAX_INT256.subn(1),
|
||||
MAX_INT256,
|
||||
]) {
|
||||
it(`correctly computes the absolute value of ${n}`, async function () {
|
||||
expect(await this.math.$abs(n)).to.be.bignumber.equal(n.abs());
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,152 @@
|
||||
const { BN, constants, expectRevert } = require('@openzeppelin/test-helpers');
|
||||
const { MAX_INT256, MIN_INT256 } = constants;
|
||||
|
||||
const { expect } = require('chai');
|
||||
|
||||
const SignedSafeMath = artifacts.require('$SignedSafeMath');
|
||||
|
||||
contract('SignedSafeMath', function () {
|
||||
beforeEach(async function () {
|
||||
this.safeMath = await SignedSafeMath.new();
|
||||
});
|
||||
|
||||
async function testCommutative(fn, lhs, rhs, expected) {
|
||||
expect(await fn(lhs, rhs)).to.be.bignumber.equal(expected);
|
||||
expect(await fn(rhs, lhs)).to.be.bignumber.equal(expected);
|
||||
}
|
||||
|
||||
async function testFailsCommutative(fn, lhs, rhs) {
|
||||
await expectRevert.unspecified(fn(lhs, rhs));
|
||||
await expectRevert.unspecified(fn(rhs, lhs));
|
||||
}
|
||||
|
||||
describe('add', function () {
|
||||
it('adds correctly if it does not overflow and the result is positive', async function () {
|
||||
const a = new BN('1234');
|
||||
const b = new BN('5678');
|
||||
|
||||
await testCommutative(this.safeMath.$add, a, b, a.add(b));
|
||||
});
|
||||
|
||||
it('adds correctly if it does not overflow and the result is negative', async function () {
|
||||
const a = MAX_INT256;
|
||||
const b = MIN_INT256;
|
||||
|
||||
await testCommutative(this.safeMath.$add, a, b, a.add(b));
|
||||
});
|
||||
|
||||
it('reverts on positive addition overflow', async function () {
|
||||
const a = MAX_INT256;
|
||||
const b = new BN('1');
|
||||
|
||||
await testFailsCommutative(this.safeMath.$add, a, b);
|
||||
});
|
||||
|
||||
it('reverts on negative addition overflow', async function () {
|
||||
const a = MIN_INT256;
|
||||
const b = new BN('-1');
|
||||
|
||||
await testFailsCommutative(this.safeMath.$add, a, b);
|
||||
});
|
||||
});
|
||||
|
||||
describe('sub', function () {
|
||||
it('subtracts correctly if it does not overflow and the result is positive', async function () {
|
||||
const a = new BN('5678');
|
||||
const b = new BN('1234');
|
||||
|
||||
const result = await this.safeMath.$sub(a, b);
|
||||
expect(result).to.be.bignumber.equal(a.sub(b));
|
||||
});
|
||||
|
||||
it('subtracts correctly if it does not overflow and the result is negative', async function () {
|
||||
const a = new BN('1234');
|
||||
const b = new BN('5678');
|
||||
|
||||
const result = await this.safeMath.$sub(a, b);
|
||||
expect(result).to.be.bignumber.equal(a.sub(b));
|
||||
});
|
||||
|
||||
it('reverts on positive subtraction overflow', async function () {
|
||||
const a = MAX_INT256;
|
||||
const b = new BN('-1');
|
||||
|
||||
await expectRevert.unspecified(this.safeMath.$sub(a, b));
|
||||
});
|
||||
|
||||
it('reverts on negative subtraction overflow', async function () {
|
||||
const a = MIN_INT256;
|
||||
const b = new BN('1');
|
||||
|
||||
await expectRevert.unspecified(this.safeMath.$sub(a, b));
|
||||
});
|
||||
});
|
||||
|
||||
describe('mul', function () {
|
||||
it('multiplies correctly', async function () {
|
||||
const a = new BN('5678');
|
||||
const b = new BN('-1234');
|
||||
|
||||
await testCommutative(this.safeMath.$mul, a, b, a.mul(b));
|
||||
});
|
||||
|
||||
it('multiplies by zero correctly', async function () {
|
||||
const a = new BN('0');
|
||||
const b = new BN('5678');
|
||||
|
||||
await testCommutative(this.safeMath.$mul, a, b, '0');
|
||||
});
|
||||
|
||||
it('reverts on multiplication overflow, positive operands', async function () {
|
||||
const a = MAX_INT256;
|
||||
const b = new BN('2');
|
||||
|
||||
await testFailsCommutative(this.safeMath.$mul, a, b);
|
||||
});
|
||||
|
||||
it('reverts when minimum integer is multiplied by -1', async function () {
|
||||
const a = MIN_INT256;
|
||||
const b = new BN('-1');
|
||||
|
||||
await testFailsCommutative(this.safeMath.$mul, a, b);
|
||||
});
|
||||
});
|
||||
|
||||
describe('div', function () {
|
||||
it('divides correctly', async function () {
|
||||
const a = new BN('-5678');
|
||||
const b = new BN('5678');
|
||||
|
||||
const result = await this.safeMath.$div(a, b);
|
||||
expect(result).to.be.bignumber.equal(a.div(b));
|
||||
});
|
||||
|
||||
it('divides zero correctly', async function () {
|
||||
const a = new BN('0');
|
||||
const b = new BN('5678');
|
||||
|
||||
expect(await this.safeMath.$div(a, b)).to.be.bignumber.equal('0');
|
||||
});
|
||||
|
||||
it('returns complete number result on non-even division', async function () {
|
||||
const a = new BN('7000');
|
||||
const b = new BN('5678');
|
||||
|
||||
expect(await this.safeMath.$div(a, b)).to.be.bignumber.equal('1');
|
||||
});
|
||||
|
||||
it('reverts on division by zero', async function () {
|
||||
const a = new BN('-5678');
|
||||
const b = new BN('0');
|
||||
|
||||
await expectRevert.unspecified(this.safeMath.$div(a, b));
|
||||
});
|
||||
|
||||
it('reverts on overflow, negative second', async function () {
|
||||
const a = new BN(MIN_INT256);
|
||||
const b = new BN('-1');
|
||||
|
||||
await expectRevert.unspecified(this.safeMath.$div(a, b));
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,145 @@
|
||||
const { BN } = require('@openzeppelin/test-helpers');
|
||||
const { expect } = require('chai');
|
||||
|
||||
const BitMap = artifacts.require('$BitMaps');
|
||||
|
||||
contract('BitMap', function () {
|
||||
const keyA = new BN('7891');
|
||||
const keyB = new BN('451');
|
||||
const keyC = new BN('9592328');
|
||||
|
||||
beforeEach(async function () {
|
||||
this.bitmap = await BitMap.new();
|
||||
});
|
||||
|
||||
it('starts empty', async function () {
|
||||
expect(await this.bitmap.$get(0, keyA)).to.equal(false);
|
||||
expect(await this.bitmap.$get(0, keyB)).to.equal(false);
|
||||
expect(await this.bitmap.$get(0, keyC)).to.equal(false);
|
||||
});
|
||||
|
||||
describe('setTo', function () {
|
||||
it('set a key to true', async function () {
|
||||
await this.bitmap.$setTo(0, keyA, true);
|
||||
expect(await this.bitmap.$get(0, keyA)).to.equal(true);
|
||||
expect(await this.bitmap.$get(0, keyB)).to.equal(false);
|
||||
expect(await this.bitmap.$get(0, keyC)).to.equal(false);
|
||||
});
|
||||
|
||||
it('set a key to false', async function () {
|
||||
await this.bitmap.$setTo(0, keyA, true);
|
||||
await this.bitmap.$setTo(0, keyA, false);
|
||||
expect(await this.bitmap.$get(0, keyA)).to.equal(false);
|
||||
expect(await this.bitmap.$get(0, keyB)).to.equal(false);
|
||||
expect(await this.bitmap.$get(0, keyC)).to.equal(false);
|
||||
});
|
||||
|
||||
it('set several consecutive keys', async function () {
|
||||
await this.bitmap.$setTo(0, keyA.addn(0), true);
|
||||
await this.bitmap.$setTo(0, keyA.addn(1), true);
|
||||
await this.bitmap.$setTo(0, keyA.addn(2), true);
|
||||
await this.bitmap.$setTo(0, keyA.addn(3), true);
|
||||
await this.bitmap.$setTo(0, keyA.addn(4), true);
|
||||
await this.bitmap.$setTo(0, keyA.addn(2), false);
|
||||
await this.bitmap.$setTo(0, keyA.addn(4), false);
|
||||
expect(await this.bitmap.$get(0, keyA.addn(0))).to.equal(true);
|
||||
expect(await this.bitmap.$get(0, keyA.addn(1))).to.equal(true);
|
||||
expect(await this.bitmap.$get(0, keyA.addn(2))).to.equal(false);
|
||||
expect(await this.bitmap.$get(0, keyA.addn(3))).to.equal(true);
|
||||
expect(await this.bitmap.$get(0, keyA.addn(4))).to.equal(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('set', function () {
|
||||
it('adds a key', async function () {
|
||||
await this.bitmap.$set(0, keyA);
|
||||
expect(await this.bitmap.$get(0, keyA)).to.equal(true);
|
||||
expect(await this.bitmap.$get(0, keyB)).to.equal(false);
|
||||
expect(await this.bitmap.$get(0, keyC)).to.equal(false);
|
||||
});
|
||||
|
||||
it('adds several keys', async function () {
|
||||
await this.bitmap.$set(0, keyA);
|
||||
await this.bitmap.$set(0, keyB);
|
||||
expect(await this.bitmap.$get(0, keyA)).to.equal(true);
|
||||
expect(await this.bitmap.$get(0, keyB)).to.equal(true);
|
||||
expect(await this.bitmap.$get(0, keyC)).to.equal(false);
|
||||
});
|
||||
|
||||
it('adds several consecutive keys', async function () {
|
||||
await this.bitmap.$set(0, keyA.addn(0));
|
||||
await this.bitmap.$set(0, keyA.addn(1));
|
||||
await this.bitmap.$set(0, keyA.addn(3));
|
||||
expect(await this.bitmap.$get(0, keyA.addn(0))).to.equal(true);
|
||||
expect(await this.bitmap.$get(0, keyA.addn(1))).to.equal(true);
|
||||
expect(await this.bitmap.$get(0, keyA.addn(2))).to.equal(false);
|
||||
expect(await this.bitmap.$get(0, keyA.addn(3))).to.equal(true);
|
||||
expect(await this.bitmap.$get(0, keyA.addn(4))).to.equal(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('unset', function () {
|
||||
it('removes added keys', async function () {
|
||||
await this.bitmap.$set(0, keyA);
|
||||
await this.bitmap.$set(0, keyB);
|
||||
await this.bitmap.$unset(0, keyA);
|
||||
expect(await this.bitmap.$get(0, keyA)).to.equal(false);
|
||||
expect(await this.bitmap.$get(0, keyB)).to.equal(true);
|
||||
expect(await this.bitmap.$get(0, keyC)).to.equal(false);
|
||||
});
|
||||
|
||||
it('removes consecutive added keys', async function () {
|
||||
await this.bitmap.$set(0, keyA.addn(0));
|
||||
await this.bitmap.$set(0, keyA.addn(1));
|
||||
await this.bitmap.$set(0, keyA.addn(3));
|
||||
await this.bitmap.$unset(0, keyA.addn(1));
|
||||
expect(await this.bitmap.$get(0, keyA.addn(0))).to.equal(true);
|
||||
expect(await this.bitmap.$get(0, keyA.addn(1))).to.equal(false);
|
||||
expect(await this.bitmap.$get(0, keyA.addn(2))).to.equal(false);
|
||||
expect(await this.bitmap.$get(0, keyA.addn(3))).to.equal(true);
|
||||
expect(await this.bitmap.$get(0, keyA.addn(4))).to.equal(false);
|
||||
});
|
||||
|
||||
it('adds and removes multiple keys', async function () {
|
||||
// []
|
||||
|
||||
await this.bitmap.$set(0, keyA);
|
||||
await this.bitmap.$set(0, keyC);
|
||||
|
||||
// [A, C]
|
||||
|
||||
await this.bitmap.$unset(0, keyA);
|
||||
await this.bitmap.$unset(0, keyB);
|
||||
|
||||
// [C]
|
||||
|
||||
await this.bitmap.$set(0, keyB);
|
||||
|
||||
// [C, B]
|
||||
|
||||
await this.bitmap.$set(0, keyA);
|
||||
await this.bitmap.$unset(0, keyC);
|
||||
|
||||
// [A, B]
|
||||
|
||||
await this.bitmap.$set(0, keyA);
|
||||
await this.bitmap.$set(0, keyB);
|
||||
|
||||
// [A, B]
|
||||
|
||||
await this.bitmap.$set(0, keyC);
|
||||
await this.bitmap.$unset(0, keyA);
|
||||
|
||||
// [B, C]
|
||||
|
||||
await this.bitmap.$set(0, keyA);
|
||||
await this.bitmap.$unset(0, keyB);
|
||||
|
||||
// [A, C]
|
||||
|
||||
expect(await this.bitmap.$get(0, keyA)).to.equal(true);
|
||||
expect(await this.bitmap.$get(0, keyB)).to.equal(false);
|
||||
expect(await this.bitmap.$get(0, keyC)).to.equal(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,99 @@
|
||||
const { expectEvent } = require('@openzeppelin/test-helpers');
|
||||
const { expectRevertCustomError } = require('../../helpers/customError');
|
||||
|
||||
const DoubleEndedQueue = artifacts.require('$DoubleEndedQueue');
|
||||
|
||||
/** Rebuild the content of the deque as a JS array. */
|
||||
const getContent = deque =>
|
||||
deque.$length(0).then(bn =>
|
||||
Promise.all(
|
||||
Array(bn.toNumber())
|
||||
.fill()
|
||||
.map((_, i) => deque.$at(0, i)),
|
||||
),
|
||||
);
|
||||
|
||||
contract('DoubleEndedQueue', function () {
|
||||
const bytesA = '0xdeadbeef'.padEnd(66, '0');
|
||||
const bytesB = '0x0123456789'.padEnd(66, '0');
|
||||
const bytesC = '0x42424242'.padEnd(66, '0');
|
||||
const bytesD = '0x171717'.padEnd(66, '0');
|
||||
|
||||
beforeEach(async function () {
|
||||
this.deque = await DoubleEndedQueue.new();
|
||||
});
|
||||
|
||||
describe('when empty', function () {
|
||||
it('getters', async function () {
|
||||
expect(await this.deque.$empty(0)).to.be.equal(true);
|
||||
expect(await getContent(this.deque)).to.have.ordered.members([]);
|
||||
});
|
||||
|
||||
it('reverts on accesses', async function () {
|
||||
await expectRevertCustomError(this.deque.$popBack(0), 'Empty()');
|
||||
await expectRevertCustomError(this.deque.$popFront(0), 'Empty()');
|
||||
await expectRevertCustomError(this.deque.$back(0), 'Empty()');
|
||||
await expectRevertCustomError(this.deque.$front(0), 'Empty()');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when not empty', function () {
|
||||
beforeEach(async function () {
|
||||
await this.deque.$pushBack(0, bytesB);
|
||||
await this.deque.$pushFront(0, bytesA);
|
||||
await this.deque.$pushBack(0, bytesC);
|
||||
this.content = [bytesA, bytesB, bytesC];
|
||||
});
|
||||
|
||||
it('getters', async function () {
|
||||
expect(await this.deque.$empty(0)).to.be.equal(false);
|
||||
expect(await this.deque.$length(0)).to.be.bignumber.equal(this.content.length.toString());
|
||||
expect(await this.deque.$front(0)).to.be.equal(this.content[0]);
|
||||
expect(await this.deque.$back(0)).to.be.equal(this.content[this.content.length - 1]);
|
||||
expect(await getContent(this.deque)).to.have.ordered.members(this.content);
|
||||
});
|
||||
|
||||
it('out of bounds access', async function () {
|
||||
await expectRevertCustomError(this.deque.$at(0, this.content.length), 'OutOfBounds()');
|
||||
});
|
||||
|
||||
describe('push', function () {
|
||||
it('front', async function () {
|
||||
await this.deque.$pushFront(0, bytesD);
|
||||
this.content.unshift(bytesD); // add element at the beginning
|
||||
|
||||
expect(await getContent(this.deque)).to.have.ordered.members(this.content);
|
||||
});
|
||||
|
||||
it('back', async function () {
|
||||
await this.deque.$pushBack(0, bytesD);
|
||||
this.content.push(bytesD); // add element at the end
|
||||
|
||||
expect(await getContent(this.deque)).to.have.ordered.members(this.content);
|
||||
});
|
||||
});
|
||||
|
||||
describe('pop', function () {
|
||||
it('front', async function () {
|
||||
const value = this.content.shift(); // remove first element
|
||||
expectEvent(await this.deque.$popFront(0), 'return$popFront', { value });
|
||||
|
||||
expect(await getContent(this.deque)).to.have.ordered.members(this.content);
|
||||
});
|
||||
|
||||
it('back', async function () {
|
||||
const value = this.content.pop(); // remove last element
|
||||
expectEvent(await this.deque.$popBack(0), 'return$popBack', { value });
|
||||
|
||||
expect(await getContent(this.deque)).to.have.ordered.members(this.content);
|
||||
});
|
||||
});
|
||||
|
||||
it('clear', async function () {
|
||||
await this.deque.$clear(0);
|
||||
|
||||
expect(await this.deque.$empty(0)).to.be.equal(true);
|
||||
expect(await getContent(this.deque)).to.have.ordered.members([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,185 @@
|
||||
const { expectEvent, expectRevert } = require('@openzeppelin/test-helpers');
|
||||
const { expect } = require('chai');
|
||||
|
||||
const zip = require('lodash.zip');
|
||||
|
||||
function shouldBehaveLikeMap(keys, values, zeroValue, methods, events) {
|
||||
const [keyA, keyB, keyC] = keys;
|
||||
const [valueA, valueB, valueC] = values;
|
||||
|
||||
async function expectMembersMatch(map, keys, values) {
|
||||
expect(keys.length).to.equal(values.length);
|
||||
|
||||
await Promise.all(keys.map(async key => expect(await methods.contains(map, key)).to.equal(true)));
|
||||
|
||||
expect(await methods.length(map)).to.bignumber.equal(keys.length.toString());
|
||||
|
||||
expect((await Promise.all(keys.map(key => methods.get(map, key)))).map(k => k.toString())).to.have.same.members(
|
||||
values.map(value => value.toString()),
|
||||
);
|
||||
|
||||
// To compare key-value pairs, we zip keys and values, and convert BNs to
|
||||
// strings to workaround Chai limitations when dealing with nested arrays
|
||||
expect(
|
||||
await Promise.all(
|
||||
[...Array(keys.length).keys()].map(async index => {
|
||||
const entry = await methods.at(map, index);
|
||||
return [entry[0].toString(), entry[1].toString()];
|
||||
}),
|
||||
),
|
||||
).to.have.same.deep.members(
|
||||
zip(
|
||||
keys.map(k => k.toString()),
|
||||
values.map(v => v.toString()),
|
||||
),
|
||||
);
|
||||
|
||||
// This also checks that both arrays have the same length
|
||||
expect((await methods.keys(map)).map(k => k.toString())).to.have.same.members(keys.map(key => key.toString()));
|
||||
}
|
||||
|
||||
it('starts empty', async function () {
|
||||
expect(await methods.contains(this.map, keyA)).to.equal(false);
|
||||
|
||||
await expectMembersMatch(this.map, [], []);
|
||||
});
|
||||
|
||||
describe('set', function () {
|
||||
it('adds a key', async function () {
|
||||
const receipt = await methods.set(this.map, keyA, valueA);
|
||||
expectEvent(receipt, events.setReturn, { ret0: true });
|
||||
|
||||
await expectMembersMatch(this.map, [keyA], [valueA]);
|
||||
});
|
||||
|
||||
it('adds several keys', async function () {
|
||||
await methods.set(this.map, keyA, valueA);
|
||||
await methods.set(this.map, keyB, valueB);
|
||||
|
||||
await expectMembersMatch(this.map, [keyA, keyB], [valueA, valueB]);
|
||||
expect(await methods.contains(this.map, keyC)).to.equal(false);
|
||||
});
|
||||
|
||||
it('returns false when adding keys already in the set', async function () {
|
||||
await methods.set(this.map, keyA, valueA);
|
||||
|
||||
const receipt = await methods.set(this.map, keyA, valueA);
|
||||
expectEvent(receipt, events.setReturn, { ret0: false });
|
||||
|
||||
await expectMembersMatch(this.map, [keyA], [valueA]);
|
||||
});
|
||||
|
||||
it('updates values for keys already in the set', async function () {
|
||||
await methods.set(this.map, keyA, valueA);
|
||||
await methods.set(this.map, keyA, valueB);
|
||||
|
||||
await expectMembersMatch(this.map, [keyA], [valueB]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('remove', function () {
|
||||
it('removes added keys', async function () {
|
||||
await methods.set(this.map, keyA, valueA);
|
||||
|
||||
const receipt = await methods.remove(this.map, keyA);
|
||||
expectEvent(receipt, events.removeReturn, { ret0: true });
|
||||
|
||||
expect(await methods.contains(this.map, keyA)).to.equal(false);
|
||||
await expectMembersMatch(this.map, [], []);
|
||||
});
|
||||
|
||||
it('returns false when removing keys not in the set', async function () {
|
||||
const receipt = await methods.remove(this.map, keyA);
|
||||
expectEvent(receipt, events.removeReturn, { ret0: false });
|
||||
|
||||
expect(await methods.contains(this.map, keyA)).to.equal(false);
|
||||
});
|
||||
|
||||
it('adds and removes multiple keys', async function () {
|
||||
// []
|
||||
|
||||
await methods.set(this.map, keyA, valueA);
|
||||
await methods.set(this.map, keyC, valueC);
|
||||
|
||||
// [A, C]
|
||||
|
||||
await methods.remove(this.map, keyA);
|
||||
await methods.remove(this.map, keyB);
|
||||
|
||||
// [C]
|
||||
|
||||
await methods.set(this.map, keyB, valueB);
|
||||
|
||||
// [C, B]
|
||||
|
||||
await methods.set(this.map, keyA, valueA);
|
||||
await methods.remove(this.map, keyC);
|
||||
|
||||
// [A, B]
|
||||
|
||||
await methods.set(this.map, keyA, valueA);
|
||||
await methods.set(this.map, keyB, valueB);
|
||||
|
||||
// [A, B]
|
||||
|
||||
await methods.set(this.map, keyC, valueC);
|
||||
await methods.remove(this.map, keyA);
|
||||
|
||||
// [B, C]
|
||||
|
||||
await methods.set(this.map, keyA, valueA);
|
||||
await methods.remove(this.map, keyB);
|
||||
|
||||
// [A, C]
|
||||
|
||||
await expectMembersMatch(this.map, [keyA, keyC], [valueA, valueC]);
|
||||
|
||||
expect(await methods.contains(this.map, keyA)).to.equal(true);
|
||||
expect(await methods.contains(this.map, keyB)).to.equal(false);
|
||||
expect(await methods.contains(this.map, keyC)).to.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('read', function () {
|
||||
beforeEach(async function () {
|
||||
await methods.set(this.map, keyA, valueA);
|
||||
});
|
||||
|
||||
describe('get', function () {
|
||||
it('existing value', async function () {
|
||||
expect(await methods.get(this.map, keyA).then(r => r.toString())).to.be.equal(valueA.toString());
|
||||
});
|
||||
it('missing value', async function () {
|
||||
await expectRevert(methods.get(this.map, keyB), 'EnumerableMap: nonexistent key');
|
||||
});
|
||||
});
|
||||
|
||||
describe('get with message', function () {
|
||||
it('existing value', async function () {
|
||||
expect(await methods.getWithMessage(this.map, keyA, 'custom error string').then(r => r.toString())).to.be.equal(
|
||||
valueA.toString(),
|
||||
);
|
||||
});
|
||||
it('missing value', async function () {
|
||||
await expectRevert(methods.getWithMessage(this.map, keyB, 'custom error string'), 'custom error string');
|
||||
});
|
||||
});
|
||||
|
||||
describe('tryGet', function () {
|
||||
it('existing value', async function () {
|
||||
const result = await methods.tryGet(this.map, keyA);
|
||||
expect(result['0']).to.be.equal(true);
|
||||
expect(result['1'].toString()).to.be.equal(valueA.toString());
|
||||
});
|
||||
it('missing value', async function () {
|
||||
const result = await methods.tryGet(this.map, keyB);
|
||||
expect(result['0']).to.be.equal(false);
|
||||
expect(result['1'].toString()).to.be.equal(zeroValue.toString());
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
shouldBehaveLikeMap,
|
||||
};
|
||||
@@ -0,0 +1,154 @@
|
||||
const { BN, constants } = require('@openzeppelin/test-helpers');
|
||||
const { mapValues } = require('../../helpers/map-values');
|
||||
|
||||
const EnumerableMap = artifacts.require('$EnumerableMap');
|
||||
|
||||
const { shouldBehaveLikeMap } = require('./EnumerableMap.behavior');
|
||||
|
||||
const getMethods = ms => {
|
||||
return mapValues(
|
||||
ms,
|
||||
m =>
|
||||
(self, ...args) =>
|
||||
self.methods[m](0, ...args),
|
||||
);
|
||||
};
|
||||
|
||||
// Get the name of the library. In the transpiled code it will be EnumerableMapUpgradeable.
|
||||
const library = EnumerableMap._json.contractName.replace(/^\$/, '');
|
||||
|
||||
contract('EnumerableMap', function (accounts) {
|
||||
const [accountA, accountB, accountC] = accounts;
|
||||
|
||||
const keyA = new BN('7891');
|
||||
const keyB = new BN('451');
|
||||
const keyC = new BN('9592328');
|
||||
|
||||
const bytesA = '0xdeadbeef'.padEnd(66, '0');
|
||||
const bytesB = '0x0123456789'.padEnd(66, '0');
|
||||
const bytesC = '0x42424242'.padEnd(66, '0');
|
||||
|
||||
beforeEach(async function () {
|
||||
this.map = await EnumerableMap.new();
|
||||
});
|
||||
|
||||
// AddressToUintMap
|
||||
describe('AddressToUintMap', function () {
|
||||
shouldBehaveLikeMap(
|
||||
[accountA, accountB, accountC],
|
||||
[keyA, keyB, keyC],
|
||||
new BN('0'),
|
||||
getMethods({
|
||||
set: '$set(uint256,address,uint256)',
|
||||
get: '$get(uint256,address)',
|
||||
getWithMessage: '$get(uint256,address,string)',
|
||||
tryGet: '$tryGet(uint256,address)',
|
||||
remove: '$remove(uint256,address)',
|
||||
length: `$length_${library}_AddressToUintMap(uint256)`,
|
||||
at: `$at_${library}_AddressToUintMap(uint256,uint256)`,
|
||||
contains: '$contains(uint256,address)',
|
||||
keys: `$keys_${library}_AddressToUintMap(uint256)`,
|
||||
}),
|
||||
{
|
||||
setReturn: `return$set_${library}_AddressToUintMap_address_uint256`,
|
||||
removeReturn: `return$remove_${library}_AddressToUintMap_address`,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
// UintToAddressMap
|
||||
describe('UintToAddressMap', function () {
|
||||
shouldBehaveLikeMap(
|
||||
[keyA, keyB, keyC],
|
||||
[accountA, accountB, accountC],
|
||||
constants.ZERO_ADDRESS,
|
||||
getMethods({
|
||||
set: '$set(uint256,uint256,address)',
|
||||
get: `$get_${library}_UintToAddressMap(uint256,uint256)`,
|
||||
getWithMessage: `$get_${library}_UintToAddressMap(uint256,uint256,string)`,
|
||||
tryGet: `$tryGet_${library}_UintToAddressMap(uint256,uint256)`,
|
||||
remove: `$remove_${library}_UintToAddressMap(uint256,uint256)`,
|
||||
length: `$length_${library}_UintToAddressMap(uint256)`,
|
||||
at: `$at_${library}_UintToAddressMap(uint256,uint256)`,
|
||||
contains: `$contains_${library}_UintToAddressMap(uint256,uint256)`,
|
||||
keys: `$keys_${library}_UintToAddressMap(uint256)`,
|
||||
}),
|
||||
{
|
||||
setReturn: `return$set_${library}_UintToAddressMap_uint256_address`,
|
||||
removeReturn: `return$remove_${library}_UintToAddressMap_uint256`,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
// Bytes32ToBytes32Map
|
||||
describe('Bytes32ToBytes32Map', function () {
|
||||
shouldBehaveLikeMap(
|
||||
[keyA, keyB, keyC].map(k => '0x' + k.toString(16).padEnd(64, '0')),
|
||||
[bytesA, bytesB, bytesC],
|
||||
constants.ZERO_BYTES32,
|
||||
getMethods({
|
||||
set: '$set(uint256,bytes32,bytes32)',
|
||||
get: `$get_${library}_Bytes32ToBytes32Map(uint256,bytes32)`,
|
||||
getWithMessage: `$get_${library}_Bytes32ToBytes32Map(uint256,bytes32,string)`,
|
||||
tryGet: `$tryGet_${library}_Bytes32ToBytes32Map(uint256,bytes32)`,
|
||||
remove: `$remove_${library}_Bytes32ToBytes32Map(uint256,bytes32)`,
|
||||
length: `$length_${library}_Bytes32ToBytes32Map(uint256)`,
|
||||
at: `$at_${library}_Bytes32ToBytes32Map(uint256,uint256)`,
|
||||
contains: `$contains_${library}_Bytes32ToBytes32Map(uint256,bytes32)`,
|
||||
keys: `$keys_${library}_Bytes32ToBytes32Map(uint256)`,
|
||||
}),
|
||||
{
|
||||
setReturn: `return$set_${library}_Bytes32ToBytes32Map_bytes32_bytes32`,
|
||||
removeReturn: `return$remove_${library}_Bytes32ToBytes32Map_bytes32`,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
// UintToUintMap
|
||||
describe('UintToUintMap', function () {
|
||||
shouldBehaveLikeMap(
|
||||
[keyA, keyB, keyC],
|
||||
[keyA, keyB, keyC].map(k => k.add(new BN('1332'))),
|
||||
new BN('0'),
|
||||
getMethods({
|
||||
set: '$set(uint256,uint256,uint256)',
|
||||
get: `$get_${library}_UintToUintMap(uint256,uint256)`,
|
||||
getWithMessage: `$get_${library}_UintToUintMap(uint256,uint256,string)`,
|
||||
tryGet: `$tryGet_${library}_UintToUintMap(uint256,uint256)`,
|
||||
remove: `$remove_${library}_UintToUintMap(uint256,uint256)`,
|
||||
length: `$length_${library}_UintToUintMap(uint256)`,
|
||||
at: `$at_${library}_UintToUintMap(uint256,uint256)`,
|
||||
contains: `$contains_${library}_UintToUintMap(uint256,uint256)`,
|
||||
keys: `$keys_${library}_UintToUintMap(uint256)`,
|
||||
}),
|
||||
{
|
||||
setReturn: `return$set_${library}_UintToUintMap_uint256_uint256`,
|
||||
removeReturn: `return$remove_${library}_UintToUintMap_uint256`,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
// Bytes32ToUintMap
|
||||
describe('Bytes32ToUintMap', function () {
|
||||
shouldBehaveLikeMap(
|
||||
[bytesA, bytesB, bytesC],
|
||||
[keyA, keyB, keyC],
|
||||
new BN('0'),
|
||||
getMethods({
|
||||
set: '$set(uint256,bytes32,uint256)',
|
||||
get: `$get_${library}_Bytes32ToUintMap(uint256,bytes32)`,
|
||||
getWithMessage: `$get_${library}_Bytes32ToUintMap(uint256,bytes32,string)`,
|
||||
tryGet: `$tryGet_${library}_Bytes32ToUintMap(uint256,bytes32)`,
|
||||
remove: `$remove_${library}_Bytes32ToUintMap(uint256,bytes32)`,
|
||||
length: `$length_${library}_Bytes32ToUintMap(uint256)`,
|
||||
at: `$at_${library}_Bytes32ToUintMap(uint256,uint256)`,
|
||||
contains: `$contains_${library}_Bytes32ToUintMap(uint256,bytes32)`,
|
||||
keys: `$keys_${library}_Bytes32ToUintMap(uint256)`,
|
||||
}),
|
||||
{
|
||||
setReturn: `return$set_${library}_Bytes32ToUintMap_bytes32_uint256`,
|
||||
removeReturn: `return$remove_${library}_Bytes32ToUintMap_bytes32`,
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,129 @@
|
||||
const { expectEvent, expectRevert } = require('@openzeppelin/test-helpers');
|
||||
const { expect } = require('chai');
|
||||
|
||||
function shouldBehaveLikeSet(values, methods, events) {
|
||||
const [valueA, valueB, valueC] = values;
|
||||
|
||||
async function expectMembersMatch(set, values) {
|
||||
const contains = await Promise.all(values.map(value => methods.contains(set, value)));
|
||||
expect(contains.every(Boolean)).to.be.equal(true);
|
||||
|
||||
const length = await methods.length(set);
|
||||
expect(length).to.bignumber.equal(values.length.toString());
|
||||
|
||||
// To compare values we convert to strings to workaround Chai
|
||||
// limitations when dealing with nested arrays (required for BNs)
|
||||
const indexedValues = await Promise.all(
|
||||
Array(values.length)
|
||||
.fill()
|
||||
.map((_, index) => methods.at(set, index)),
|
||||
);
|
||||
expect(indexedValues.map(v => v.toString())).to.have.same.members(values.map(v => v.toString()));
|
||||
|
||||
const returnedValues = await methods.values(set);
|
||||
expect(returnedValues.map(v => v.toString())).to.have.same.members(values.map(v => v.toString()));
|
||||
}
|
||||
|
||||
it('starts empty', async function () {
|
||||
expect(await methods.contains(this.set, valueA)).to.equal(false);
|
||||
|
||||
await expectMembersMatch(this.set, []);
|
||||
});
|
||||
|
||||
describe('add', function () {
|
||||
it('adds a value', async function () {
|
||||
const receipt = await methods.add(this.set, valueA);
|
||||
expectEvent(receipt, events.addReturn, { ret0: true });
|
||||
|
||||
await expectMembersMatch(this.set, [valueA]);
|
||||
});
|
||||
|
||||
it('adds several values', async function () {
|
||||
await methods.add(this.set, valueA);
|
||||
await methods.add(this.set, valueB);
|
||||
|
||||
await expectMembersMatch(this.set, [valueA, valueB]);
|
||||
expect(await methods.contains(this.set, valueC)).to.equal(false);
|
||||
});
|
||||
|
||||
it('returns false when adding values already in the set', async function () {
|
||||
await methods.add(this.set, valueA);
|
||||
|
||||
const receipt = await methods.add(this.set, valueA);
|
||||
expectEvent(receipt, events.addReturn, { ret0: false });
|
||||
|
||||
await expectMembersMatch(this.set, [valueA]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('at', function () {
|
||||
it('reverts when retrieving non-existent elements', async function () {
|
||||
await expectRevert.unspecified(methods.at(this.set, 0));
|
||||
});
|
||||
});
|
||||
|
||||
describe('remove', function () {
|
||||
it('removes added values', async function () {
|
||||
await methods.add(this.set, valueA);
|
||||
|
||||
const receipt = await methods.remove(this.set, valueA);
|
||||
expectEvent(receipt, events.removeReturn, { ret0: true });
|
||||
|
||||
expect(await methods.contains(this.set, valueA)).to.equal(false);
|
||||
await expectMembersMatch(this.set, []);
|
||||
});
|
||||
|
||||
it('returns false when removing values not in the set', async function () {
|
||||
const receipt = await methods.remove(this.set, valueA);
|
||||
expectEvent(receipt, events.removeReturn, { ret0: false });
|
||||
|
||||
expect(await methods.contains(this.set, valueA)).to.equal(false);
|
||||
});
|
||||
|
||||
it('adds and removes multiple values', async function () {
|
||||
// []
|
||||
|
||||
await methods.add(this.set, valueA);
|
||||
await methods.add(this.set, valueC);
|
||||
|
||||
// [A, C]
|
||||
|
||||
await methods.remove(this.set, valueA);
|
||||
await methods.remove(this.set, valueB);
|
||||
|
||||
// [C]
|
||||
|
||||
await methods.add(this.set, valueB);
|
||||
|
||||
// [C, B]
|
||||
|
||||
await methods.add(this.set, valueA);
|
||||
await methods.remove(this.set, valueC);
|
||||
|
||||
// [A, B]
|
||||
|
||||
await methods.add(this.set, valueA);
|
||||
await methods.add(this.set, valueB);
|
||||
|
||||
// [A, B]
|
||||
|
||||
await methods.add(this.set, valueC);
|
||||
await methods.remove(this.set, valueA);
|
||||
|
||||
// [B, C]
|
||||
|
||||
await methods.add(this.set, valueA);
|
||||
await methods.remove(this.set, valueB);
|
||||
|
||||
// [A, C]
|
||||
|
||||
await expectMembersMatch(this.set, [valueA, valueC]);
|
||||
|
||||
expect(await methods.contains(this.set, valueB)).to.equal(false);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
shouldBehaveLikeSet,
|
||||
};
|
||||
@@ -0,0 +1,79 @@
|
||||
const EnumerableSet = artifacts.require('$EnumerableSet');
|
||||
const { mapValues } = require('../../helpers/map-values');
|
||||
|
||||
const { shouldBehaveLikeSet } = require('./EnumerableSet.behavior');
|
||||
|
||||
const getMethods = ms => {
|
||||
return mapValues(
|
||||
ms,
|
||||
m =>
|
||||
(self, ...args) =>
|
||||
self.methods[m](0, ...args),
|
||||
);
|
||||
};
|
||||
|
||||
// Get the name of the library. In the transpiled code it will be EnumerableSetUpgradeable.
|
||||
const library = EnumerableSet._json.contractName.replace(/^\$/, '');
|
||||
|
||||
contract('EnumerableSet', function (accounts) {
|
||||
beforeEach(async function () {
|
||||
this.set = await EnumerableSet.new();
|
||||
});
|
||||
|
||||
// Bytes32Set
|
||||
describe('EnumerableBytes32Set', function () {
|
||||
shouldBehaveLikeSet(
|
||||
['0xdeadbeef', '0x0123456789', '0x42424242'].map(e => e.padEnd(66, '0')),
|
||||
getMethods({
|
||||
add: '$add(uint256,bytes32)',
|
||||
remove: '$remove(uint256,bytes32)',
|
||||
contains: '$contains(uint256,bytes32)',
|
||||
length: `$length_${library}_Bytes32Set(uint256)`,
|
||||
at: `$at_${library}_Bytes32Set(uint256,uint256)`,
|
||||
values: `$values_${library}_Bytes32Set(uint256)`,
|
||||
}),
|
||||
{
|
||||
addReturn: `return$add_${library}_Bytes32Set_bytes32`,
|
||||
removeReturn: `return$remove_${library}_Bytes32Set_bytes32`,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
// AddressSet
|
||||
describe('EnumerableAddressSet', function () {
|
||||
shouldBehaveLikeSet(
|
||||
accounts,
|
||||
getMethods({
|
||||
add: '$add(uint256,address)',
|
||||
remove: '$remove(uint256,address)',
|
||||
contains: '$contains(uint256,address)',
|
||||
length: `$length_${library}_AddressSet(uint256)`,
|
||||
at: `$at_${library}_AddressSet(uint256,uint256)`,
|
||||
values: `$values_${library}_AddressSet(uint256)`,
|
||||
}),
|
||||
{
|
||||
addReturn: `return$add_${library}_AddressSet_address`,
|
||||
removeReturn: `return$remove_${library}_AddressSet_address`,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
// UintSet
|
||||
describe('EnumerableUintSet', function () {
|
||||
shouldBehaveLikeSet(
|
||||
[1234, 5678, 9101112].map(e => web3.utils.toBN(e)),
|
||||
getMethods({
|
||||
add: '$add(uint256,uint256)',
|
||||
remove: '$remove(uint256,uint256)',
|
||||
contains: '$contains(uint256,uint256)',
|
||||
length: `$length_${library}_UintSet(uint256)`,
|
||||
at: `$at_${library}_UintSet(uint256,uint256)`,
|
||||
values: `$values_${library}_UintSet(uint256)`,
|
||||
}),
|
||||
{
|
||||
addReturn: `return$add_${library}_UintSet_uint256`,
|
||||
removeReturn: `return$remove_${library}_UintSet_uint256`,
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user