Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 89 additions & 23 deletions contracts/BackedAutoFeeTokenImplementation.sol
Original file line number Diff line number Diff line change
Expand Up @@ -89,35 +89,28 @@ contract BackedAutoFeeTokenImplementation is BackedTokenImplementation, IBackedA
uint256 public newMultiplier;
uint256 public newMultiplierActivationTime;

/**
* @dev Append-only log of *explicit* multiplier updates submitted via
* `updateMultiplierValue` / `updateMultiplierWithNonce`.
*
* Index 0 is a genesis sentinel `{1e18, 1e18, 0}` so that
* `_storeScheduledMultiplierUpdate` can safely read `length - 1`.
*
* Pending future-dated entries are overridden in place: when a new
* explicit update is submitted while the previous one is still
* scheduled (activationTime > block.timestamp), the previous entry is
* popped before the new one is appended. The corresponding
* `MultiplierScheduled` event from the popped entry remains on chain;
* the array does not retain it.
*/
MultiplierUpdate[] public multiplierUpdates;

function multiplierNonce() external view returns (uint256) {
if(block.timestamp >= newMultiplierActivationTime) {
return newMultiplierNonce;
}
return lastMultiplierNonce;
}
// Events:

/**
* @dev Emitted when multiplier updater is changed
*/
event NewMultiplierUpdater(address indexed newMultiplierUpdater);

/**
* @dev Emitted when `value` token shares are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event TransferShares(
address indexed from,
address indexed to,
uint256 value
);

/**
* @dev Emitted when multiplier value is updated
*/
event MultiplierUpdated(uint256 value);

// Modifiers:

Expand Down Expand Up @@ -198,6 +191,25 @@ contract BackedAutoFeeTokenImplementation is BackedTokenImplementation, IBackedA
newMultiplierActivationTime = 0;
}

function initialize_v4(
MultiplierUpdate [] calldata _pastMultipliersUpdates
) external virtual {
require(multiplierUpdates.length == 0, "BackedAutoFeeTokenImplementation v4 already initialized");
multiplierUpdates.push(MultiplierUpdate({
previousMultiplier: 1e18,
newMultiplier: 1e18,
activationTime: 0
}));
for (uint i = 0; i < _pastMultipliersUpdates.length; i++) {
require(_pastMultipliersUpdates[i].previousMultiplier > 0, "BackedAutoFeeTokenImplementation: previousMultiplier cannot be zero");
multiplierUpdates.push(MultiplierUpdate({
previousMultiplier: _pastMultipliersUpdates[i].previousMultiplier,
newMultiplier: _pastMultipliersUpdates[i].newMultiplier,
activationTime: _pastMultipliersUpdates[i].activationTime
}));
}
}

function _initialize_auto_fee(
uint256 _periodLength,
uint256 _lastTimeFeeApplied,
Expand All @@ -214,6 +226,11 @@ contract BackedAutoFeeTokenImplementation is BackedTokenImplementation, IBackedA
periodLength = _periodLength;
lastTimeFeeApplied = _lastTimeFeeApplied;
feePerPeriod = _feePerPeriod;
multiplierUpdates.push(MultiplierUpdate({
previousMultiplier: 1e18,
newMultiplier: 1e18,
activationTime: 0
}));
}

/**
Expand Down Expand Up @@ -292,6 +309,13 @@ contract BackedAutoFeeTokenImplementation is BackedTokenImplementation, IBackedA
return _getUnderlyingAmountByShares(_sharesAmount, currentMultiplier);
}

/**
* @return Length of scheduled multipliers updates array
*/
function multiplierUpdatesLength() external view returns (uint256) {
return multiplierUpdates.length;
}

/**
* @dev Delegated Transfer Shares, transfer shares via a sign message, using erc712.
*/
Expand Down Expand Up @@ -424,6 +448,7 @@ contract BackedAutoFeeTokenImplementation is BackedTokenImplementation, IBackedA
uint256 pendingNewMultiplierActivationTime
) public onlyMultiplierUpdater updateMultiplier onlyUpdatedMultiplier(oldMultiplier) {
_updateMultiplier(pendingNewMultiplier, lastMultiplierNonce + 1, pendingNewMultiplierActivationTime);
_storeScheduledMultiplierUpdate(oldMultiplier, pendingNewMultiplier, pendingNewMultiplierActivationTime);
}

/**
Expand All @@ -441,6 +466,46 @@ contract BackedAutoFeeTokenImplementation is BackedTokenImplementation, IBackedA
uint256 pendingNewMultiplierActivationTime
) external onlyMultiplierUpdater updateMultiplier onlyUpdatedMultiplier(oldMultiplier) onlyNewerMultiplierNonce(newMultiplierNonce){
_updateMultiplier(newMultiplier, newMultiplierNonce, pendingNewMultiplierActivationTime);
_storeScheduledMultiplierUpdate(oldMultiplier, newMultiplier, pendingNewMultiplierActivationTime);
}

/**
* @dev Stores an explicit multiplier update in `multiplierUpdates`.
*
* If the previously stored entry is still pending (activationTime in the
* future), it is overwritten in place — only one scheduled update can be
* pending at a time — and a `MultiplierScheduleOverridden` event is
* emitted so off-chain consumers can reconcile the now-stale
* `MultiplierScheduled` event for the discarded entry.
*
* Tolerates an empty array (the not-yet-migrated state between a v4
* implementation upgrade and an `initialize_v4` call) by falling through
* to the append path.
*/
function _storeScheduledMultiplierUpdate(
uint256 _previousMultiplierValue,
uint256 _multiplierValue,
uint256 _multiplierActivationTime
) internal {
MultiplierUpdate memory entry = MultiplierUpdate({
previousMultiplier: _previousMultiplierValue,
newMultiplier: _multiplierValue,
activationTime: _multiplierActivationTime > block.timestamp ? _multiplierActivationTime : block.timestamp
});

uint256 len = multiplierUpdates.length;
if (len > 0 && multiplierUpdates[len - 1].activationTime > block.timestamp) {
MultiplierUpdate memory overridden = multiplierUpdates[len - 1];
multiplierUpdates[len - 1] = entry;
emit MultiplierScheduleOverridden(
overridden.newMultiplier,
overridden.activationTime,
entry.newMultiplier,
entry.activationTime
);
} else {
multiplierUpdates.push(entry);
}
}

/**
Expand Down Expand Up @@ -624,6 +689,7 @@ contract BackedAutoFeeTokenImplementation is BackedTokenImplementation, IBackedA

if(pendingNewMultiplierActivationTime > block.timestamp) {
newMultiplierActivationTime = pendingNewMultiplierActivationTime;
emit MultiplierScheduled(pendingNewMultiplier, pendingNewMultiplierActivationTime);
// We don't need to update lastMultiplier and lastMultiplierNonce here, as they will be updated in updateMultiplier modifier when calling updateMultiplier method
} else {
newMultiplierActivationTime = 0;
Expand Down
101 changes: 101 additions & 0 deletions contracts/interfaces/IBackedAutoFeeToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,69 @@ import "./IBackedToken.sol";
* - The multiplier can be updated by an authorized multiplierUpdater address
*/
interface IBackedAutoFeeToken is IBackedToken {
/**
* @dev Struct representing multiplier update
* @param previousMultiplier The multiplier value before this update
* @param newMultiplier The multiplier value after this update
* @param activationTime The Unix timestamp when this update was/will be activated
*/
struct MultiplierUpdate {
uint256 previousMultiplier;
uint256 newMultiplier;
uint256 activationTime;
}

// Events

/**
* @dev Emitted when the multiplier updater address is changed
* @param newMultiplierUpdater The address of the new multiplier updater
*/
event NewMultiplierUpdater(address indexed newMultiplierUpdater);

/**
* @dev Emitted when shares are transferred between addresses
* @param from The address shares are transferred from
* @param to The address shares are transferred to
* @param value The amount of shares transferred
*/
event TransferShares(address indexed from, address indexed to, uint256 value);

/**
* @dev Emitted when the multiplier value is updated and activated
* @param value The new multiplier value (in 1e18 precision)
*/
event MultiplierUpdated(uint256 value);

/**
* @dev Emitted when a multiplier is scheduled for future activation
* @param newMultiplier The new multiplier value that will be activated (in 1e18 precision)
* @param activationTime The Unix timestamp when the multiplier will become active
*/
event MultiplierScheduled(uint256 newMultiplier, uint256 activationTime);

/**
* @dev Emitted when a previously scheduled multiplier update is replaced
* before its activationTime is reached. The overridden entry is removed
* from `multiplierUpdates` in place and replaced by the new one.
*
* Off-chain consumers reconstructing state from events should drop any
* earlier `MultiplierScheduled(overriddenMultiplier, overriddenActivationTime)`
* upon seeing this event.
*
* @param overriddenMultiplier The newMultiplier of the pending entry that
* was discarded (the one previously announced via MultiplierScheduled)
* @param overriddenActivationTime The activationTime of the discarded entry
* @param newMultiplier The newMultiplier that replaces it (already announced
* in this same transaction via MultiplierScheduled or MultiplierUpdated)
* @param newActivationTime The activationTime stored for the replacement
*/
event MultiplierScheduleOverridden(
uint256 overriddenMultiplier,
uint256 overriddenActivationTime,
uint256 newMultiplier,
uint256 newActivationTime
);

// View functions - EIP-712 and Roles

Expand Down Expand Up @@ -119,6 +182,44 @@ interface IBackedAutoFeeToken is IBackedToken {
*/
function getUnderlyingAmountByShares(uint256 _sharesAmount) external view returns (uint256);

/**
* @dev Returns the length of the multiplierUpdates array.
*
* The array records *explicit* multiplier updates only (those submitted
* via `updateMultiplierValue` / `updateMultiplierWithNonce`). Automatic
* per-period fee decay is NOT appended. See `multiplierUpdates` for the
* full semantics.
*
* @return The number of explicit multiplier updates stored
* (including the genesis sentinel at index 0).
*/
function multiplierUpdatesLength() external view returns (uint256);

/**
* @dev Returns a specific explicit multiplier update by index.
*
* This array is an append-only log of *explicit* multiplier updates only;
* automatic per-period fee decay is applied lazily to `lastMultiplier`
* without appending here. As a result `previousMultiplier` at index `i`
* is the fee-decayed value at the time of the i-th explicit update and
* is typically less than `newMultiplier` at index `i-1` — the gap is the
* accrual that happened in between.
*
* Index 0 is a genesis sentinel `{1e18, 1e18, 0}`. A future-dated entry
* that is overridden before activation is popped from this array; the
* `MultiplierScheduled` event for the popped entry remains on chain.
*
* @param index The index in the multiplierUpdates array
* @return previousMultiplier The (possibly fee-decayed) multiplier value
* immediately before this explicit update was applied
* @return newMultiplier The multiplier value after this update
* @return activationTime The Unix timestamp when this update was/will be
* activated; equals `block.timestamp` at recording time for
* immediate updates, or the requested future timestamp for
* scheduled ones
*/
function multiplierUpdates(uint256 index) external view returns (uint256 previousMultiplier, uint256 newMultiplier, uint256 activationTime);

// State-changing functions - Share Transfers

/**
Expand Down
27 changes: 27 additions & 0 deletions scripts/helpers/upgradeToV4.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { ethers } from "hardhat";
import fs from "fs";

async function main() {
const PROXY_ADMIN = process.env.PROXY_ADMIN!;
const TOKEN_PROXY = process.env.TOKEN_PROXY!;

type RawUpdate = { previousMultiplier: string; newMultiplier: string; activationTime: number };
const pastUpdates: RawUpdate[] = JSON.parse(fs.readFileSync(process.env.PAST_UPDATES_FILE!, "utf8"));

const v4Impl = await (
await ethers.getContractFactory("BackedAutoFeeTokenImplementation")
).deploy();
await v4Impl.deployed();

const proxyAdmin = await ethers.getContractAt("ProxyAdmin", PROXY_ADMIN);

const tx = await proxyAdmin.upgradeAndCall(
TOKEN_PROXY,
v4Impl.address,
v4Impl.interface.encodeFunctionData("initialize_v4", [pastUpdates])
);
const receipt = await tx.wait();
console.log(`Upgrade + initialize_v4 mined in block ${receipt.blockNumber} (tx: ${tx.hash})`);
}

main().catch((e) => { console.error(e); process.exit(1); });
Loading
Loading