Commit 83a449c4 authored by Maurelian's avatar Maurelian Committed by Kelvin Fichter

feat(core-utils): Add absolute deviation options to expectApprox

parent cb7e3ae7
---
'@eth-optimism/core-utils': minor
---
Change the expectApprox interface to allow setting an absoluteexpected deviation range
...@@ -62,7 +62,7 @@ describe('Native ETH Integration Tests', async () => { ...@@ -62,7 +62,7 @@ describe('Native ETH Integration Tests', async () => {
'0xFFFF' '0xFFFF'
) )
// Expect gas to be less than or equal to the target plus 1% // Expect gas to be less than or equal to the target plus 1%
expectApprox(gas, 6700060, { upperPercentDeviation: 1 }) expectApprox(gas, 6700060, { absoluteUpperDeviation: 1000 })
}) })
}) })
...@@ -213,17 +213,17 @@ describe('Native ETH Integration Tests', async () => { ...@@ -213,17 +213,17 @@ describe('Native ETH Integration Tests', async () => {
expectApprox( expectApprox(
postBalances.l1BridgeBalance, postBalances.l1BridgeBalance,
preBalances.l1BridgeBalance.sub(withdrawAmount), preBalances.l1BridgeBalance.sub(withdrawAmount),
{ upperPercentDeviation: 1 } { percentUpperDeviation: 1 }
) )
expectApprox( expectApprox(
postBalances.l2UserBalance, postBalances.l2UserBalance,
preBalances.l2UserBalance.sub(withdrawAmount.add(fee)), preBalances.l2UserBalance.sub(withdrawAmount.add(fee)),
{ upperPercentDeviation: 1 } { percentUpperDeviation: 1 }
) )
expectApprox( expectApprox(
postBalances.l1UserBalance, postBalances.l1UserBalance,
preBalances.l1UserBalance.add(withdrawAmount), preBalances.l1UserBalance.add(withdrawAmount),
{ upperPercentDeviation: 1 } { percentUpperDeviation: 1 }
) )
}) })
......
...@@ -380,7 +380,7 @@ describe('Basic RPC tests', () => { ...@@ -380,7 +380,7 @@ describe('Basic RPC tests', () => {
value: 0, value: 0,
}) })
// Expect gas to be less than or equal to the target plus 1% // Expect gas to be less than or equal to the target plus 1%
expectApprox(estimate, 21000, { upperPercentDeviation: 1 }) expectApprox(estimate, 21000, { percentUpperDeviation: 1 })
}) })
it('should fail for a reverting call transaction', async () => { it('should fail for a reverting call transaction', async () => {
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
import { ethers } from 'hardhat' import { ethers } from 'hardhat'
import { Signer, ContractFactory, Contract, constants } from 'ethers' import { Signer, ContractFactory, Contract, constants } from 'ethers'
import { smoddit } from '@eth-optimism/smock' import { smoddit } from '@eth-optimism/smock'
import { expectApprox } from '@eth-optimism/core-utils'
/* Internal Imports */ /* Internal Imports */
import { import {
...@@ -155,6 +156,12 @@ describe('[GAS BENCHMARK] Depositing via the standard bridge', () => { ...@@ -155,6 +156,12 @@ describe('[GAS BENCHMARK] Depositing via the standard bridge', () => {
(((regenesis040Cost - gasUsed) / regenesis040Cost) * 100).toFixed(2) + (((regenesis040Cost - gasUsed) / regenesis040Cost) * 100).toFixed(2) +
'%' '%'
) )
expectApprox(gasUsed, 154_247, {
absoluteUpperDeviation: 500,
// Assert a lower bound of 1% reduction on gas cost. If your tests are breaking because your
// contracts are too efficient, consider updating the target value!
percentLowerDeviation: 1,
})
// Sanity check that the message was enqueued. // Sanity check that the message was enqueued.
expect(await CanonicalTransactionChain.getQueueLength()).to.equal(2) expect(await CanonicalTransactionChain.getQueueLength()).to.equal(2)
}) })
...@@ -186,7 +193,12 @@ describe('[GAS BENCHMARK] Depositing via the standard bridge', () => { ...@@ -186,7 +193,12 @@ describe('[GAS BENCHMARK] Depositing via the standard bridge', () => {
(((regenesis040Cost - gasUsed) / regenesis040Cost) * 100).toFixed(2) + (((regenesis040Cost - gasUsed) / regenesis040Cost) * 100).toFixed(2) +
'%' '%'
) )
expectApprox(gasUsed, 202_088, {
absoluteUpperDeviation: 500,
// Assert a lower bound of 1% reduction on gas cost. If your tests are breaking because your
// contracts are too efficient, consider updating the target value!
percentLowerDeviation: 1,
})
// Sanity check that the message was enqueued. // Sanity check that the message was enqueued.
expect(await CanonicalTransactionChain.getQueueLength()).to.equal(3) expect(await CanonicalTransactionChain.getQueueLength()).to.equal(3)
}) })
......
...@@ -171,10 +171,10 @@ describe('[GAS BENCHMARK] CanonicalTransactionChain', () => { ...@@ -171,10 +171,10 @@ describe('[GAS BENCHMARK] CanonicalTransactionChain', () => {
(gasUsed - fixedCalldataCost) / numTxs (gasUsed - fixedCalldataCost) / numTxs
) )
expectApprox(gasUsed, 1_422_181, { expectApprox(gasUsed, 1_422_181, {
upperPercentDeviation: 0, absoluteUpperDeviation: 1000,
// Assert a lower bound of 1% reduction on gas cost. If your tests are breaking because your // Assert a lower bound of 1% reduction on gas cost. If your tests are breaking because your
// contracts are too efficient, consider updating the target value! // contracts are too efficient, consider updating the target value!
lowerPercentDeviation: 1, percentLowerDeviation: 1,
}) })
}).timeout(10_000_000) }).timeout(10_000_000)
...@@ -219,10 +219,10 @@ describe('[GAS BENCHMARK] CanonicalTransactionChain', () => { ...@@ -219,10 +219,10 @@ describe('[GAS BENCHMARK] CanonicalTransactionChain', () => {
(gasUsed - fixedCalldataCost) / numTxs (gasUsed - fixedCalldataCost) / numTxs
) )
expectApprox(gasUsed, 1_632_687, { expectApprox(gasUsed, 1_632_687, {
upperPercentDeviation: 0, absoluteUpperDeviation: 1000,
// Assert a lower bound of 1% reduction on gas cost. If your tests are breaking because your // Assert a lower bound of 1% reduction on gas cost. If your tests are breaking because your
// contracts are too efficient, consider updating the target value! // contracts are too efficient, consider updating the target value!
lowerPercentDeviation: 1, percentLowerDeviation: 1,
}) })
}).timeout(10_000_000) }).timeout(10_000_000)
...@@ -276,7 +276,12 @@ describe('[GAS BENCHMARK] CanonicalTransactionChain', () => { ...@@ -276,7 +276,12 @@ describe('[GAS BENCHMARK] CanonicalTransactionChain', () => {
'Non-calldata overhead gas cost per transaction:', 'Non-calldata overhead gas cost per transaction:',
(gasUsed - fixedCalldataCost) / numTxs (gasUsed - fixedCalldataCost) / numTxs
) )
expectApprox(gasUsed, 1_293_611, { upperPercentDeviation: 0 }) expectApprox(gasUsed, 891_158, {
absoluteUpperDeviation: 1000,
// Assert a lower bound of 1% reduction on gas cost. If your tests are breaking because your
// contracts are too efficient, consider updating the target value!
percentLowerDeviation: 1,
})
}).timeout(10_000_000) }).timeout(10_000_000)
}) })
...@@ -304,11 +309,11 @@ describe('[GAS BENCHMARK] CanonicalTransactionChain', () => { ...@@ -304,11 +309,11 @@ describe('[GAS BENCHMARK] CanonicalTransactionChain', () => {
console.log('Benchmark complete.') console.log('Benchmark complete.')
console.log('Gas used:', gasUsed) console.log('Gas used:', gasUsed)
expectApprox(gasUsed, 219_896, { expectApprox(gasUsed, 218_203, {
upperPercentDeviation: 0, absoluteUpperDeviation: 500,
// Assert a lower bound of 1% reduction on gas cost. If your tests are breaking because your // Assert a lower bound of 1% reduction on gas cost. If your tests are breaking because your
// contracts are too efficient, consider updating the target value! // contracts are too efficient, consider updating the target value!
lowerPercentDeviation: 1, percentLowerDeviation: 1,
}) })
}) })
...@@ -326,11 +331,11 @@ describe('[GAS BENCHMARK] CanonicalTransactionChain', () => { ...@@ -326,11 +331,11 @@ describe('[GAS BENCHMARK] CanonicalTransactionChain', () => {
console.log('Benchmark complete.') console.log('Benchmark complete.')
console.log('Gas used:', gasUsed) console.log('Gas used:', gasUsed)
expectApprox(gasUsed, 158_709, { expectApprox(gasUsed, 157_822, {
upperPercentDeviation: 0, absoluteUpperDeviation: 500,
// Assert a lower bound of 1% reduction on gas cost. If your tests are breaking because your // Assert a lower bound of 1% reduction on gas cost. If your tests are breaking because your
// contracts are too efficient, consider updating the target value! // contracts are too efficient, consider updating the target value!
lowerPercentDeviation: 1, percentLowerDeviation: 1,
}) })
}) })
}) })
......
import { expect } from 'chai' import { expect } from 'chai'
import { BigNumber } from 'ethers' import { BigNumber } from 'ethers'
interface percentDeviationRange { interface deviationRanges {
upperPercentDeviation: number percentUpperDeviation?: number
lowerPercentDeviation?: number percentLowerDeviation?: number
absoluteUpperDeviation?: number
absoluteLowerDeviation?: number
} }
/** /**
...@@ -12,30 +14,74 @@ interface percentDeviationRange { ...@@ -12,30 +14,74 @@ interface percentDeviationRange {
export const expectApprox = ( export const expectApprox = (
actual: BigNumber | number, actual: BigNumber | number,
target: BigNumber | number, target: BigNumber | number,
{ upperPercentDeviation, lowerPercentDeviation = 100 }: percentDeviationRange {
percentUpperDeviation,
percentLowerDeviation,
absoluteUpperDeviation,
absoluteLowerDeviation,
}: deviationRanges
): void => { ): void => {
actual = BigNumber.from(actual) actual = BigNumber.from(actual)
target = BigNumber.from(target) target = BigNumber.from(target)
const validDeviations = // Ensure at least one deviation parameter is defined
upperPercentDeviation >= 0 && const nonNullDeviations =
upperPercentDeviation <= 100 && percentUpperDeviation ||
lowerPercentDeviation >= 0 && percentLowerDeviation ||
lowerPercentDeviation <= 100 absoluteUpperDeviation ||
if (!validDeviations) { absoluteLowerDeviation
if (!nonNullDeviations) {
throw new Error( throw new Error(
'Upper and lower deviation percentage arguments should be between 0 and 100' 'Must define at least one parameter to limit the deviation of the actual value.'
) )
} }
const upper = target.mul(100 + upperPercentDeviation).div(100)
const lower = target.mul(100 - lowerPercentDeviation).div(100)
expect( // Upper bound calculation.
actual.lte(upper), let upper: BigNumber
`Actual value (${actual}) is more than ${upperPercentDeviation}% greater than target (${target})` // Set the two possible upper bounds if and only if they are defined.
).to.be.true const upperPcnt: BigNumber = !percentUpperDeviation
expect( ? null
actual.gte(lower), : target.mul(100 + percentUpperDeviation).div(100)
`Actual value (${actual}) is more than ${lowerPercentDeviation}% less than target (${target})` const upperAbs: BigNumber = !absoluteUpperDeviation
).to.be.true ? null
: target.add(absoluteUpperDeviation)
if (upperPcnt && upperAbs) {
// If both are set, take the lesser of the two upper bounds.
upper = upperPcnt.lte(upperAbs) ? upperPcnt : upperAbs
} else {
// Else take whichever is not undefined or set to null.
upper = upperPcnt || upperAbs
}
// Lower bound calculation.
let lower: BigNumber
// Set the two possible lower bounds if and only if they are defined.
const lowerPcnt: BigNumber = !percentLowerDeviation
? null
: target.mul(100 - percentLowerDeviation).div(100)
const lowerAbs: BigNumber = !absoluteLowerDeviation
? null
: target.sub(absoluteLowerDeviation)
if (lowerPcnt && lowerAbs) {
// If both are set, take the greater of the two lower bounds.
lower = lowerPcnt.gte(lowerAbs) ? lowerPcnt : lowerAbs
} else {
// Else take whichever is not undefined or set to null.
lower = lowerPcnt || lowerAbs
}
// Apply the assertions if they are non-null.
if (upper) {
expect(
actual.lte(upper),
`Actual value (${actual}) is greater than the calculated upper bound of (${upper})`
).to.be.true
}
if (lower) {
expect(
actual.gte(lower),
`Actual value (${actual}) is less than the calculated lower bound of (${lower})`
).to.be.true
}
} }
...@@ -2,30 +2,150 @@ import { expect } from '../setup' ...@@ -2,30 +2,150 @@ import { expect } from '../setup'
/* Imports: Internal */ /* Imports: Internal */
import { expectApprox } from '../../src' import { expectApprox } from '../../src'
import { assert } from 'chai'
describe('expectApprox', async () => { describe('expectApprox', () => {
it('should throw an error if the actual value is higher than expected', async () => { it('should pass when the actual number is higher, but within the expected range of the target', async () => {
expectApprox(119, 100, {
percentUpperDeviation: 20,
percentLowerDeviation: 20,
absoluteUpperDeviation: 20,
absoluteLowerDeviation: 20,
})
})
it('should pass when the actual number is lower, but within the expected range of the target', async () => {
expectApprox(81, 100, {
percentUpperDeviation: 20,
percentLowerDeviation: 20,
absoluteUpperDeviation: 20,
absoluteLowerDeviation: 20,
})
})
it('should throw an error when no deviation values are given', async () => {
try { try {
expectApprox(121, 100, { expectApprox(101, 100, {})
upperPercentDeviation: 20, assert.fail('expectApprox did not throw an error')
})
} catch (error) { } catch (error) {
expect(error.message).to.equal( expect(error.message).to.equal(
'Actual value (121) is more than 20% greater than target (100): expected false to be true' 'Must define at least one parameter to limit the deviation of the actual value.'
) )
} }
}) })
it('should throw an error if the actual value is lower than expected', async () => { describe('should throw an error if the actual value is higher than expected', () => {
try { describe('... when only one upper bound value is defined', () => {
expectApprox(79, 100, { it('... and percentUpperDeviation sets the upper bound', async () => {
upperPercentDeviation: 0, try {
lowerPercentDeviation: 20, expectApprox(121, 100, {
percentUpperDeviation: 20,
})
assert.fail('expectApprox did not throw an error')
} catch (error) {
expect(error.message).to.equal(
'Actual value (121) is greater than the calculated upper bound of (120): expected false to be true'
)
}
}) })
} catch (error) { it('... and absoluteUpperDeviation sets the upper bound', async () => {
expect(error.message).to.equal( try {
'Actual value (79) is more than 20% less than target (100): expected false to be true' expectApprox(121, 100, {
) absoluteUpperDeviation: 20,
} })
assert.fail('expectApprox did not throw an error')
} catch (error) {
expect(error.message).to.equal(
'Actual value (121) is greater than the calculated upper bound of (120): expected false to be true'
)
}
})
})
describe('... when both values are defined', () => {
it('... and percentUpperDeviation sets the upper bound', async () => {
try {
expectApprox(121, 100, {
percentUpperDeviation: 20,
absoluteUpperDeviation: 30,
})
assert.fail('expectApprox did not throw an error')
} catch (error) {
expect(error.message).to.equal(
'Actual value (121) is greater than the calculated upper bound of (120): expected false to be true'
)
}
})
it('... and absoluteUpperDeviation sets the upper bound', async () => {
try {
expectApprox(121, 100, {
percentUpperDeviation: 30,
absoluteUpperDeviation: 20,
})
assert.fail('expectApprox did not throw an error')
} catch (error) {
expect(error.message).to.equal(
'Actual value (121) is greater than the calculated upper bound of (120): expected false to be true'
)
}
})
})
})
describe('should throw an error if the actual value is lower than expected', () => {
describe('... when only one lower bound value is defined', () => {
it('... and percentLowerDeviation sets the lower bound', async () => {
try {
expectApprox(79, 100, {
percentLowerDeviation: 20,
})
assert.fail('expectApprox did not throw an error')
} catch (error) {
expect(error.message).to.equal(
'Actual value (79) is less than the calculated lower bound of (80): expected false to be true'
)
}
})
it('... and absoluteLowerDeviation sets the lower bound', async () => {
try {
expectApprox(79, 100, {
absoluteLowerDeviation: 20,
})
assert.fail('expectApprox did not throw an error')
} catch (error) {
expect(error.message).to.equal(
'Actual value (79) is less than the calculated lower bound of (80): expected false to be true'
)
}
})
})
describe('... when both values are defined', () => {
it('... and percentLowerDeviation sets the lower bound', async () => {
try {
expectApprox(79, 100, {
percentLowerDeviation: 20,
absoluteLowerDeviation: 30,
})
assert.fail('expectApprox did not throw an error')
} catch (error) {
expect(error.message).to.equal(
'Actual value (79) is less than the calculated lower bound of (80): expected false to be true'
)
}
})
it('... and absoluteLowerDeviation sets the lower bound', async () => {
try {
expectApprox(79, 100, {
percentLowerDeviation: 30,
absoluteLowerDeviation: 20,
})
assert.fail('expectApprox did not throw an error')
} catch (error) {
expect(error.message).to.equal(
'Actual value (79) is less than the calculated lower bound of (80): expected false to be true'
)
}
})
})
}) })
}) })
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment