Skip to content

Commit

Permalink
queue multiple fcs
Browse files Browse the repository at this point in the history
  • Loading branch information
mejango committed Aug 21, 2023
1 parent 5ae86db commit 6a150c2
Show file tree
Hide file tree
Showing 4 changed files with 159 additions and 30 deletions.
58 changes: 30 additions & 28 deletions contracts/JBFundingCycleStore.sol
Original file line number Diff line number Diff line change
Expand Up @@ -265,8 +265,13 @@ contract JBFundingCycleStore is JBControllerUtility, IJBFundingCycleStore {
}
}

// The configuration timestamp is now.
uint256 _configuration = block.timestamp;
// Get a reference to the latest configration.
uint256 _latestConfiguration = latestConfigurationOf[_projectId];

// The configuration timestamp is now, or an increment from now if the current timestamp is taken.
uint256 _configuration = _latestConfiguration >= block.timestamp
? _latestConfiguration + 1
: block.timestamp;

// Set up a reconfiguration by configuring intrinsic properties.
_configureIntrinsicPropertiesFor(_projectId, _configuration, _data.weight, _mustStartAtOrAfter);
Expand Down Expand Up @@ -332,10 +337,12 @@ contract JBFundingCycleStore is JBControllerUtility, IJBFundingCycleStore {
// Get a reference to the funding cycle.
JBFundingCycle memory _baseFundingCycle = _getStructFor(_projectId, _currentConfiguration);

if (!_isApproved(_projectId, _baseFundingCycle) || block.timestamp < _baseFundingCycle.start)
// If it hasn't been approved or hasn't yet started, set the ID to be the funding cycle it's based on,
// which carries the latest approved configuration.
_baseFundingCycle = _getStructFor(_projectId, _baseFundingCycle.basedOn);
// If the funding cycle hasn't started but is currently approved OR or it has started but wasn't approved, set the ID to be the funding cycle it's based on,
// which carries the latest approved configuration.
if (
(block.timestamp < _baseFundingCycle.start && _isApproved(_projectId, _baseFundingCycle)) ||
(block.timestamp > _baseFundingCycle.start && !_isApproved(_projectId, _baseFundingCycle))
) _baseFundingCycle = _getStructFor(_projectId, _baseFundingCycle.basedOn);

// The configuration can't be the same as the base configuration.
if (_baseFundingCycle.configuration == _configuration) revert NO_SAME_BLOCK_RECONFIGURATION();
Expand Down Expand Up @@ -476,35 +483,30 @@ contract JBFundingCycleStore is JBControllerUtility, IJBFundingCycleStore {
/// @dev A value of 0 is returned if no funding cycle was found.
/// @dev Assumes the project has a latest configuration.
/// @param _projectId The ID of the project to look through.
/// @return configuration The configuration of an eligible funding cycle if one exists, or 0 if one doesn't exist.
function _eligibleOf(uint256 _projectId) private view returns (uint256 configuration) {
/// @return The configuration of an eligible funding cycle if one exists, or 0 if one doesn't exist.
function _eligibleOf(uint256 _projectId) private view returns (uint256) {
// Get a reference to the project's latest funding cycle.
configuration = latestConfigurationOf[_projectId];
uint256 _configuration = latestConfigurationOf[_projectId];

// Get the latest funding cycle.
JBFundingCycle memory _fundingCycle = _getStructFor(_projectId, configuration);

// If the latest is expired, return an empty funding cycle.
// A duration of 0 cannot be expired.
if (
_fundingCycle.duration > 0 && block.timestamp >= _fundingCycle.start + _fundingCycle.duration
) return 0;
JBFundingCycle memory _fundingCycle = _getStructFor(_projectId, _configuration);

// Return the funding cycle's configuration if it has started.
if (block.timestamp >= _fundingCycle.start) return _fundingCycle.configuration;
// Loop through all most recently configured funding cycles until an eligible one is found, or we've proven one can't exit.
while (_fundingCycle.number != 0) {
// If the latest is expired, return an empty funding cycle.
// A duration of 0 cannot be expired.
if (
_fundingCycle.duration != 0 &&
block.timestamp >= _fundingCycle.start + _fundingCycle.duration
) return 0;

// Get a reference to the cycle's base configuration.
JBFundingCycle memory _baseFundingCycle = _getStructFor(_projectId, _fundingCycle.basedOn);
// Return the funding cycle's configuration if it has started.
if (block.timestamp >= _fundingCycle.start) return _fundingCycle.configuration;

// If the base cycle isn't eligible, the project has no eligible cycle.
// A duration of 0 is always eligible.
if (
_baseFundingCycle.duration > 0 &&
block.timestamp >= _baseFundingCycle.start + _baseFundingCycle.duration
) return 0;
_fundingCycle = _getStructFor(_projectId, _fundingCycle.basedOn);
}

// Return the configuration that the latest funding cycle is based on.
configuration = _fundingCycle.basedOn;
return 0;
}

/// @notice A view of the funding cycle that would be created based on the provided one if the project doesn't make a reconfiguration.
Expand Down
2 changes: 2 additions & 0 deletions contracts/JBReconfigurationBufferBallot.sol
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ contract JBReconfigurationBufferBallot is ERC165, IJBFundingCycleBallot {
if (_configured > _start) return JBBallotState.Failed;

unchecked {
// If the ballot hasn't yet started, it's state is active.
if (block.timestamp < _start - duration) return JBBallotState.Active;
// If there was sufficient time between configuration and the start of the cycle, it is approved. Otherwise, it is failed.
return (_start - _configured < duration) ? JBBallotState.Failed : JBBallotState.Approved;
}
Expand Down
2 changes: 1 addition & 1 deletion hardhat.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ module.exports = {
defaultNetwork,
networks: {
hardhat: {
allowUnlimitedContractSize: true,
allowUnlimitedContractSize: true
},
localhost: {
url: 'http://localhost:8545',
Expand Down
127 changes: 126 additions & 1 deletion test/jb_funding_cycle_store/configure_for.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import ijbFundingCycleBallot from '../../artifacts/contracts/interfaces/IJBFundi
import { BigNumber } from 'ethers';
import errors from '../helpers/errors.json';

describe('JBFundingCycleStore::configureFor(...)', function () {
describe.only('JBFundingCycleStore::configureFor(...)', function () {
const PROJECT_ID = 2;

const EMPTY_FUNDING_CYCLE = {
Expand Down Expand Up @@ -815,6 +815,131 @@ describe('JBFundingCycleStore::configureFor(...)', function () {
);
});

// it.only('Should not overwrite when configure a subsequent cycle during a funding cycle with approved ballot', async function () {
// const { controller, mockJbDirectory, jbFundingCycleStore, mockBallot } = await setup();
// await mockJbDirectory.mock.controllerOf.withArgs(PROJECT_ID).returns(controller.address);

// const firstFundingCycleData = createFundingCycleData({ ballot: mockBallot.address });

// // Configure first funding cycle
// const firstConfigureForTx = await jbFundingCycleStore
// .connect(controller)
// .configureFor(
// PROJECT_ID,
// firstFundingCycleData,
// RANDOM_FUNDING_CYCLE_METADATA_1,
// FUNDING_CYCLE_CAN_START_ASAP,
// );

// // The timestamp the first configuration was made during.
// const firstConfigurationTimestamp = await getTimestamp(firstConfigureForTx.blockNumber);

// const expectedFirstFundingCycle = {
// number: ethers.BigNumber.from(1),
// configuration: firstConfigurationTimestamp,
// basedOn: ethers.BigNumber.from(0),
// start: firstConfigurationTimestamp,
// duration: firstFundingCycleData.duration,
// weight: firstFundingCycleData.weight,
// discountRate: firstFundingCycleData.discountRate,
// ballot: firstFundingCycleData.ballot,
// metadata: RANDOM_FUNDING_CYCLE_METADATA_1,
// };

// const secondFundingCycleData = createFundingCycleData({
// ballot: ethers.constants.AddressZero,
// duration: firstFundingCycleData.duration.add(1),
// discountRate: firstFundingCycleData.discountRate.add(1),
// weight: firstFundingCycleData.weight.add(1),
// });

// const ballotDuration = firstFundingCycleData.duration / 2;

// // Set the ballot to have a short duration.
// await mockBallot.mock.duration.withArgs().returns(ballotDuration);

// // Configure second funding cycle
// const secondConfigureForTx = await jbFundingCycleStore
// .connect(controller)
// .configureFor(
// PROJECT_ID,
// secondFundingCycleData,
// RANDOM_FUNDING_CYCLE_METADATA_2,
// FUNDING_CYCLE_CAN_START_ASAP,
// );

// // The timestamp the second configuration was made during.
// const secondConfigurationTimestamp = await getTimestamp(secondConfigureForTx.blockNumber);

// await expect(secondConfigureForTx)
// .to.emit(jbFundingCycleStore, `Init`)
// .withArgs(secondConfigurationTimestamp, PROJECT_ID, /*basedOn=*/ firstConfigurationTimestamp);

// //keep half the seconds before the end of the cycle so make all necessary checks before the cycle ends.
// await fastForward(
// firstConfigureForTx.blockNumber,
// firstFundingCycleData.duration.sub(ballotDuration / 2),
// );

// const expectedSecondFundingCycle = {
// number: 2, // second cycle
// configuration: secondConfigurationTimestamp,
// basedOn: firstConfigurationTimestamp, // based on the first cycle
// start: firstConfigurationTimestamp.add(firstFundingCycleData.duration), // starts at the end of the first cycle
// duration: secondFundingCycleData.duration,
// weight: secondFundingCycleData.weight,
// discountRate: secondFundingCycleData.discountRate,
// ballot: secondFundingCycleData.ballot,
// metadata: RANDOM_FUNDING_CYCLE_METADATA_2,
// };

// // Mock the ballot on the funding cycle as approved.
// await mockBallot.mock.stateOf
// .withArgs(
// PROJECT_ID,
// secondConfigurationTimestamp,
// firstConfigurationTimestamp.add(firstFundingCycleData.duration),
// )
// .returns(ballotStatus.APPROVED);

// const thirdFundingCycleData = createFundingCycleData({
// ballot: ethers.constants.AddressZero,
// duration: secondFundingCycleData.duration.add(1),
// discountRate: secondFundingCycleData.discountRate.add(1),
// weight: secondFundingCycleData.weight.add(1),
// });

// // Configure third funding cycle
// const thirdConfigureForTx = await jbFundingCycleStore
// .connect(controller)
// .configureFor(
// PROJECT_ID,
// thirdFundingCycleData,
// RANDOM_FUNDING_CYCLE_METADATA_2,
// FUNDING_CYCLE_CAN_START_ASAP,
// );

// // expect(
// // cleanFundingCycle(await jbFundingCycleStore.get(PROJECT_ID, secondConfigurationTimestamp)),
// // ).to.eql(expectedSecondFundingCycle);

// let [latestFundingCycle, ballotState] = await jbFundingCycleStore.latestConfiguredOf(
// PROJECT_ID,
// );
// // expect(cleanFundingCycle(latestFundingCycle)).to.eql(expectedThirdFundingCycle);
// // expect(ballotState).to.deep.eql(ballotStatus.APPROVED);
// // expect(cleanFundingCycle(await jbFundingCycleStore.currentOf(PROJECT_ID))).to.eql({
// // ...expectedFirstFundingCycle,
// // number: expectedFirstFundingCycle.number.add(cycleDiff.sub(1)),
// // start: expectedFirstFundingCycle.start.add(
// // expectedFirstFundingCycle.duration.mul(cycleDiff.sub(1)),
// // ),
// // });
// // expect(cleanFundingCycle(await jbFundingCycleStore.queuedOf(PROJECT_ID))).to.eql(
// // expectedSecondFundingCycle,
// // );
// });

it('Should configure subsequent cycle during a rolled over funding cycle many multiples of duration later', async function () {
// Increase timeout because this test will need a long for loop iteration.
this.timeout(20000);
Expand Down

0 comments on commit 6a150c2

Please sign in to comment.