Open code423n4 opened 2 years ago
Thank you for your report. It is very helpful. ありがとうございます!
Your solution looks good. We will change the code to use initializable for constructor.
Since we don't use the hardhat plugin in the actual deployment, we din't use it in the test code. By the way, if you know how to deploy in a secure way, I would appreciate it if you could us me know.
We have compliant USDC, so the code is looks like. However, 'functionDelegateCall()' was deleted from OZ upgradable contract, so we will check it.
We didn't include 'whenNotPaused()' for whitelist because it doesn't have a significant impact on the contract itself.
We agree with your advice.
Codebase Impressions & Summary
Overall, code quality for the JPYC contracts is very high. Supporting documentation provided adequate information on design choices made, such as why the UUPS proxy was chosen over the transparent proxy pattern.
The test suite could be easily run, and are rather comprehensive. One thing that stood out and that I’m impressed with was a test checklist. すばらしい! Test coverage was close to 100% with the following functions / branches missed (not significant IMO):
EIP712Domain._domainSeparatorV4()
I could be mistaken, but I would like to mention that while the coverage tool highlights that zero inputs for FiatTokenV2’s initializer weren’t tested, they actually are in the FiatTokenV2_proxy test file.
The findings I made revolved around the upgradeability aspect of the contracts. I also made recommendations on adding / removing functionality when the contract is paused.
Low Severity Findings
L01: Add constructor initializer in implementation contracts
Description
As per OpenZeppelin’s (OZ) recommendation, “The guidelines are now to make it impossible for anyone to run
initialize
on an implementation contract, by adding an empty constructor with theinitializer
modifier. So the implementation contract gets initialized automatically upon deployment.”Note that this behaviour is also incorporated the OZ Wizard since the UUPS vulnerability discovery: “Additionally, we modified the code generated by the Wizard 19 to include a constructor that automatically initializes the implementation when deployed.”
Furthermore, this thwarts any attempts to frontrun the initialization tx of the implementation contract.
Incorporating this change would require inheriting the
Initializable
contract instead of having an explicitinitialized
variable.Recommended Mitigation Steps
FiatTokenV1
,FiatTokenV2
and subsequent implementation contracts should inherit OZ’sInitializable
contract and have the following constructor method:L02: Use OZ upgrades (hardhat) plugin to handle proxy deployments and upgrades
Description
The project manually deploys and manages their own proxy contract and upgrades. I strongly recommend that the team use the upgrades plugin from OpenZeppelin instead, because it provides an important feature of validating that the incoming implementations are upgrade safe.
I note that the plugin is part of
package.json
and was imported into hardhat config file, but am puzzled why it wasn’t used (at least in tests).Recommended Mitigation Steps
Strongly consider using the OZ upgrades plugin to manage deployments. More information about its usage can be found in their documentation and UUPS Proxy guide.
An example is provided below:
L03: Contracts are not using their OZ upgradeable counterparts
Tools Used
Diffchecker
Description
The non-upgradeable standard version of OpenZeppelin’s library, such as
Ownable
,Pausable
,Address
,Context
,SafeERC20
,ERC1967Upgrade
etc, are inherited / used by both the proxy and the implementation contracts.As a result, when attempting to use the upgrades plugin mentioned, the following errors are raised:
Having reviewed these errors, none had any adversarial impact:
totalSupply_
andpaused
are explictly assigned the default values0
andfalse
_transferOwnership()
in the initializer, thus transferring ownership tonewOwner
regardless of who the current owner isAddress
'sdelegatecall
is only used by theERC1967Upgrade
contract. Comparing both theAddress
andERC1967Upgrade
contracts against their upgradeable counterparts show similar behaviour (differences are some refactoring done to shift the delegatecall into theERC1967Upgrade
contract).Nevertheless, it would be safer to use the upgradeable versions of the library contracts to avoid unexpected behaviour.
Recommended Mitigation Steps
Where applicable, use the contracts from
@openzeppelin/contracts-upgradeable
instead of@openzeppelin/contracts
.L04: FiatTokenV1 / V2: Remove
whenNotPaused
modifier fromcancelAuthorization()
anddecreaseAllowance()
functionsLine References
https://github.com/code-423n4/2022-02-jpyc/blob/main/contracts/v1/FiatTokenV1.sol#L409-L414
https://github.com/code-423n4/2022-02-jpyc/blob/main/contracts/v1/FiatTokenV1.sol#L535-L541
https://github.com/code-423n4/2022-02-jpyc/blob/main/contracts/v2/FiatTokenV2.sol#L420-L425
https://github.com/code-423n4/2022-02-jpyc/blob/main/contracts/v2/FiatTokenV2.sol#L558-L564
Description
Just like how
removeMinter()
doesn’t have thewhenNotPaused
modifier, it would be very beneficial useful (eg. when a hack / rug pull occurs) to allow users to revoke allowances and cancel authorizations whilst having transfers paused.Recommended Mitigation Steps
Remove the
whenNotPaused
modifiers for thecancelAuthorization()
anddecreaseAllowance()
functions.L05: FiatTokenV2:
whitelist()
should be unusable if contract is pausedLine References
https://github.com/code-423n4/2022-02-jpyc/blob/main/contracts/v2/FiatTokenV2.sol#L645
Description
Should the contract be paused, it would be safer to prevent additional addresses from being whitelisted.
Recommended Mitigation Steps
Add the
whenNotPaused
modifier for thewhitelist()
function.L06: Incorrect versioning of
FiatTokenV2
Line References
https://github.com/code-423n4/2022-02-jpyc/blob/main/contracts/v2/FiatTokenV2.sol#L111
https://github.com/code-423n4/2022-02-jpyc/blob/main/contracts/v2/FiatTokenV2.sol#L114
Description
Since V2 is an upgrade of V1, its versioning should be updated to reflect the upgrade as well. It is important for the correct version to be reflected since it also part of the EIP712 data to be signed, which is used by EIP2612 and EIP3009 for fungible asset transfer authorizations. Authorizations given for outdated versions should rightfully be made invalid.
Recommended Mitigation Steps
Non-Critical Findings
NC01: Bump OZ packages to
^4.5.0
.Line Reference
https://github.com/code-423n4/2022-02-jpyc#about-soliditys-version
https://github.com/OpenZeppelin/openzeppelin-contracts/commit/e192fac2769386b7d4b61a3541073ab47bb7723a
Description
The section referenced referred to a change in the
UUPSUpgradeable
andERC1967Upgrade
contracts that are only included in the latest package version of the OZ npm packages . However, the version specified inpackage-lock.json
is4.4.1
, which does not include this change (and hence I assume was manually imported).I can verify that the installed version is
4.4.1
by executing the following commands:Recommended Mitigation Steps
Update the versions of
@openzeppelin/contracts
and@openzeppelin/contracts-upgradeable
to be the latest inpackage.json
. I also recommend double checking the versions of other dependencies as a precaution, as they may include important bug fixes.