Merge branch 'master' into next-v5.0

This commit is contained in:
Francisco Giordano
2023-05-16 00:07:07 -03:00
308 changed files with 21085 additions and 11515 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 226 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 40 KiB

View File

@ -13,6 +13,7 @@
** xref:erc721.adoc[ERC721]
** xref:erc777.adoc[ERC777]
** xref:erc1155.adoc[ERC1155]
** xref:erc4626.adoc[ERC4626]
* xref:governance.adoc[Governance]
@ -20,4 +21,6 @@
* xref:utilities.adoc[Utilities]
* xref:fag.adoc[FAQ]
* xref:subgraphs::index.adoc[Subgraphs]
* xref:faq.adoc[FAQ]

View File

@ -131,6 +131,8 @@ Every role has an associated admin role, which grants permission to call the `gr
This mechanism can be used to create complex permissioning structures resembling organizational charts, but it also provides an easy way to manage simpler applications. `AccessControl` includes a special role, called `DEFAULT_ADMIN_ROLE`, which acts as the **default admin role for all roles**. An account with this role will be able to manage any other role, unless `_setRoleAdmin` is used to select a new admin role.
Since it is the admin for all roles by default, and in fact it is also its own admin, this role carries significant risk. To mitigate this risk we provide xref:api:access.adoc#AccessControlDefaultAdminRules[`AccessControlDefaultAdminRules`], a recommended extension of `AccessControl` that adds a number of enforced security measures for this role: the admin is restricted to a single account, with a 2-step transfer procedure with a delay in between steps.
Let's take a look at the ERC20 token example, this time taking advantage of the default admin role:
[source,solidity]

View File

@ -0,0 +1,214 @@
= ERC4626
:stem: latexmath
https://eips.ethereum.org/EIPS/eip-4626[ERC4626] is an extension of xref:erc20.adoc[ERC20] that proposes a standard interface for token vaults. This standard interface can be used by widely different contracts (including lending markets, aggregators, and intrinsically interest bearing tokens), which brings a number of subtleties. Navigating these potential issues is essential to implementing a compliant and composable token vault.
We provide a base implementation of ERC4626 that includes a simple vault. This contract is designed in a way that allows developers to easily re-configure the vault's behavior, with minimal overrides, while staying compliant. In this guide, we will discuss some security considerations that affect ERC4626. We will also discuss common customizations of the vault.
[[inflation-attack]]
== Security concern: Inflation attack
=== Visualizing the vault
In exchange for the assets deposited into an ERC4626 vault, a user receives shares. These shares can later be burned to redeem the corresponding underlying assets. The number of shares a user gets depends on the amount of assets they put in and on the exchange rate of the vault. This exchange rate is defined by the current liquidity held by the vault.
- If a vault has 100 tokens to back 200 shares, then each share is worth 0.5 assets.
- If a vault has 200 tokens to back 100 shares, then each share is worth 2.0 assets.
In other words, the exchange rate can be defined as the slope of the line that passes through the origin and the current number of assets and shares in the vault. Deposits and withdrawals move the vault in this line.
image::erc4626-rate-linear.png[Exchange rates in linear scale]
When plotted in log-log scale, the rate is defined similarly, but appears differently (because the point (0,0) is infinitely far away). Rates are represented by "diagonal" lines with different offsets.
image::erc4626-rate-loglog.png[Exchange rates in logarithmic scale]
In such a reprentation, widely different rates can be clearly visible in the same graph. This wouldn't be the case in linear scale.
image::erc4626-rate-loglogext.png[More exchange rates in logarithmic scale]
=== The attack
When depositing tokens, the number of shares a user gets is rounded down. This rounding takes away value from the user in favor or the vault (i.e. in favor of all the current share holders). This rounding is often negligible because of the amount at stake. If you deposit 1e9 shares worth of tokens, the rounding will have you lose at most 0.0000001% of your deposit. However if you deposit 10 shares worth of tokens, you could lose 10% of your deposit. Even worse, if you deposit <1 share worth of tokens, then you get 0 shares, and you basically made a donation.
For a given amount of assets, the more shares you receive the safer you are. If you want to limit your losses to at most 1%, you need to receive at least 100 shares.
image::erc4626-deposit.png[Depositing assets]
In the figure we can see that for a given deposit of 500 assets, the number of shares we get and the corresponding rounding losses depend on the exchange rate. If the exchange rate is that of the orange curve, we are getting less than a share, so we lose 100% of our deposit. However, if the exchange rate is that of the green curve, we get 5000 shares, which limits our rounding losses to at most 0.02%.
image::erc4626-mint.png[Minting shares]
Symmetrically, if we focus on limiting our losses to a maximum of 0.5%, we need to get at least 200 shares. With the green exchange rate that requires just 20 tokens, but with the orange rate that requires 200000 tokens.
We can clearly see that that the blue and green curves correspond to vaults that are safer than the yellow and orange curves.
The idea of an inflation attack is that an attacker can donate assets to the vault to move the rate curve to the right, and make the vault unsafe.
image::erc4626-attack.png[Inflation attack without protection]
Figure 6 shows how an attacker can manipulate the rate of an empty vault. First the attacker must deposit a small amount of tokens (1 token) and follow up with a donation of 1e5 tokens directly to the vault to move the exchange rate "right". This puts the vault in a state where any deposit smaller than 1e5 would be completely lost to the vault. Given that the attacker is the only share holder (from their donation), the attacker would steal all the tokens deposited.
An attacker would typically wait for a user to do the first deposit into the vault, and would frontrun that operation with the attack described above. The risk is low, and the size of the "donation" required to manipulate the vault is equivalent to the size of the deposit that is being attacked.
In math that gives:
- stem:[a_0] the attacker deposit
- stem:[a_1] the attacker donation
- stem:[u] the user deposit
[%header,cols=4*]
|===
|
| Assets
| Shares
| Rate
| initial
| stem:[0]
| stem:[0]
| -
| after attacker's deposit
| stem:[a_0]
| stem:[a_0]
| stem:[1]
| after attacker's donation
| stem:[a_0+a_1]
| stem:[a_0]
| stem:[\frac{a_0}{a_0+a_1}]
|===
This means a deposit of stem:[u] will give stem:[\frac{u \times a_0}{a_0 + a_1}] shares.
For the attacker to dilute that deposit to 0 shares, causing the user to lose all its deposit, it must ensure that
[stem]
++++
\frac{u \times a_0}{a_0+a_1} < 1 \iff u < 1 + \frac{a_1}{a_0}
++++
Using stem:[a_0 = 1] and stem:[a_1 = u] is enough. So the attacker only needs stem:[u+1] assets to perform a successful attack.
It is easy to generalize the above results to scenarios where the attacker is going after a smaller fraction of the user's deposit. In order to target stem:[\frac{u}{n}], the user needs to suffer rounding of a similar fraction, which means the user must receive at most stem:[n] shares. This results in:
[stem]
++++
\frac{u \times a_0}{a_0+a_1} < n \iff \frac{u}{n} < 1 + \frac{a_1}{a_0}
++++
In this scenario, the attack is stem:[n] times less powerful (in how much it is stealing) and costs stem:[n] times less to execute. In both cases, the amount of funds the attacker needs to commit is equivalent to its potential earnings.
=== Defending with a virtual offset
The defense we propose is based on the approach used in link:https://github.com/boringcrypto/YieldBox[YieldBox]. It consists of two parts:
- Use an offset between the "precision" of the representation of shares and assets. Said otherwise, we use more decimal places to represent the shares than the underlying token does to represent the assets.
- Include virtual shares and virtual assets in the exchange rate computation. These virtual assets enforce the conversion rate when the vault is empty.
These two parts work together in enforcing the security of the vault. First, the increased precision corresponds to a high rate, which we saw is safer as it reduces the rounding error when computing the amount of shares. Second, the virtual assets and shares (in addition to simplifying a lot of the computations) capture part of the donation, making it unprofitable for a developer to perform an attack.
Following the previous math definitions, we have:
- stem:[\delta] the vault offset
- stem:[a_0] the attacker deposit
- stem:[a_1] the attacker donation
- stem:[u] the user deposit
[%header,cols=4*]
|===
|
| Assets
| Shares
| Rate
| initial
| stem:[1]
| stem:[10^\delta]
| stem:[10^\delta]
| after attacker's deposit
| stem:[1+a_0]
| stem:[10^\delta \times (1+a_0)]
| stem:[10^\delta]
| after attacker's donation
| stem:[1+a_0+a_1]
| stem:[10^\delta \times (1+a_0)]
| stem:[10^\delta \times \frac{1+a_0}{1+a_0+a_1}]
|===
One important thing to note is that the attacker only owns a fraction stem:[\frac{a_0}{1 + a_0}] of the shares, so when doing the donation, he will only be able to recover that fraction stem:[\frac{a_1 \times a_0}{1 + a_0}] of the donation. The remaining stem:[\frac{a_1}{1+a_0}] are captured by the vault.
[stem]
++++
\mathit{loss} = \frac{a_1}{1+a_0}
++++
When the user deposits stem:[u], he receives
[stem]
++++
10^\delta \times u \times \frac{1+a_0}{1+a_0+a_1}
++++
For the attacker to dilute that deposit to 0 shares, causing the user to lose all its deposit, it must ensure that
[stem]
++++
10^\delta \times u \times \frac{1+a_0}{1+a_0+a_1} < 1
++++
[stem]
++++
\iff 10^\delta \times u < \frac{1+a_0+a_1}{1+a_0}
++++
[stem]
++++
\iff 10^\delta \times u < 1 + \frac{a_1}{1+a_0}
++++
[stem]
++++
\iff 10^\delta \times u \le \mathit{loss}
++++
- If the offset is 0, the attacker loss is at least equal to the user's deposit.
- If the offset is greater than 0, the attacker will have to suffer losses that are orders of magnitude bigger than the amount of value that can hypothetically be stolen from the user.
This shows that even with an offset of 0, the virtual shares and assets make this attack non profitable for the attacker. Bigger offsets increase the security even further by making any attack on the user extremely wasteful.
The following figure shows how the offset impacts the initial rate and limits the ability of an attacker with limited funds to inflate it effectively.
image::erc4626-attack-3a.png[Inflation attack without offset=3]
stem:[\delta = 3], stem:[a_0 = 1], stem:[a_1 = 10^5]
image::erc4626-attack-3b.png[Inflation attack without offset=3 and an attacker deposit that limits its losses]
stem:[\delta = 3], stem:[a_0 = 100], stem:[a_1 = 10^5]
image::erc4626-attack-6.png[Inflation attack without offset=6]
stem:[\delta = 6], stem:[a_0 = 1], stem:[a_1 = 10^5]
[[fees]]
== Custom behavior: Adding fees to the vault
In an ERC4626 vaults, fees can be captured during the deposit/mint and/or during the withdraw/redeem steps. In both cases it is essential to remain compliant with the ERC4626 requirements with regard to the preview functions.
For example, if calling `deposit(100, receiver)`, the caller should deposit exactly 100 underlying tokens, including fees, and the receiver should receive a number of shares that matches the value returned by `previewDeposit(100)`. Similarly, `previewMint` should account for the fees that the user will have to pay on top of share's cost.
As for the `Deposit` event, while this is less clear in the EIP spec itself, there seems to be consensus that it should include the number of assets paid for by the user, including the fees.
On the other hand, when withdrawing assets, the number given by the user should correspond to what he receives. Any fees should be added to the quote (in shares) performed by `previewWithdraw`.
The `Withdraw` event should include the number of shares the user burns (including fees) and the number of assets the user actually receives (after fees are deducted).
The consequence of this design is that both the `Deposit` and `Withdraw` events will describe two exchange rates. The spread between the "Buy-in" and the "Exit" prices correspond to the fees taken by the vault.
The following example describes how fees proportional to the deposited/withdrawn amount can be implemented:
```solidity
include::api:example$ERC4626Fees.sol[]
```

View File

@ -1,5 +1,7 @@
= ERC777
CAUTION: As of v4.9, OpenZeppelin's implementation of ERC-777 is deprecated and will be removed in the next major release.
Like xref:erc20.adoc[ERC20], ERC777 is a standard for xref:tokens.adoc#different-kinds-of-tokens[_fungible_ tokens], and is focused around allowing more complex interactions when trading tokens. More generally, it brings tokens and Ether closer together by providing the equivalent of a `msg.value` field, but for tokens.
The standard also brings multiple quality-of-life improvements, such as getting rid of the confusion around `decimals`, minting and burning with proper events, among others, but its killer feature is *receive hooks*. A hook is simply a function in a contract that is called when tokens are sent to it, meaning *accounts and contracts can react to receiving tokens*.

View File

@ -66,7 +66,7 @@ contract ModifiedAccessControl is AccessControl {
The `super.revokeRole` statement at the end will invoke ``AccessControl``'s original version of `revokeRole`, the same code that would've run if there were no overrides in place.
NOTE: As of v3.0.0, `view` functions are not `virtual` in OpenZeppelin, and therefore cannot be overridden. We're considering https://github.com/OpenZeppelin/openzeppelin-contracts/issues/2154[lifting this restriction] in an upcoming release. Let us know if this is something you care about!
NOTE: The same rule is implemented and extended in xref:api:access.adoc#AccessControlDefaultAdminRules[`AccessControlDefaultAdminRules`], an extension that also adds enforced security measures for the `DEFAULT_ADMIN_ROLE`.
[[using-hooks]]
== Using Hooks

View File

@ -20,7 +20,9 @@ The ERC20 extension to keep track of votes and vote delegation is one such case.
=== Governor & GovernorCompatibilityBravo
An OpenZeppelin Governor contract is by default not interface-compatible with GovernorAlpha or Bravo, since some of the functions are different or missing, although it shares all of the same events. However, its possible to opt in to full compatibility by inheriting from the GovernorCompatibilityBravo module. The contract will be cheaper to deploy and use without this module.
An OpenZeppelin Governor contract is by default not interface-compatible with Compound's GovernorAlpha or Bravo. Even though events are fully compatible, proposal lifecycle functions (creation, execution, etc.) have different signatures that are meant to optimize storage use. Other functions from GovernorAlpha are Bravo are likewise not available. Its possible to opt in to a higher level of compatibility by inheriting from the GovernorCompatibilityBravo module, which covers the proposal lifecycle functions such as `propose` and `execute`.
Note that even with the use of this module, there will still be differences in the way that `proposalId`s are calculated. Governor uses the hash of the proposal parameters with the purpose of keeping its data off-chain by event indexing, while the original Bravo implementation uses sequential `proposalId`s. Due to this and other differences, several of the functions from GovernorBravo are not included in the compatibility module.
=== GovernorTimelockControl & GovernorTimelockCompound
@ -119,13 +121,15 @@ contract MyToken is ERC20, ERC20Permit, ERC20Votes, ERC20Wrapper {
}
```
NOTE: Voting power could be determined in different ways: multiple ERC20 tokens, ERC721 tokens, sybil resistant identities, etc. All of these options are potentially supported by writing a custom Votes module for your Governor. The only other source of voting power available in OpenZeppelin Contracts currently is xref:api:token/ERC721.adoc#ERC721Votes[`ERC721Votes`].
NOTE: The only other source of voting power available in OpenZeppelin Contracts currently is xref:api:token/ERC721.adoc#ERC721Votes[`ERC721Votes`]. ERC721 tokens that don't provide this functionality can be wrapped into a voting tokens using a combination of xref:api:token/ERC721.adoc#ERC721Votes[`ERC721Votes`] and xref:api:token/ERC721Wrapper.adoc#ERC721Wrapper[`ERC721Wrapper`].
NOTE: The internal clock used by the token to store voting balances will dictate the operating mode of the Governor contract attached to it. By default, block numbers are used. Since v4.9, developers can override the xref:api:interfaces.adoc#IERC6372[IERC6372] clock to use timestamps instead of block numbers.
=== Governor
Initially, we will build a Governor without a timelock. The core logic is given by the Governor contract, but we still need to choose: 1) how voting power is determined, 2) how many votes are needed for quorum, 3) what options people have when casting a vote and how those votes are counted, and 4) what type of token should be used to vote. Each of these aspects is customizable by writing your own module, or more easily choosing one from OpenZeppelin Contracts.
For 1) we will use the GovernorVotes module, which hooks to an IVotes instance to determine the voting power of an account based on the token balance they hold when a proposal becomes active. This module requires as a constructor parameter the address of the token.
For 1) we will use the GovernorVotes module, which hooks to an IVotes instance to determine the voting power of an account based on the token balance they hold when a proposal becomes active. This module requires as a constructor parameter the address of the token. This module also discovers the clock mode (ERC6372) used by the token and applies it to the Governor.
For 2) we will use GovernorVotesQuorumFraction which works together with ERC20Votes to define quorum as a percentage of the total supply at the block a proposals voting power is retrieved. This requires a constructor parameter to set the percentage. Most Governors nowadays use 4%, so we will initialize the module with parameter 4 (this indicates the percentage, resulting in 4%).
@ -137,7 +141,7 @@ votingDelay: How long after a proposal is created should voting power be fixed.
votingPeriod: How long does a proposal remain open to votes.
These parameters are specified in number of blocks. Assuming block time of around 13.14 seconds, we will set votingDelay = 1 day = 6570 blocks, and votingPeriod = 1 week = 45992 blocks.
These parameters are specified in the unit defined in the token's clock. Assuming the token uses block numbers, and assuming block time of around 12 seconds, we will have set votingDelay = 1 day = 7200 blocks, and votingPeriod = 1 week = 50400 blocks.
We can optionally set a proposal threshold as well. This restricts proposal creation to accounts who have enough voting power.
@ -160,11 +164,11 @@ contract MyGovernor is Governor, GovernorCompatibilityBravo, GovernorVotes, Gove
{}
function votingDelay() public pure override returns (uint256) {
return 6575; // 1 day
return 7200; // 1 day
}
function votingPeriod() public pure override returns (uint256) {
return 46027; // 1 week
return 50400; // 1 week
}
function proposalThreshold() public pure override returns (uint256) {
@ -223,7 +227,6 @@ contract MyGovernor is Governor, GovernorCompatibilityBravo, GovernorVotes, Gove
return super.supportsInterface(interfaceId);
}
}
```
=== Timelock
@ -261,7 +264,7 @@ const grantAmount = ...;
const transferCalldata = token.interface.encodeFunctionData(transfer, [teamAddress, grantAmount]);
```
Now we are ready to call the propose function of the governor. Note that we dont pass in one array of actions, but instead three arrays corresponding to the list of targets, the list of values, and the list of calldatas. In this case its a single action, so its simple:
Now we are ready to call the propose function of the Governor. Note that we dont pass in one array of actions, but instead three arrays corresponding to the list of targets, the list of values, and the list of calldatas. In this case its a single action, so its simple:
```javascript
await governor.propose(
@ -284,9 +287,9 @@ image::tally-vote.png[Voting in Tally]
=== Execute the Proposal
Once the voting period is over, if quorum was reached (enough voting power participated) and the majority voted in favor, the proposal is considered successful and can proceed to be executed. This can also be done in Tally in the "Administration Panel" section of a project.
Once the voting period is over, if quorum was reached (enough voting power participated) and the majority voted in favor, the proposal is considered successful and can proceed to be executed. Once a proposal passes, it can be queued and executed from the same place you voted.
image::tally-admin.png[Administration Panel in Tally]
image::tally-exec.png[Administration Panel in Tally]
We will see now how to do this manually using Ethers.js.
@ -305,7 +308,7 @@ await governor.queue(
);
```
This will cause the governor to interact with the timelock contract and queue the actions for execution after the required delay.
This will cause the Governor to interact with the timelock contract and queue the actions for execution after the required delay.
After enough time has passed (according to the timelock parameters), the proposal can be executed. If there was no timelock to begin with, this step can be ran immediately after the proposal succeeds.
@ -319,3 +322,107 @@ await governor.execute(
```
Executing the proposal will transfer the ERC20 tokens to the chosen recipient. To wrap up: we set up a system where a treasury is controlled by the collective decision of the token holders of a project, and all actions are executed via proposals enforced by on-chain votes.
== Timestamp based governance
=== Motivation
It is sometimes difficult to deal with durations expressed in number of blocks because of inconsistent or unpredictable time between blocks. This is particularly true of some L2 networks where blocks are produced based on blockchain usage. Using number of blocks can also lead to the governance rules being affected by network upgrades that modify the expected time between blocks.
The difficulty of replacing block numbers with timestamps is that the Governor and the token must both use the same format when querying past votes. If a token is designed around block numbers, it is not possible for a Governor to reliably do timestamp based lookups.
Therefore, designing a timestamp based voting system starts with the token.
=== Token
Since v4.9, all voting contracts (including xref:api:token/ERC20.adoc#ERC20Votes[`ERC20Votes`] and xref:api:token/ERC721.adoc#ERC721Votes[`ERC721Votes`]) rely on xref:api:interfaces.adoc#IERC6372[IERC6372] for clock management. In order to change from operating with block numbers to operating with timestamps, all that is required is to override the `clock()` and `CLOCK_MODE()` functions.
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.2;
import "github.com/openzeppelin/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol";
import "github.com/openzeppelin/openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20Permit.sol";
import "github.com/openzeppelin/openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20Votes.sol";
contract MyToken is ERC20, ERC20Permit, ERC20Votes {
constructor() ERC20("MyToken", "MTK") ERC20Permit("MyToken") {}
// Overrides IERC6372 functions to make the token & governor timestamp-based
function clock() public view override returns (uint48) {
return uint48(block.timestamp);
}
function CLOCK_MODE() public pure override returns (string memory) {
return "mode=timestamp";
}
// The functions below are overrides required by Solidity.
function _afterTokenTransfer(address from, address to, uint256 amount)
internal
override(ERC20, ERC20Votes)
{
super._afterTokenTransfer(from, to, amount);
}
function _mint(address to, uint256 amount)
internal
override(ERC20, ERC20Votes)
{
super._mint(to, amount);
}
function _burn(address account, uint256 amount)
internal
override(ERC20, ERC20Votes)
{
super._burn(account, amount);
}
}
```
=== Governor
The Governor will automatically detect the clock mode used by the token and adapt to it. There is no need to override anything in the Governor contract. However, the clock mode does affect how some values are interpreted. It is therefore necessary to set the `votingDelay()` and `votingPeriod()` accordingly.
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.2;
import "@openzeppelin/contracts/governance/Governor.sol";
import "@openzeppelin/contracts/governance/compatibility/GovernorCompatibilityBravo.sol";
import "@openzeppelin/contracts/governance/extensions/GovernorVotes.sol";
import "@openzeppelin/contracts/governance/extensions/GovernorVotesQuorumFraction.sol";
import "@openzeppelin/contracts/governance/extensions/GovernorTimelockControl.sol";
contract MyGovernor is Governor, GovernorCompatibilityBravo, GovernorVotes, GovernorVotesQuorumFraction, GovernorTimelockControl {
constructor(IVotes _token, TimelockController _timelock)
Governor("MyGovernor")
GovernorVotes(_token)
GovernorVotesQuorumFraction(4)
GovernorTimelockControl(_timelock)
{}
function votingDelay() public pure virtual override returns (uint256) {
return 1 days;
}
function votingPeriod() public pure virtual override returns (uint256) {
return 1 weeks;
}
function proposalThreshold() public pure virtual override returns (uint256) {
return 0;
}
// ...
}
```
=== Disclaimer
Timestamp based voting is a recent feature that was formalized in EIP-6372 and EIP-5805, and introduced in v4.9. At the time this feature is released, governance tooling such as https://www.tally.xyz[Tally] does not support it yet. While support for timestamps should come soon, users can expect invalid reporting of deadlines & durations. This invalid reporting by offchain tools does not affect the onchain security and functionality of the governance contract.
Governors with timestamp support (v4.9 and above) are compatible with old tokens (before v4.9) and will operate in "block number" mode (which is the mode all old tokens operate on). On the other hand, old Governor instances (before v4.9) are not compatible with new tokens operating using timestamps. If you update your token code to use timestamps, make sure to also update your Governor code.