Merge tag 'v2.2.0' of github.com:OpenZeppelin/openzeppelin-solidity

v2.2.0
This commit is contained in:
Francisco Giordano
2019-05-28 17:40:27 -03:00
162 changed files with 3035 additions and 2330 deletions

View File

@ -54,7 +54,7 @@ contract('AllowanceCrowdsale', function ([_, investor, wallet, purchaser, tokenW
});
describe('check remaining allowance', function () {
it('should report correct allowace left', async function () {
it('should report correct allowance left', async function () {
const remainingAllowance = tokenAllowance.sub(expectedTokenAmount);
await this.crowdsale.buyTokens(investor, { value: value, from: purchaser });
(await this.crowdsale.remainingTokens()).should.be.bignumber.equal(remainingAllowance);
@ -72,7 +72,7 @@ contract('AllowanceCrowdsale', function ([_, investor, wallet, purchaser, tokenW
});
});
describe('when token wallet is different from token address', function () {
describe('when token wallet is the zero address', function () {
it('creation reverts', async function () {
this.token = await SimpleToken.new({ from: tokenWallet });
await shouldFail.reverting(AllowanceCrowdsaleImpl.new(rate, wallet, this.token.address, ZERO_ADDRESS));

View File

@ -31,7 +31,7 @@ contract('IncreasingPriceCrowdsale', function ([_, investor, wallet, purchaser])
));
});
it('reverts with a final equal to the initial rate', async function () {
it('reverts with a final rate equal to the initial rate', async function () {
await shouldFail.reverting(IncreasingPriceCrowdsaleImpl.new(
this.startTime, this.closingTime, wallet, this.token.address, initialRate, initialRate
));

View File

@ -1,4 +1,4 @@
const { BN, ether, shouldFail, time } = require('openzeppelin-test-helpers');
const { BN, ether, expectEvent, shouldFail, time } = require('openzeppelin-test-helpers');
const TimedCrowdsaleImpl = artifacts.require('TimedCrowdsaleImpl');
const SimpleToken = artifacts.require('SimpleTokenMock');
@ -73,5 +73,62 @@ contract('TimedCrowdsale', function ([_, investor, wallet, purchaser]) {
await shouldFail.reverting(this.crowdsale.buyTokens(investor, { value: value, from: purchaser }));
});
});
describe('extending closing time', function () {
it('should not reduce duration', async function () {
// Same date
await shouldFail.reverting(this.crowdsale.extendTime(this.closingTime));
// Prescending date
const newClosingTime = this.closingTime.sub(time.duration.seconds(1));
await shouldFail.reverting(this.crowdsale.extendTime(newClosingTime));
});
context('before crowdsale start', function () {
beforeEach(async function () {
(await this.crowdsale.isOpen()).should.equal(false);
await shouldFail.reverting(this.crowdsale.send(value));
});
it('it extends end time', async function () {
const newClosingTime = this.closingTime.add(time.duration.days(1));
const { logs } = await this.crowdsale.extendTime(newClosingTime);
expectEvent.inLogs(logs, 'TimedCrowdsaleExtended', {
prevClosingTime: this.closingTime,
newClosingTime: newClosingTime,
});
(await this.crowdsale.closingTime()).should.be.bignumber.equal(newClosingTime);
});
});
context('after crowdsale start', function () {
beforeEach(async function () {
await time.increaseTo(this.openingTime);
(await this.crowdsale.isOpen()).should.equal(true);
await this.crowdsale.send(value);
});
it('it extends end time', async function () {
const newClosingTime = this.closingTime.add(time.duration.days(1));
const { logs } = await this.crowdsale.extendTime(newClosingTime);
expectEvent.inLogs(logs, 'TimedCrowdsaleExtended', {
prevClosingTime: this.closingTime,
newClosingTime: newClosingTime,
});
(await this.crowdsale.closingTime()).should.be.bignumber.equal(newClosingTime);
});
});
context('after crowdsale end', function () {
beforeEach(async function () {
await time.increaseTo(this.afterClosingTime);
});
it('it reverts', async function () {
const newClosingTime = await time.latest();
await shouldFail.reverting(this.crowdsale.extendTime(newClosingTime));
});
});
});
});
});

View File

@ -1,5 +1,6 @@
const { shouldFail } = require('openzeppelin-test-helpers');
const { signMessage, toEthSignedMessageHash } = require('../helpers/sign');
const { constants, shouldFail } = require('openzeppelin-test-helpers');
const { ZERO_ADDRESS } = constants;
const { toEthSignedMessageHash, fixSignature } = require('../helpers/sign');
const ECDSAMock = artifacts.require('ECDSAMock');
@ -19,10 +20,10 @@ contract('ECDSA', function ([_, anyone]) {
const signatureWithoutVersion = '0x5d99b6f7f6d1f73d1a26497f2b1c89b24c0993913f86e9a2d02cd69887d9c94f3c880358579d811b21dd1b7fd9bb01c1d81d10e69f0384e675c32b39643be892';
context('with 00 as version value', function () {
it('works', async function () {
it('returns 0', async function () {
const version = '00';
const signature = signatureWithoutVersion + version;
(await this.ecdsa.recover(TEST_MESSAGE, signature)).should.equal(signer);
(await this.ecdsa.recover(TEST_MESSAGE, signature)).should.equal(ZERO_ADDRESS);
});
});
@ -40,8 +41,7 @@ contract('ECDSA', function ([_, anyone]) {
// The only valid values are 0, 1, 27 and 28.
const version = '02';
const signature = signatureWithoutVersion + version;
(await this.ecdsa.recover(TEST_MESSAGE, signature)).should.equal(
'0x0000000000000000000000000000000000000000');
(await this.ecdsa.recover(TEST_MESSAGE, signature)).should.equal(ZERO_ADDRESS);
});
});
});
@ -52,14 +52,14 @@ contract('ECDSA', function ([_, anyone]) {
const signatureWithoutVersion = '0x331fe75a821c982f9127538858900d87d3ec1f9f737338ad67cad133fa48feff48e6fa0c18abc62e42820f05943e47af3e9fbe306ce74d64094bdf1691ee53e0';
context('with 01 as version value', function () {
it('works', async function () {
it('returns 0', async function () {
const version = '01';
const signature = signatureWithoutVersion + version;
(await this.ecdsa.recover(TEST_MESSAGE, signature)).should.equal(signer);
(await this.ecdsa.recover(TEST_MESSAGE, signature)).should.equal(ZERO_ADDRESS);
});
});
context('with 28 signature', function () {
context('with 28 as version value', function () {
it('works', async function () {
const version = '1c'; // 28 = 1c.
const signature = signatureWithoutVersion + version;
@ -73,17 +73,26 @@ contract('ECDSA', function ([_, anyone]) {
// The only valid values are 0, 1, 27 and 28.
const version = '02';
const signature = signatureWithoutVersion + version;
(await this.ecdsa.recover(TEST_MESSAGE, signature)).should.equal(
'0x0000000000000000000000000000000000000000');
(await this.ecdsa.recover(TEST_MESSAGE, signature)).should.equal(ZERO_ADDRESS);
});
});
});
context('with high-s value signature', function () {
it('returns 0', async function () {
const message = '0xb94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9';
// eslint-disable-next-line max-len
const highSSignature = '0xe742ff452d41413616a5bf43fe15dd88294e983d3d36206c2712f39083d638bde0a0fc89be718fbc1033e1d30d78be1c68081562ed2e97af876f286f3453231d1b';
(await this.ecdsa.recover(message, highSSignature)).should.equal(ZERO_ADDRESS);
});
});
context('using web3.eth.sign', function () {
context('with correct signature', function () {
it('returns signer address', async function () {
// Create the signature
const signature = await signMessage(anyone, TEST_MESSAGE);
const signature = fixSignature(await web3.eth.sign(TEST_MESSAGE, anyone));
// Recover the signer address from the generated message and signature.
(await this.ecdsa.recover(
@ -96,23 +105,23 @@ contract('ECDSA', function ([_, anyone]) {
context('with wrong signature', function () {
it('does not return signer address', async function () {
// Create the signature
const signature = await signMessage(anyone, TEST_MESSAGE);
const signature = await web3.eth.sign(TEST_MESSAGE, anyone);
// Recover the signer address from the generated message and wrong signature.
(await this.ecdsa.recover(WRONG_MESSAGE, signature)).should.not.equal(anyone);
});
});
});
});
context('with small hash', function () {
// @TODO - remove `skip` once we upgrade to solc^0.5
it.skip('reverts', async function () {
// Create the signature
const signature = await signMessage(anyone, TEST_MESSAGE);
await shouldFail.reverting(
this.ecdsa.recover(TEST_MESSAGE.substring(2), signature)
);
context('with small hash', function () {
// @TODO - remove `skip` once we upgrade to solc^0.5
it.skip('reverts', async function () {
// Create the signature
const signature = await web3.eth.sign(TEST_MESSAGE, anyone);
await shouldFail.reverting(
this.ecdsa.recover(TEST_MESSAGE.substring(2), signature)
);
});
});
});

View File

@ -1,37 +0,0 @@
const { BN } = require('openzeppelin-test-helpers');
const CounterImpl = artifacts.require('CounterImpl');
const EXPECTED = [new BN(1), new BN(2), new BN(3), new BN(4)];
const KEY1 = web3.utils.sha3('key1');
const KEY2 = web3.utils.sha3('key2');
contract('Counter', function ([_, owner]) {
beforeEach(async function () {
this.mock = await CounterImpl.new({ from: owner });
});
context('custom key', async function () {
it('should return expected values', async function () {
for (const expectedId of EXPECTED) {
await this.mock.doThing(KEY1, { from: owner });
const actualId = await this.mock.theId();
actualId.should.be.bignumber.equal(expectedId);
}
});
});
context('parallel keys', async function () {
it('should return expected values for each counter', async function () {
for (const expectedId of EXPECTED) {
await this.mock.doThing(KEY1, { from: owner });
let actualId = await this.mock.theId();
actualId.should.be.bignumber.equal(expectedId);
await this.mock.doThing(KEY2, { from: owner });
actualId = await this.mock.theId();
actualId.should.be.bignumber.equal(expectedId);
}
});
});
});

View File

@ -0,0 +1,58 @@
const { shouldFail } = require('openzeppelin-test-helpers');
const CountersImpl = artifacts.require('CountersImpl');
contract('Counters', function () {
beforeEach(async function () {
this.counter = await CountersImpl.new();
});
it('starts at zero', async function () {
(await this.counter.current()).should.be.bignumber.equal('0');
});
describe('increment', function () {
it('increments the current value by one', async function () {
await this.counter.increment();
(await this.counter.current()).should.be.bignumber.equal('1');
});
it('can be called multiple times', async function () {
await this.counter.increment();
await this.counter.increment();
await this.counter.increment();
(await this.counter.current()).should.be.bignumber.equal('3');
});
});
describe('decrement', function () {
beforeEach(async function () {
await this.counter.increment();
(await this.counter.current()).should.be.bignumber.equal('1');
});
it('decrements the current value by one', async function () {
await this.counter.decrement();
(await this.counter.current()).should.be.bignumber.equal('0');
});
it('reverts if the current value is 0', async function () {
await this.counter.decrement();
await shouldFail.reverting(this.counter.decrement());
});
it('can be called multiple times', async function () {
await this.counter.increment();
await this.counter.increment();
(await this.counter.current()).should.be.bignumber.equal('3');
await this.counter.decrement();
await this.counter.decrement();
await this.counter.decrement();
(await this.counter.current()).should.be.bignumber.equal('0');
});
});
});

View File

@ -0,0 +1,23 @@
require('openzeppelin-test-helpers');
const ERC20MetadataMock = artifacts.require('ERC20MetadataMock');
const metadataURI = 'https://example.com';
describe('ERC20Metadata', function () {
beforeEach(async function () {
this.token = await ERC20MetadataMock.new(metadataURI);
});
it('responds with the metadata', async function () {
(await this.token.tokenURI()).should.equal(metadataURI);
});
describe('setTokenURI', function () {
it('changes the original URI', async function () {
const newMetadataURI = 'https://betterexample.com';
await this.token.setTokenURI(newMetadataURI);
(await this.token.tokenURI()).should.equal(newMetadataURI);
});
});
});

View File

@ -1,15 +0,0 @@
require('openzeppelin-test-helpers');
const ERC20WithMetadataMock = artifacts.require('ERC20WithMetadataMock');
const metadataURI = 'https://example.com';
describe('ERC20WithMetadata', function () {
beforeEach(async function () {
this.token = await ERC20WithMetadataMock.new(metadataURI);
});
it('responds with the metadata', async function () {
(await this.token.tokenURI()).should.equal(metadataURI);
});
});

View File

@ -44,6 +44,40 @@ contract('ERC20Migrator', function ([_, owner, recipient, anotherAccount]) {
});
});
context('before starting the migration', function () {
it('returns the zero address for the new token', async function () {
(await this.migrator.newToken()).should.be.equal(ZERO_ADDRESS);
});
describe('migrateAll', function () {
const amount = totalSupply;
describe('when the approved balance is equal to the owned balance', function () {
beforeEach('approving the whole balance to the new contract', async function () {
await this.legacyToken.approve(this.migrator.address, amount, { from: owner });
});
it('reverts', async function () {
await shouldFail.reverting(this.migrator.migrateAll(owner));
});
});
});
describe('migrate', function () {
const amount = new BN(50);
describe('when the amount is equal to the approved value', function () {
beforeEach('approving tokens to the new contract', async function () {
await this.legacyToken.approve(this.migrator.address, amount, { from: owner });
});
it('reverts', async function () {
await shouldFail.reverting(this.migrator.migrate(owner, amount));
});
});
});
});
describe('once migration began', function () {
beforeEach('beginning migration', async function () {
await this.newToken.addMinter(this.migrator.address);

View File

@ -0,0 +1,197 @@
const { BN, expectEvent, shouldFail } = require('openzeppelin-test-helpers');
const ERC20SnapshotMock = artifacts.require('ERC20SnapshotMock');
contract('ERC20Snapshot', function ([_, initialHolder, recipient, anyone]) {
const initialSupply = new BN(100);
beforeEach(async function () {
this.token = await ERC20SnapshotMock.new(initialHolder, initialSupply);
});
describe('snapshot', function () {
it('emits a snapshot event', async function () {
const { logs } = await this.token.snapshot();
expectEvent.inLogs(logs, 'Snapshot');
});
it('creates increasing snapshots ids, starting from 1', async function () {
for (const id of ['1', '2', '3', '4', '5']) {
const { logs } = await this.token.snapshot();
expectEvent.inLogs(logs, 'Snapshot', { id });
}
});
});
describe('totalSupplyAt', function () {
it('reverts with a snapshot id of 0', async function () {
await shouldFail.reverting(this.token.totalSupplyAt(0));
});
it('reverts with a not-yet-created snapshot id', async function () {
await shouldFail.reverting(this.token.totalSupplyAt(1));
});
context('with initial snapshot', function () {
beforeEach(async function () {
this.initialSnapshotId = new BN('1');
const { logs } = await this.token.snapshot();
expectEvent.inLogs(logs, 'Snapshot', { id: this.initialSnapshotId });
});
context('with no supply changes after the snapshot', function () {
it('returns the current total supply', async function () {
(await this.token.totalSupplyAt(this.initialSnapshotId)).should.be.bignumber.equal(initialSupply);
});
});
context('with supply changes after the snapshot', function () {
beforeEach(async function () {
await this.token.mint(anyone, new BN('50'));
await this.token.burn(initialHolder, new BN('20'));
});
it('returns the total supply before the changes', async function () {
(await this.token.totalSupplyAt(this.initialSnapshotId)).should.be.bignumber.equal(initialSupply);
});
context('with a second snapshot after supply changes', function () {
beforeEach(async function () {
this.secondSnapshotId = new BN('2');
const { logs } = await this.token.snapshot();
expectEvent.inLogs(logs, 'Snapshot', { id: this.secondSnapshotId });
});
it('snapshots return the supply before and after the changes', async function () {
(await this.token.totalSupplyAt(this.initialSnapshotId)).should.be.bignumber.equal(initialSupply);
(await this.token.totalSupplyAt(this.secondSnapshotId)).should.be.bignumber.equal(
await this.token.totalSupply()
);
});
});
context('with multiple snapshots after supply changes', function () {
beforeEach(async function () {
this.secondSnapshotIds = ['2', '3', '4'];
for (const id of this.secondSnapshotIds) {
const { logs } = await this.token.snapshot();
expectEvent.inLogs(logs, 'Snapshot', { id });
}
});
it('all posterior snapshots return the supply after the changes', async function () {
(await this.token.totalSupplyAt(this.initialSnapshotId)).should.be.bignumber.equal(initialSupply);
const currentSupply = await this.token.totalSupply();
for (const id of this.secondSnapshotIds) {
(await this.token.totalSupplyAt(id)).should.be.bignumber.equal(currentSupply);
}
});
});
});
});
});
describe('balanceOfAt', function () {
it('reverts with a snapshot id of 0', async function () {
await shouldFail.reverting(this.token.balanceOfAt(anyone, 0));
});
it('reverts with a not-yet-created snapshot id', async function () {
await shouldFail.reverting(this.token.balanceOfAt(anyone, 1));
});
context('with initial snapshot', function () {
beforeEach(async function () {
this.initialSnapshotId = new BN('1');
const { logs } = await this.token.snapshot();
expectEvent.inLogs(logs, 'Snapshot', { id: this.initialSnapshotId });
});
context('with no balance changes after the snapshot', function () {
it('returns the current balance for all accounts', async function () {
(await this.token.balanceOfAt(initialHolder, this.initialSnapshotId))
.should.be.bignumber.equal(initialSupply);
(await this.token.balanceOfAt(recipient, this.initialSnapshotId)).should.be.bignumber.equal('0');
(await this.token.balanceOfAt(anyone, this.initialSnapshotId)).should.be.bignumber.equal('0');
});
});
context('with balance changes after the snapshot', function () {
beforeEach(async function () {
await this.token.transfer(recipient, new BN('10'), { from: initialHolder });
await this.token.mint(recipient, new BN('50'));
await this.token.burn(initialHolder, new BN('20'));
});
it('returns the balances before the changes', async function () {
(await this.token.balanceOfAt(initialHolder, this.initialSnapshotId))
.should.be.bignumber.equal(initialSupply);
(await this.token.balanceOfAt(recipient, this.initialSnapshotId)).should.be.bignumber.equal('0');
(await this.token.balanceOfAt(anyone, this.initialSnapshotId)).should.be.bignumber.equal('0');
});
context('with a second snapshot after supply changes', function () {
beforeEach(async function () {
this.secondSnapshotId = new BN('2');
const { logs } = await this.token.snapshot();
expectEvent.inLogs(logs, 'Snapshot', { id: this.secondSnapshotId });
});
it('snapshots return the balances before and after the changes', async function () {
(await this.token.balanceOfAt(initialHolder, this.initialSnapshotId))
.should.be.bignumber.equal(initialSupply);
(await this.token.balanceOfAt(recipient, this.initialSnapshotId)).should.be.bignumber.equal('0');
(await this.token.balanceOfAt(anyone, this.initialSnapshotId)).should.be.bignumber.equal('0');
(await this.token.balanceOfAt(initialHolder, this.secondSnapshotId)).should.be.bignumber.equal(
await this.token.balanceOf(initialHolder)
);
(await this.token.balanceOfAt(recipient, this.secondSnapshotId)).should.be.bignumber.equal(
await this.token.balanceOf(recipient)
);
(await this.token.balanceOfAt(anyone, this.secondSnapshotId)).should.be.bignumber.equal(
await this.token.balanceOf(anyone)
);
});
});
context('with multiple snapshots after supply changes', function () {
beforeEach(async function () {
this.secondSnapshotIds = ['2', '3', '4'];
for (const id of this.secondSnapshotIds) {
const { logs } = await this.token.snapshot();
expectEvent.inLogs(logs, 'Snapshot', { id });
}
});
it('all posterior snapshots return the supply after the changes', async function () {
(await this.token.balanceOfAt(initialHolder, this.initialSnapshotId))
.should.be.bignumber.equal(initialSupply);
(await this.token.balanceOfAt(recipient, this.initialSnapshotId)).should.be.bignumber.equal('0');
(await this.token.balanceOfAt(anyone, this.initialSnapshotId)).should.be.bignumber.equal('0');
for (const id of this.secondSnapshotIds) {
(await this.token.balanceOfAt(initialHolder, id)).should.be.bignumber.equal(
await this.token.balanceOf(initialHolder)
);
(await this.token.balanceOfAt(recipient, id)).should.be.bignumber.equal(
await this.token.balanceOf(recipient)
);
(await this.token.balanceOfAt(anyone, id)).should.be.bignumber.equal(
await this.token.balanceOf(anyone)
);
}
});
});
});
});
});
});

View File

@ -8,34 +8,43 @@ contract('SignedSafeMath', function () {
this.safeMath = await SignedSafeMathMock.new();
});
async function testCommutative (fn, lhs, rhs, expected) {
(await fn(lhs, rhs)).should.be.bignumber.equal(expected);
(await fn(rhs, lhs)).should.be.bignumber.equal(expected);
}
async function testFailsCommutative (fn, lhs, rhs) {
await shouldFail.reverting(fn(lhs, rhs));
await shouldFail.reverting(fn(rhs, lhs));
}
describe('add', function () {
it('adds correctly if it does not overflow and the result is positve', async 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 this.safeMath.add(a, b)).should.be.bignumber.equal(a.add(b));
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;
const result = await this.safeMath.add(a, b);
result.should.be.bignumber.equal(a.add(b));
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 shouldFail.reverting(this.safeMath.add(a, b));
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 shouldFail.reverting(this.safeMath.add(a, b));
await testFailsCommutative(this.safeMath.add, a, b);
});
});
@ -76,37 +85,28 @@ contract('SignedSafeMath', function () {
const a = new BN('5678');
const b = new BN('-1234');
const result = await this.safeMath.mul(a, b);
result.should.be.bignumber.equal(a.mul(b));
await testCommutative(this.safeMath.mul, a, b, a.mul(b));
});
it('handles a zero product correctly', async function () {
it('multiplies by zero correctly', async function () {
const a = new BN('0');
const b = new BN('5678');
const result = await this.safeMath.mul(a, b);
result.should.be.bignumber.equal(a.mul(b));
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 shouldFail.reverting(this.safeMath.mul(a, b));
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 shouldFail.reverting(this.safeMath.mul(a, b));
});
it('reverts when -1 is multiplied by minimum integer', async function () {
const a = new BN('-1');
const b = MIN_INT256;
await shouldFail.reverting(this.safeMath.mul(a, b));
await testFailsCommutative(this.safeMath.mul, a, b);
});
});
@ -119,7 +119,21 @@ contract('SignedSafeMath', function () {
result.should.be.bignumber.equal(a.div(b));
});
it('reverts on zero division', async function () {
it('divides zero correctly', async function () {
const a = new BN('0');
const b = new BN('5678');
(await this.safeMath.div(a, b)).should.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');
(await this.safeMath.div(a, b)).should.be.bignumber.equal('1');
});
it('reverts on division by zero', async function () {
const a = new BN('-5678');
const b = new BN('0');

View File

@ -9,9 +9,21 @@ function toEthSignedMessageHash (messageHex) {
return web3.utils.sha3(Buffer.concat([prefix, messageBuffer]));
}
function fixSignature (signature) {
// in geth its always 27/28, in ganache its 0/1. Change to 27/28 to prevent
// signature malleability if version is 0/1
// see https://github.com/ethereum/go-ethereum/blob/v1.8.23/internal/ethapi/api.go#L465
let v = parseInt(signature.slice(130, 132), 16);
if (v < 27) {
v += 27;
}
const vHex = v.toString(16);
return signature.slice(0, 130) + vHex;
}
// signs message in node (ganache auto-applies "Ethereum Signed Message" prefix)
const signMessage = (signer, messageHex = '0x') => {
return web3.eth.sign(messageHex, signer);
async function signMessage (signer, messageHex = '0x') {
return fixSignature(await web3.eth.sign(messageHex, signer));
};
/**
@ -50,5 +62,6 @@ const getSignFor = (contract, signer) => (redeemer, methodName, methodArgs = [])
module.exports = {
signMessage,
toEthSignedMessageHash,
fixSignature,
getSignFor,
};

View File

@ -17,7 +17,7 @@ contract('Pausable', function ([_, pauser, otherPauser, anyone, ...otherAccounts
shouldBehaveLikePublicRole(pauser, otherPauser, otherAccounts, 'pauser');
});
context('when unapused', function () {
context('when unpaused', function () {
beforeEach(async function () {
(await this.pausable.paused()).should.equal(false);
});

View File

@ -8,19 +8,29 @@ contract('SafeMath', function () {
this.safeMath = await SafeMathMock.new();
});
async function testCommutative (fn, lhs, rhs, expected) {
(await fn(lhs, rhs)).should.be.bignumber.equal(expected);
(await fn(rhs, lhs)).should.be.bignumber.equal(expected);
}
async function testFailsCommutative (fn, lhs, rhs) {
await shouldFail.reverting(fn(lhs, rhs));
await shouldFail.reverting(fn(rhs, lhs));
}
describe('add', function () {
it('adds correctly', async function () {
const a = new BN('5678');
const b = new BN('1234');
(await this.safeMath.add(a, b)).should.be.bignumber.equal(a.add(b));
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 shouldFail.reverting(this.safeMath.add(a, b));
await testFailsCommutative(this.safeMath.add, a, b);
});
});
@ -45,28 +55,21 @@ contract('SafeMath', function () {
const a = new BN('1234');
const b = new BN('5678');
(await this.safeMath.mul(a, b)).should.be.bignumber.equal(a.mul(b));
await testCommutative(this.safeMath.mul, a, b, a.mul(b));
});
it('handles a zero product correctly (first number as zero)', async function () {
it('multiplies by zero correctly', async function () {
const a = new BN('0');
const b = new BN('5678');
(await this.safeMath.mul(a, b)).should.be.bignumber.equal(a.mul(b));
});
it('handles a zero product correctly (second number as zero)', async function () {
const a = new BN('5678');
const b = new BN('0');
(await this.safeMath.mul(a, b)).should.be.bignumber.equal(a.mul(b));
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 shouldFail.reverting(this.safeMath.mul(a, b));
await testFailsCommutative(this.safeMath.mul, a, b);
});
});
@ -92,7 +95,7 @@ contract('SafeMath', function () {
(await this.safeMath.div(a, b)).should.be.bignumber.equal('1');
});
it('reverts on zero division', async function () {
it('reverts on divison by zero', async function () {
const a = new BN('5678');
const b = new BN('0');

View File

@ -16,7 +16,7 @@ function shouldBehaveLikeOwnable (owner, [anyone]) {
(await this.ownable.isOwner({ from: anyone })).should.be.equal(true);
});
it('should prevent non-owners from transfering', async function () {
it('should prevent non-owners from transferring', async function () {
await shouldFail.reverting(this.ownable.transferOwnership(anyone, { from: anyone }));
});

View File

@ -29,7 +29,7 @@ contract('Secondary', function ([_, primary, newPrimary, anyone]) {
(await this.secondary.primary()).should.equal(newPrimary);
});
it('reverts when transfering to the null address', async function () {
it('reverts when transferring to the null address', async function () {
await shouldFail.reverting(this.secondary.transferPrimary(ZERO_ADDRESS, { from: primary }));
});

View File

@ -84,19 +84,19 @@ contract('PaymentSplitter', function ([_, owner, payee1, payee2, payee3, nonpaye
const initAmount1 = await balance.current(payee1);
const { logs: logs1 } = await this.contract.release(payee1, { gasPrice: 0 });
const profit1 = (await balance.current(payee1)).sub(initAmount1);
profit1.should.be.bignumber.equal(ether('0.20', 'ether'));
profit1.should.be.bignumber.equal(ether('0.20'));
expectEvent.inLogs(logs1, 'PaymentReleased', { to: payee1, amount: profit1 });
const initAmount2 = await balance.current(payee2);
const { logs: logs2 } = await this.contract.release(payee2, { gasPrice: 0 });
const profit2 = (await balance.current(payee2)).sub(initAmount2);
profit2.should.be.bignumber.equal(ether('0.10', 'ether'));
profit2.should.be.bignumber.equal(ether('0.10'));
expectEvent.inLogs(logs2, 'PaymentReleased', { to: payee2, amount: profit2 });
const initAmount3 = await balance.current(payee3);
const { logs: logs3 } = await this.contract.release(payee3, { gasPrice: 0 });
const profit3 = (await balance.current(payee3)).sub(initAmount3);
profit3.should.be.bignumber.equal(ether('0.70', 'ether'));
profit3.should.be.bignumber.equal(ether('0.70'));
expectEvent.inLogs(logs3, 'PaymentReleased', { to: payee3, amount: profit3 });
// end balance should be zero

View File

@ -73,89 +73,6 @@ contract('ERC20', function ([_, initialHolder, recipient, anotherAccount]) {
});
});
describe('approve', function () {
describe('when the spender is not the zero address', function () {
const spender = recipient;
describe('when the sender has enough balance', function () {
const amount = initialSupply;
it('emits an approval event', async function () {
const { logs } = await this.token.approve(spender, amount, { from: initialHolder });
expectEvent.inLogs(logs, 'Approval', {
owner: initialHolder,
spender: spender,
value: amount,
});
});
describe('when there was no approved amount before', function () {
it('approves the requested amount', async function () {
await this.token.approve(spender, amount, { from: initialHolder });
(await this.token.allowance(initialHolder, spender)).should.be.bignumber.equal(amount);
});
});
describe('when the spender had an approved amount', function () {
beforeEach(async function () {
await this.token.approve(spender, new BN(1), { from: initialHolder });
});
it('approves the requested amount and replaces the previous one', async function () {
await this.token.approve(spender, amount, { from: initialHolder });
(await this.token.allowance(initialHolder, spender)).should.be.bignumber.equal(amount);
});
});
});
describe('when the sender does not have enough balance', function () {
const amount = initialSupply.addn(1);
it('emits an approval event', async function () {
const { logs } = await this.token.approve(spender, amount, { from: initialHolder });
expectEvent.inLogs(logs, 'Approval', {
owner: initialHolder,
spender: spender,
value: amount,
});
});
describe('when there was no approved amount before', function () {
it('approves the requested amount', async function () {
await this.token.approve(spender, amount, { from: initialHolder });
(await this.token.allowance(initialHolder, spender)).should.be.bignumber.equal(amount);
});
});
describe('when the spender had an approved amount', function () {
beforeEach(async function () {
await this.token.approve(spender, new BN(1), { from: initialHolder });
});
it('approves the requested amount and replaces the previous one', async function () {
await this.token.approve(spender, amount, { from: initialHolder });
(await this.token.allowance(initialHolder, spender)).should.be.bignumber.equal(amount);
});
});
});
});
describe('when the spender is the zero address', function () {
const amount = initialSupply;
const spender = ZERO_ADDRESS;
it('reverts', async function () {
await shouldFail.reverting(this.token.approve(spender, amount, { from: initialHolder }));
});
});
});
describe('transfer from', function () {
const spender = recipient;
@ -546,4 +463,100 @@ contract('ERC20', function ([_, initialHolder, recipient, anotherAccount]) {
describeBurnFrom('for less amount than allowance', allowance.subn(1));
});
});
describe('approve', function () {
testApprove(initialHolder, recipient, initialSupply, function (owner, spender, amount) {
return this.token.approve(spender, amount, { from: owner });
});
});
describe('_approve', function () {
testApprove(initialHolder, recipient, initialSupply, function (owner, spender, amount) {
return this.token.approveInternal(owner, spender, amount);
});
describe('when the owner is the zero address', function () {
it('reverts', async function () {
await shouldFail.reverting(this.token.approveInternal(ZERO_ADDRESS, recipient, initialSupply));
});
});
});
function testApprove (owner, spender, supply, approve) {
describe('when the spender is not the zero address', function () {
describe('when the sender has enough balance', function () {
const amount = supply;
it('emits an approval event', async function () {
const { logs } = await approve.call(this, owner, spender, amount);
expectEvent.inLogs(logs, 'Approval', {
owner: owner,
spender: spender,
value: amount,
});
});
describe('when there was no approved amount before', function () {
it('approves the requested amount', async function () {
await approve.call(this, owner, spender, amount);
(await this.token.allowance(owner, spender)).should.be.bignumber.equal(amount);
});
});
describe('when the spender had an approved amount', function () {
beforeEach(async function () {
await approve.call(this, owner, spender, new BN(1));
});
it('approves the requested amount and replaces the previous one', async function () {
await approve.call(this, owner, spender, amount);
(await this.token.allowance(owner, spender)).should.be.bignumber.equal(amount);
});
});
});
describe('when the sender does not have enough balance', function () {
const amount = supply.addn(1);
it('emits an approval event', async function () {
const { logs } = await approve.call(this, owner, spender, amount);
expectEvent.inLogs(logs, 'Approval', {
owner: owner,
spender: spender,
value: amount,
});
});
describe('when there was no approved amount before', function () {
it('approves the requested amount', async function () {
await approve.call(this, owner, spender, amount);
(await this.token.allowance(owner, spender)).should.be.bignumber.equal(amount);
});
});
describe('when the spender had an approved amount', function () {
beforeEach(async function () {
await approve.call(this, owner, spender, new BN(1));
});
it('approves the requested amount and replaces the previous one', async function () {
await approve.call(this, owner, spender, amount);
(await this.token.allowance(owner, spender)).should.be.bignumber.equal(amount);
});
});
});
});
describe('when the spender is the zero address', function () {
it('reverts', async function () {
await shouldFail.reverting(approve.call(this, owner, ZERO_ADDRESS, supply));
});
});
}
});

View File

@ -1,91 +1,122 @@
const { shouldFail } = require('openzeppelin-test-helpers');
const SafeERC20Helper = artifacts.require('SafeERC20Helper');
const ERC20ReturnFalseMock = artifacts.require('ERC20ReturnFalseMock');
const ERC20ReturnTrueMock = artifacts.require('ERC20ReturnTrueMock');
const ERC20NoReturnMock = artifacts.require('ERC20NoReturnMock');
const SafeERC20Wrapper = artifacts.require('SafeERC20Wrapper');
contract('SafeERC20', function () {
beforeEach(async function () {
this.helper = await SafeERC20Helper.new();
contract('SafeERC20', function ([_, hasNoCode]) {
describe('with address that has no contract code', function () {
beforeEach(async function () {
this.wrapper = await SafeERC20Wrapper.new(hasNoCode);
});
shouldRevertOnAllCalls();
});
describe('with token that returns false on all calls', function () {
it('reverts on transfer', async function () {
await shouldFail.reverting(this.helper.doFailingTransfer());
beforeEach(async function () {
this.wrapper = await SafeERC20Wrapper.new((await ERC20ReturnFalseMock.new()).address);
});
it('reverts on transferFrom', async function () {
await shouldFail.reverting(this.helper.doFailingTransferFrom());
});
it('reverts on approve', async function () {
await shouldFail.reverting(this.helper.doFailingApprove());
});
it('reverts on increaseAllowance', async function () {
await shouldFail.reverting(this.helper.doFailingIncreaseAllowance());
});
it('reverts on decreaseAllowance', async function () {
await shouldFail.reverting(this.helper.doFailingDecreaseAllowance());
});
shouldRevertOnAllCalls();
});
describe('with token that returns true on all calls', function () {
it('doesn\'t revert on transfer', async function () {
await this.helper.doSucceedingTransfer();
beforeEach(async function () {
this.wrapper = await SafeERC20Wrapper.new((await ERC20ReturnTrueMock.new()).address);
});
it('doesn\'t revert on transferFrom', async function () {
await this.helper.doSucceedingTransferFrom();
shouldOnlyRevertOnErrors();
});
describe('with token that returns no boolean values', function () {
beforeEach(async function () {
this.wrapper = await SafeERC20Wrapper.new((await ERC20NoReturnMock.new()).address);
});
describe('approvals', function () {
context('with zero allowance', function () {
beforeEach(async function () {
await this.helper.setAllowance(0);
});
shouldOnlyRevertOnErrors();
});
});
it('doesn\'t revert when approving a non-zero allowance', async function () {
await this.helper.doSucceedingApprove(100);
});
function shouldRevertOnAllCalls () {
it('reverts on transfer', async function () {
await shouldFail.reverting(this.wrapper.transfer());
});
it('doesn\'t revert when approving a zero allowance', async function () {
await this.helper.doSucceedingApprove(0);
});
it('reverts on transferFrom', async function () {
await shouldFail.reverting(this.wrapper.transferFrom());
});
it('doesn\'t revert when increasing the allowance', async function () {
await this.helper.doSucceedingIncreaseAllowance(10);
});
it('reverts on approve', async function () {
await shouldFail.reverting(this.wrapper.approve(0));
});
it('reverts when decreasing the allowance', async function () {
await shouldFail.reverting(this.helper.doSucceedingDecreaseAllowance(10));
});
it('reverts on increaseAllowance', async function () {
await shouldFail.reverting(this.wrapper.increaseAllowance(0));
});
it('reverts on decreaseAllowance', async function () {
await shouldFail.reverting(this.wrapper.decreaseAllowance(0));
});
}
function shouldOnlyRevertOnErrors () {
it('doesn\'t revert on transfer', async function () {
await this.wrapper.transfer();
});
it('doesn\'t revert on transferFrom', async function () {
await this.wrapper.transferFrom();
});
describe('approvals', function () {
context('with zero allowance', function () {
beforeEach(async function () {
await this.wrapper.setAllowance(0);
});
context('with non-zero allowance', function () {
beforeEach(async function () {
await this.helper.setAllowance(100);
});
it('doesn\'t revert when approving a non-zero allowance', async function () {
await this.wrapper.approve(100);
});
it('reverts when approving a non-zero allowance', async function () {
await shouldFail.reverting(this.helper.doSucceedingApprove(20));
});
it('doesn\'t revert when approving a zero allowance', async function () {
await this.wrapper.approve(0);
});
it('doesn\'t revert when approving a zero allowance', async function () {
await this.helper.doSucceedingApprove(0);
});
it('doesn\'t revert when increasing the allowance', async function () {
await this.wrapper.increaseAllowance(10);
});
it('doesn\'t revert when increasing the allowance', async function () {
await this.helper.doSucceedingIncreaseAllowance(10);
});
it('reverts when decreasing the allowance', async function () {
await shouldFail.reverting(this.wrapper.decreaseAllowance(10));
});
});
it('doesn\'t revert when decreasing the allowance to a positive value', async function () {
await this.helper.doSucceedingDecreaseAllowance(50);
});
context('with non-zero allowance', function () {
beforeEach(async function () {
await this.wrapper.setAllowance(100);
});
it('reverts when decreasing the allowance to a negative value', async function () {
await shouldFail.reverting(this.helper.doSucceedingDecreaseAllowance(200));
});
it('reverts when approving a non-zero allowance', async function () {
await shouldFail.reverting(this.wrapper.approve(20));
});
it('doesn\'t revert when approving a zero allowance', async function () {
await this.wrapper.approve(0);
});
it('doesn\'t revert when increasing the allowance', async function () {
await this.wrapper.increaseAllowance(10);
});
it('doesn\'t revert when decreasing the allowance to a positive value', async function () {
await this.wrapper.decreaseAllowance(50);
});
it('reverts when decreasing the allowance to a negative value', async function () {
await shouldFail.reverting(this.wrapper.decreaseAllowance(200));
});
});
});
});
}

View File

@ -55,7 +55,7 @@ function shouldBehaveLikeERC721PausedToken (owner, [recipient, operator]) {
});
describe('exists', function () {
it('should return token existance', async function () {
it('should return token existence', async function () {
(await this.token.exists(firstTokenId)).should.equal(true);
});
});