Refactor Time library to use valueBefore/valueAfter (#4555)

Co-authored-by: Francisco <fg@frang.io>
This commit is contained in:
Hadrien Croubois
2023-09-06 04:19:21 +02:00
committed by GitHub
parent bb7ca7d151
commit 87f7a2cd42
11 changed files with 203 additions and 45 deletions

View File

@ -3,6 +3,7 @@ const { constants, expectEvent, time } = require('@openzeppelin/test-helpers');
const { expectRevertCustomError } = require('../../helpers/customError');
const { selector } = require('../../helpers/methods');
const { clockFromReceipt } = require('../../helpers/time');
const { product } = require('../../helpers/iterate');
const AccessManager = artifacts.require('$AccessManager');
const AccessManagedTarget = artifacts.require('$AccessManagedTarget');
@ -620,8 +621,6 @@ contract('AccessManager', function (accounts) {
// WIP
describe('Calling restricted & unrestricted functions', function () {
const product = (...arrays) => arrays.reduce((a, b) => a.flatMap(ai => b.map(bi => [...ai, bi])), [[]]);
for (const [callerGroups, fnGroup, closed, delay] of product(
[[], [GROUPS.SOME]],
[undefined, GROUPS.ADMIN, GROUPS.SOME, GROUPS.PUBLIC],

16
test/helpers/iterate.js Normal file
View File

@ -0,0 +1,16 @@
// Map values in an object
const mapValues = (obj, fn) => Object.fromEntries(Object.entries(obj).map(([k, v]) => [k, fn(v)]));
// Array of number or bigint
const max = (...values) => values.slice(1).reduce((x, y) => (x > y ? x : y), values[0]);
const min = (...values) => values.slice(1).reduce((x, y) => (x < y ? x : y), values[0]);
// Cartesian product of a list of arrays
const product = (...arrays) => arrays.reduce((a, b) => a.flatMap(ai => b.map(bi => [...ai, bi])), [[]]);
module.exports = {
mapValues,
max,
min,
product,
};

View File

@ -1,7 +0,0 @@
function mapValues(obj, fn) {
return Object.fromEntries(Object.entries(obj).map(([k, v]) => [k, fn(v)]));
}
module.exports = {
mapValues,
};

View File

@ -3,7 +3,7 @@ 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 { mapValues } = require('../../helpers/iterate');
const EIP712Verifier = artifacts.require('$EIP712Verifier');
const Clones = artifacts.require('$Clones');

View File

@ -1,5 +1,5 @@
const { BN, constants } = require('@openzeppelin/test-helpers');
const { mapValues } = require('../../helpers/map-values');
const { mapValues } = require('../../helpers/iterate');
const EnumerableMap = artifacts.require('$EnumerableMap');

View File

@ -1,5 +1,5 @@
const EnumerableSet = artifacts.require('$EnumerableSet');
const { mapValues } = require('../../helpers/map-values');
const { mapValues } = require('../../helpers/iterate');
const { shouldBehaveLikeSet } = require('./EnumerableSet.behavior');

View File

@ -0,0 +1,161 @@
require('@openzeppelin/test-helpers');
const { expect } = require('chai');
const { clock } = require('../../helpers/time');
const { product, max } = require('../../helpers/iterate');
const Time = artifacts.require('$Time');
const MAX_UINT32 = 1n << (32n - 1n);
const MAX_UINT48 = 1n << (48n - 1n);
const SOME_VALUES = [0n, 1n, 2n, 15n, 16n, 17n, 42n];
const asUint = (value, size) => {
if (typeof value != 'bigint') {
value = BigInt(value);
}
// chai does not support bigint :/
if (value < 0 || value >= 1n << BigInt(size)) {
throw new Error(`value is not a valid uint${size}`);
}
return value;
};
const unpackDelay = delay => ({
valueBefore: (asUint(delay, 112) >> 32n) % (1n << 32n),
valueAfter: (asUint(delay, 112) >> 0n) % (1n << 32n),
effect: (asUint(delay, 112) >> 64n) % (1n << 48n),
});
const packDelay = ({ valueBefore, valueAfter = 0n, effect = 0n }) =>
(asUint(valueAfter, 32) << 0n) + (asUint(valueBefore, 32) << 32n) + (asUint(effect, 48) << 64n);
const effectSamplesForTimepoint = timepoint => [
0n,
timepoint,
...product([-1n, 1n], [1n, 2n, 17n, 42n])
.map(([sign, shift]) => timepoint + sign * shift)
.filter(effect => effect > 0n && effect <= MAX_UINT48),
MAX_UINT48,
];
contract('Time', function () {
beforeEach(async function () {
this.mock = await Time.new();
});
describe('clocks', function () {
it('timestamp', async function () {
expect(await this.mock.$timestamp()).to.be.bignumber.equal(web3.utils.toBN(await clock.timestamp()));
});
it('block number', async function () {
expect(await this.mock.$blockNumber()).to.be.bignumber.equal(web3.utils.toBN(await clock.blocknumber()));
});
});
describe('Delay', function () {
describe('packing and unpacking', function () {
const valueBefore = 17n;
const valueAfter = 42n;
const effect = 69n;
const delay = 1272825341158973505578n;
it('pack', async function () {
const packed = await this.mock.$pack(valueBefore, valueAfter, effect);
expect(packed).to.be.bignumber.equal(delay.toString());
const packed2 = packDelay({ valueBefore, valueAfter, effect });
expect(packed2).to.be.equal(delay);
});
it('unpack', async function () {
const unpacked = await this.mock.$unpack(delay);
expect(unpacked[0]).to.be.bignumber.equal(valueBefore.toString());
expect(unpacked[1]).to.be.bignumber.equal(valueAfter.toString());
expect(unpacked[2]).to.be.bignumber.equal(effect.toString());
const unpacked2 = unpackDelay(delay);
expect(unpacked2).to.be.deep.equal({ valueBefore, valueAfter, effect });
});
});
it('toDelay', async function () {
for (const value of [...SOME_VALUES, MAX_UINT32]) {
const delay = await this.mock.$toDelay(value).then(unpackDelay);
expect(delay).to.be.deep.equal({ valueBefore: 0n, valueAfter: value, effect: 0n });
}
});
it('getAt & getFullAt', async function () {
const valueBefore = 24194n;
const valueAfter = 4214143n;
for (const timepoint of [...SOME_VALUES, MAX_UINT48])
for (const effect of effectSamplesForTimepoint(timepoint)) {
const isPast = effect <= timepoint;
const delay = packDelay({ valueBefore, valueAfter, effect });
expect(await this.mock.$getAt(delay, timepoint)).to.be.bignumber.equal(
String(isPast ? valueAfter : valueBefore),
);
const getFullAt = await this.mock.$getFullAt(delay, timepoint);
expect(getFullAt[0]).to.be.bignumber.equal(String(isPast ? valueAfter : valueBefore));
expect(getFullAt[1]).to.be.bignumber.equal(String(isPast ? 0n : valueAfter));
expect(getFullAt[2]).to.be.bignumber.equal(String(isPast ? 0n : effect));
}
});
it('get & getFull', async function () {
const timepoint = await clock.timestamp().then(BigInt);
const valueBefore = 24194n;
const valueAfter = 4214143n;
for (const effect of effectSamplesForTimepoint(timepoint)) {
const isPast = effect <= timepoint;
const delay = packDelay({ valueBefore, valueAfter, effect });
expect(await this.mock.$get(delay)).to.be.bignumber.equal(String(isPast ? valueAfter : valueBefore));
const result = await this.mock.$getFull(delay);
expect(result[0]).to.be.bignumber.equal(String(isPast ? valueAfter : valueBefore));
expect(result[1]).to.be.bignumber.equal(String(isPast ? 0n : valueAfter));
expect(result[2]).to.be.bignumber.equal(String(isPast ? 0n : effect));
}
});
it('withUpdate', async function () {
const timepoint = await clock.timestamp().then(BigInt);
const valueBefore = 24194n;
const valueAfter = 4214143n;
const newvalueAfter = 94716n;
for (const effect of effectSamplesForTimepoint(timepoint))
for (const minSetback of [...SOME_VALUES, MAX_UINT32]) {
const isPast = effect <= timepoint;
const expectedvalueBefore = isPast ? valueAfter : valueBefore;
const expectedSetback = max(minSetback, expectedvalueBefore - newvalueAfter, 0n);
const result = await this.mock.$withUpdate(
packDelay({ valueBefore, valueAfter, effect }),
newvalueAfter,
minSetback,
);
expect(result[0]).to.be.bignumber.equal(
String(
packDelay({
valueBefore: expectedvalueBefore,
valueAfter: newvalueAfter,
effect: timepoint + expectedSetback,
}),
),
);
expect(result[1]).to.be.bignumber.equal(String(timepoint + expectedSetback));
}
});
});
});