From 4a4d2e70799b6b85bf8c09eaba61f2cc5dc487b6 Mon Sep 17 00:00:00 2001 From: Alex Towle Date: Fri, 26 Jul 2019 13:11:29 -0700 Subject: [PATCH] Added unit tests for SafeMath --- contracts/utils/compiler.json | 3 +- .../utils/contracts/test/TestSafeMath.sol | 66 ++++++++ contracts/utils/package.json | 2 +- contracts/utils/src/artifacts.ts | 4 + contracts/utils/src/wrappers.ts | 2 + contracts/utils/test/safe_math.ts | 146 ++++++++++++++++++ contracts/utils/tsconfig.json | 4 +- packages/utils/src/safe_math_revert_errors.ts | 2 +- 8 files changed, 225 insertions(+), 4 deletions(-) create mode 100644 contracts/utils/contracts/test/TestSafeMath.sol create mode 100644 contracts/utils/test/safe_math.ts diff --git a/contracts/utils/compiler.json b/contracts/utils/compiler.json index 600011d083..e0b173967b 100644 --- a/contracts/utils/compiler.json +++ b/contracts/utils/compiler.json @@ -35,6 +35,7 @@ "src/interfaces/IOwnable.sol", "test/TestConstants.sol", "test/TestLibAddressArray.sol", - "test/TestLibBytes.sol" + "test/TestLibBytes.sol", + "test/TestSafeMath.sol" ] } diff --git a/contracts/utils/contracts/test/TestSafeMath.sol b/contracts/utils/contracts/test/TestSafeMath.sol new file mode 100644 index 0000000000..5b9d7f7544 --- /dev/null +++ b/contracts/utils/contracts/test/TestSafeMath.sol @@ -0,0 +1,66 @@ +/* + + Copyright 2019 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.5.9; + +import "../src/SafeMath.sol"; + + +contract TestSafeMath is + SafeMath +{ + function externalSafeMul(uint256 a, uint256 b) + external + pure + returns (uint256) + { + return _safeMul(a, b); + } + + function externalSafeSub(uint256 a, uint256 b) + external + pure + returns (uint256) + { + return _safeSub(a, b); + } + + function externalSafeAdd(uint256 a, uint256 b) + external + pure + returns (uint256) + { + return _safeAdd(a, b); + } + + function externalMaxUint256(uint256 a, uint256 b) + external + pure + returns (uint256) + { + return _max256(a, b); + } + + function externalMinUint256(uint256 a, uint256 b) + external + pure + returns (uint256) + { + return _min256(a, b); + } +} diff --git a/contracts/utils/package.json b/contracts/utils/package.json index 36c46f76e0..73e2f9308e 100644 --- a/contracts/utils/package.json +++ b/contracts/utils/package.json @@ -34,7 +34,7 @@ "lint-contracts": "solhint -c ../.solhint.json contracts/**/**/**/**/*.sol" }, "config": { - "abis": "./generated-artifacts/@(IOwnable|LibAddress|LibBytes|LibEIP712|Ownable|ReentrancyGuard|RichErrors|SafeMath|TestConstants|TestLibAddressArray|TestLibBytes).json", + "abis": "./generated-artifacts/@(IOwnable|LibAddress|LibBytes|LibEIP1271|LibEIP712|Ownable|ReentrancyGuard|RichErrors|SafeMath|TestConstants|TestLibAddressArray|TestLibBytes|TestSafeMath).json", "abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually." }, "repository": { diff --git a/contracts/utils/src/artifacts.ts b/contracts/utils/src/artifacts.ts index 2b82dbd63f..4028caad50 100644 --- a/contracts/utils/src/artifacts.ts +++ b/contracts/utils/src/artifacts.ts @@ -8,6 +8,7 @@ import { ContractArtifact } from 'ethereum-types'; import * as IOwnable from '../generated-artifacts/IOwnable.json'; import * as LibAddress from '../generated-artifacts/LibAddress.json'; import * as LibBytes from '../generated-artifacts/LibBytes.json'; +import * as LibEIP1271 from '../generated-artifacts/LibEIP1271.json'; import * as LibEIP712 from '../generated-artifacts/LibEIP712.json'; import * as Ownable from '../generated-artifacts/Ownable.json'; import * as ReentrancyGuard from '../generated-artifacts/ReentrancyGuard.json'; @@ -16,9 +17,11 @@ import * as SafeMath from '../generated-artifacts/SafeMath.json'; import * as TestConstants from '../generated-artifacts/TestConstants.json'; import * as TestLibAddressArray from '../generated-artifacts/TestLibAddressArray.json'; import * as TestLibBytes from '../generated-artifacts/TestLibBytes.json'; +import * as TestSafeMath from '../generated-artifacts/TestSafeMath.json'; export const artifacts = { LibAddress: LibAddress as ContractArtifact, LibBytes: LibBytes as ContractArtifact, + LibEIP1271: LibEIP1271 as ContractArtifact, LibEIP712: LibEIP712 as ContractArtifact, Ownable: Ownable as ContractArtifact, ReentrancyGuard: ReentrancyGuard as ContractArtifact, @@ -28,4 +31,5 @@ export const artifacts = { TestConstants: TestConstants as ContractArtifact, TestLibAddressArray: TestLibAddressArray as ContractArtifact, TestLibBytes: TestLibBytes as ContractArtifact, + TestSafeMath: TestSafeMath as ContractArtifact, }; diff --git a/contracts/utils/src/wrappers.ts b/contracts/utils/src/wrappers.ts index 94aab4f426..2adf5244da 100644 --- a/contracts/utils/src/wrappers.ts +++ b/contracts/utils/src/wrappers.ts @@ -6,6 +6,7 @@ export * from '../generated-wrappers/i_ownable'; export * from '../generated-wrappers/lib_address'; export * from '../generated-wrappers/lib_bytes'; +export * from '../generated-wrappers/lib_e_i_p1271'; export * from '../generated-wrappers/lib_e_i_p712'; export * from '../generated-wrappers/ownable'; export * from '../generated-wrappers/reentrancy_guard'; @@ -14,3 +15,4 @@ export * from '../generated-wrappers/safe_math'; export * from '../generated-wrappers/test_constants'; export * from '../generated-wrappers/test_lib_address_array'; export * from '../generated-wrappers/test_lib_bytes'; +export * from '../generated-wrappers/test_safe_math'; diff --git a/contracts/utils/test/safe_math.ts b/contracts/utils/test/safe_math.ts new file mode 100644 index 0000000000..ccbf0117e3 --- /dev/null +++ b/contracts/utils/test/safe_math.ts @@ -0,0 +1,146 @@ +import { chaiSetup, constants, provider, txDefaults, web3Wrapper } from '@0x/contracts-test-utils'; +import { BlockchainLifecycle } from '@0x/dev-utils'; +import { BigNumber, SafeMathRevertErrors } from '@0x/utils'; +import * as chai from 'chai'; +import * as _ from 'lodash'; + +import { artifacts, TestSafeMathContract } from '../src'; + +chaiSetup.configure(); +const expect = chai.expect; +const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); + +function toBN(a: number | string): BigNumber { + return new BigNumber(a); +} + +describe('SafeMath', () => { + let safeMath: TestSafeMathContract; + + before(async () => { + await blockchainLifecycle.startAsync(); + // Deploy SafeMath + safeMath = await TestSafeMathContract.deployFrom0xArtifactAsync( + artifacts.TestSafeMath, + provider, + txDefaults, + ); + }); + + after(async () => { + await blockchainLifecycle.revertAsync(); + }); + + describe('_safeMul', () => { + it('should return zero if first argument is zero', async () => { + const result = await safeMath.externalSafeMul.callAsync(constants.ZERO_AMOUNT, toBN(1)); + expect(result).bignumber.to.be.eq(constants.ZERO_AMOUNT); + }); + + it('should return zero if second argument is zero', async () => { + const result = await safeMath.externalSafeMul.callAsync(toBN(1), constants.ZERO_AMOUNT); + expect(result).bignumber.to.be.eq(constants.ZERO_AMOUNT); + }); + + it('should revert if the multiplication overflows', async () => { + const a = toBN('0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'); // The largest uint256 number + const b = toBN(2); + const expectedError = new SafeMathRevertErrors.SafeMathError( + SafeMathRevertErrors.SafeMathErrorCodes.Uint256MultiplicationOverflow, + a, + b, + ); + return expect(safeMath.externalSafeMul.callAsync(a, b)).to.revertWith(expectedError); + }); + + it('should calculate correct value for values that don\'t overflow', async () => { + const result = await safeMath.externalSafeMul.callAsync(toBN(15), toBN(13)); + expect(result).bignumber.to.be.eq(toBN(195)); + }); + }); + + describe('_safeSub', () => { + it('should throw if the subtraction underflows', async () => { + const a = toBN(0); + const b = toBN(1); + const expectedError = new SafeMathRevertErrors.SafeMathError( + SafeMathRevertErrors.SafeMathErrorCodes.Uint256SubtractionUnderflow, + a, + b, + ); + return expect(safeMath.externalSafeSub.callAsync(a, b)).to.revertWith(expectedError); + }); + + it('should calculate correct value for values that are equal', async () => { + const result = await safeMath.externalSafeMul.callAsync(constants.ZERO_AMOUNT, constants.ZERO_AMOUNT); + expect(result).bignumber.to.be.eq(constants.ZERO_AMOUNT); + }); + + it('should calculate correct value for values that are not equal', async () => { + const result = await safeMath.externalSafeSub.callAsync(toBN(15), toBN(13)); + expect(result).bignumber.to.be.eq(toBN(2)); + }); + }); + + describe('_safeAdd', () => { + it('should throw if the addition overflows', async () => { + const a = toBN('0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'); // The largest uint256 number + const b = toBN(1); + const expectedError = new SafeMathRevertErrors.SafeMathError( + SafeMathRevertErrors.SafeMathErrorCodes.Uint256AdditionOverflow, + a, + b, + ); + return expect(safeMath.externalSafeAdd.callAsync(a, b)).to.revertWith(expectedError); + }); + + it('should calculate correct value if addition does not overflow', async () => { + const result = await safeMath.externalSafeAdd.callAsync(toBN(15), toBN(13)); + expect(result).bignumber.to.be.eq(toBN(28)); + }); + + it('should calculate correct value if first argument is zero', async () => { + const result = await safeMath.externalSafeAdd.callAsync(constants.ZERO_AMOUNT, toBN(13)); + expect(result).bignumber.to.be.eq(toBN(13)); + }); + + it('should calculate correct value if second argument is zero', async () => { + const result = await safeMath.externalSafeAdd.callAsync(toBN(13), constants.ZERO_AMOUNT); + expect(result).bignumber.to.be.eq(toBN(13)); + }); + }); + + describe('_maxUint256', () => { + it('should return first argument if it is greater than the second', async () => { + const result = await safeMath.externalMaxUint256.callAsync(toBN(13), constants.ZERO_AMOUNT); + expect(result).bignumber.to.be.eq(toBN(13)); + }); + + it('should return first argument if it is equal the second', async () => { + const result = await safeMath.externalMaxUint256.callAsync(constants.ZERO_AMOUNT, constants.ZERO_AMOUNT); + expect(result).bignumber.to.be.eq(constants.ZERO_AMOUNT); + }); + + it('should return second argument if it is greater than the first', async () => { + const result = await safeMath.externalMaxUint256.callAsync(constants.ZERO_AMOUNT, toBN(13)); + expect(result).bignumber.to.be.eq(toBN(13)); + }); + }); + + describe('_minUint256', () => { + it('should return first argument if it is less than the second', async () => { + const result = await safeMath.externalMaxUint256.callAsync(constants.ZERO_AMOUNT, toBN(13)); + expect(result).bignumber.to.be.eq(toBN(13)); + }); + + it('should return first argument if it is equal the second', async () => { + const result = await safeMath.externalMaxUint256.callAsync(constants.ZERO_AMOUNT, constants.ZERO_AMOUNT); + expect(result).bignumber.to.be.eq(constants.ZERO_AMOUNT); + }); + + it('should return second argument if it is greater than the first', async () => { + const result = await safeMath.externalMaxUint256.callAsync(toBN(13), constants.ZERO_AMOUNT); + expect(result).bignumber.to.be.eq(toBN(13)); + }); + }); +}); diff --git a/contracts/utils/tsconfig.json b/contracts/utils/tsconfig.json index 962e961380..c89e534ee4 100644 --- a/contracts/utils/tsconfig.json +++ b/contracts/utils/tsconfig.json @@ -6,6 +6,7 @@ "generated-artifacts/IOwnable.json", "generated-artifacts/LibAddress.json", "generated-artifacts/LibBytes.json", + "generated-artifacts/LibEIP1271.json", "generated-artifacts/LibEIP712.json", "generated-artifacts/Ownable.json", "generated-artifacts/ReentrancyGuard.json", @@ -13,7 +14,8 @@ "generated-artifacts/SafeMath.json", "generated-artifacts/TestConstants.json", "generated-artifacts/TestLibAddressArray.json", - "generated-artifacts/TestLibBytes.json" + "generated-artifacts/TestLibBytes.json", + "generated-artifacts/TestSafeMath.json" ], "exclude": ["./deploy/solc/solc_bin"] } diff --git a/packages/utils/src/safe_math_revert_errors.ts b/packages/utils/src/safe_math_revert_errors.ts index e91f90ef40..897123f8da 100644 --- a/packages/utils/src/safe_math_revert_errors.ts +++ b/packages/utils/src/safe_math_revert_errors.ts @@ -10,7 +10,7 @@ export enum SafeMathErrorCodes { } export class SafeMathError extends RevertError { - constructor(error?: SafeMathErrorCodes, a?: BigNumber | number | string, b?: BigNumber | number | string) { + constructor(error?: SafeMathErrorCodes, a?: BigNumber, b?: BigNumber) { super('SafeMathError', 'SafeMathError(uint8 error, uint256 a, uint256 b)', { error, a,