Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

improve: Make changes after running Slither on SpokePool contracts #229

Merged
merged 30 commits into from
Feb 16, 2023
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
b9a38f8
[WIP] feat: Make SpokePool's upgradeable
nicholaspai Jan 4, 2023
d409fca
commit
nicholaspai Jan 4, 2023
034e801
Delete unknown-31337.json
nicholaspai Jan 4, 2023
f4185df
Update deployments.json
nicholaspai Jan 4, 2023
6db44cd
Update SpokePool.sol
nicholaspai Jan 5, 2023
38b3adf
Make implementation UUPS compatible
nicholaspai Jan 5, 2023
cfdf07e
Merge branch 'master' of https://github.com/across-protocol/contracts…
nicholaspai Jan 5, 2023
54e97c0
Fix test
nicholaspai Jan 10, 2023
681a192
Update SpokePool.sol
nicholaspai Jan 19, 2023
0ee774e
Merge branch 'master' of https://github.com/across-protocol/contracts…
nicholaspai Jan 24, 2023
98b272b
Update SpokePool.Admin.ts
nicholaspai Jan 24, 2023
dd41c7b
No longer require unsafeAllow: delegatecall when deploying proxy beca…
nicholaspai Jan 24, 2023
77b449a
Fix gas tests
nicholaspai Jan 24, 2023
c4c4771
Merge branch 'master' of https://github.com/across-protocol/contracts…
nicholaspai Jan 31, 2023
7e2b7ed
Use OZ lockable
nicholaspai Jan 31, 2023
ba9e88f
specify revert reasons
nicholaspai Jan 31, 2023
8adde77
Update contracts/SpokePool.sol
nicholaspai Jan 31, 2023
6a9f509
add 50 32byte slots to base SpokePool
nicholaspai Jan 31, 2023
2932946
add __gap to ovm-spokepool
nicholaspai Feb 2, 2023
c689fa2
__gap private
nicholaspai Feb 2, 2023
ccec16e
Merge branch 'master' of https://github.com/across-protocol/contracts…
nicholaspai Feb 2, 2023
c0bb1b1
Update SpokePool.sol
nicholaspai Feb 2, 2023
b8fb099
Update Ovm_SpokePool.sol
nicholaspai Feb 2, 2023
22f5fbe
merge
nicholaspai Feb 3, 2023
cb54ea7
improve: Make changes after running Slither on SpokePool contracts
nicholaspai Feb 4, 2023
5d13900
Update README.md
nicholaspai Feb 4, 2023
2ab13f6
remove zero checks
nicholaspai Feb 8, 2023
c36e9d8
Merge branch 'master' of https://github.com/across-protocol/contracts…
nicholaspai Feb 13, 2023
57855d1
Update EIP712CrossChainUpgradeable.sol
nicholaspai Feb 13, 2023
bc58c17
Update PolygonTokenBridger.sol
nicholaspai Feb 16, 2023
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,8 @@ artifacts
cache-zk
artifacts-zk

# Upgradeability files
.openzeppelin

# IDE
.idea
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,25 @@ NODE_URL_1=https://mainnet.infura.com/xxx yarn hardhat deploy --tags HubPool --n
ETHERSCAN_API_KEY=XXX yarn hardhat etherscan-verify --network mainnet --license AGPL-3.0 --force-license --solc-input
```

## Slither

[Slither](https://github.com/crytic/slither) is a Solidity static analysis framework written in Python 3. It runs a
suite of vulnerability detectors, prints visual information about contract details, and provides an API to easily write
custom analyses. Slither enables developers to find vulnerabilities, enhance their code comprehension, and quickly
prototype custom analyses.

Spire-Contracts has been analyzed using `Slither@0.9.2` and no major bugs was found. To rerun the analytics, run:

```sh
slither contracts/SpokePool.sol
\ --solc-remaps @=node_modules/@
\ --solc-args "--optimize --optimize-runs 1000000"
\ --filter-paths "node_modules"
\ --exclude naming-convention
Comment on lines +54 to +58
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we make a shortcut for this via package.json?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm i'm having trouble getting this to work from an npm run will look into it further

```

You can replace `SpokePool.sol` with the specific contract you want to analyze.

## ZK Sync Adapter

These are special instructions for compiling and deploying contracts on `zksync`. The compile command will create `artifacts-zk` and `cache-zk` directories.
Expand Down
10 changes: 6 additions & 4 deletions contracts/Arbitrum_SpokePool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,17 @@ contract Arbitrum_SpokePool is SpokePool {
* @param _crossDomainAdmin Cross domain admin to set. Can be changed by admin.
* @param _hubPool Hub pool address to set. Can be changed by admin.
* @param _wethAddress Weth address for this network to set.
* @param timerAddress Timer address to set.
* @param _timerAddress Timer address to set.
*/
constructor(
function initialize(
uint32 _initialDepositId,
address _l2GatewayRouter,
address _crossDomainAdmin,
address _hubPool,
address _wethAddress,
address timerAddress
) SpokePool(_initialDepositId, _crossDomainAdmin, _hubPool, _wethAddress, timerAddress) {
address _timerAddress
) public initializer {
__SpokePool_init(_initialDepositId, _crossDomainAdmin, _hubPool, _wethAddress, _timerAddress);
_setL2GatewayRouter(_l2GatewayRouter);
}

Expand Down Expand Up @@ -82,6 +83,7 @@ contract Arbitrum_SpokePool is SpokePool {
// Check that the Ethereum counterpart of the L2 token is stored on this contract.
address ethereumTokenToBridge = whitelistedTokens[relayerRefundLeaf.l2TokenAddress];
require(ethereumTokenToBridge != address(0), "Uninitialized mainnet token");
//slither-disable-next-line unused-return
StandardBridgeLike(l2GatewayRouter).outboundTransfer(
ethereumTokenToBridge, // _l1Token. Address of the L1 token to bridge over.
hubPool, // _to. Withdraw, over the bridge, to the l1 hub pool contract.
Expand Down
16 changes: 8 additions & 8 deletions contracts/Boba_SpokePool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,21 @@ contract Boba_SpokePool is Ovm_SpokePool {
* relay hash collisions.
* @param _crossDomainAdmin Cross domain admin to set. Can be changed by admin.
* @param _hubPool Hub pool address to set. Can be changed by admin.
* @param timerAddress Timer address to set.
* @param _timerAddress Timer address to set.
*/
constructor(
function initialize(
uint32 _initialDepositId,
address _crossDomainAdmin,
address _hubPool,
address timerAddress
)
Ovm_SpokePool(
address _timerAddress
) public initializer {
__OvmSpokePool_init(
_initialDepositId,
_crossDomainAdmin,
_hubPool,
0x4200000000000000000000000000000000000006,
0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000,
timerAddress
)
{}
_timerAddress
);
}
}
21 changes: 11 additions & 10 deletions contracts/Ethereum_SpokePool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,13 @@
pragma solidity ^0.8.0;

import "./SpokePool.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";

/**
* @notice Ethereum L1 specific SpokePool. Used on Ethereum L1 to facilitate L2->L1 transfers.
*/
contract Ethereum_SpokePool is SpokePool, Ownable {
using SafeERC20 for IERC20;
contract Ethereum_SpokePool is SpokePool, OwnableUpgradeable {
using SafeERC20Upgradeable for IERC20Upgradeable;

/**
* @notice Construct the Ethereum SpokePool.
Expand All @@ -19,21 +17,24 @@ contract Ethereum_SpokePool is SpokePool, Ownable {
* relay hash collisions.
* @param _hubPool Hub pool address to set. Can be changed by admin.
* @param _wethAddress Weth address for this network to set.
* @param timerAddress Timer address to set.
* @param _timerAddress Timer address to set.
*/
constructor(
function initialize(
uint32 _initialDepositId,
address _hubPool,
address _wethAddress,
address timerAddress
) SpokePool(_initialDepositId, _hubPool, _hubPool, _wethAddress, timerAddress) {}
address _timerAddress
) public initializer {
__Ownable_init();
__SpokePool_init(_initialDepositId, _hubPool, _hubPool, _wethAddress, _timerAddress);
}

/**************************************
* INTERNAL FUNCTIONS *
**************************************/

function _bridgeTokensToHubPool(RelayerRefundLeaf memory relayerRefundLeaf) internal override {
IERC20(relayerRefundLeaf.l2TokenAddress).safeTransfer(hubPool, relayerRefundLeaf.amountToReturn);
IERC20Upgradeable(relayerRefundLeaf.l2TokenAddress).safeTransfer(hubPool, relayerRefundLeaf.amountToReturn);
}

// The SpokePool deployed to the same network as the HubPool must be owned by the HubPool.
Expand Down
16 changes: 8 additions & 8 deletions contracts/Optimism_SpokePool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,21 @@ contract Optimism_SpokePool is Ovm_SpokePool {
* relay hash collisions.
* @param _crossDomainAdmin Cross domain admin to set. Can be changed by admin.
* @param _hubPool Hub pool address to set. Can be changed by admin.
* @param timerAddress Timer address to set.
* @param _timerAddress Timer address to set.
*/
constructor(
function initialize(
uint32 _initialDepositId,
address _crossDomainAdmin,
address _hubPool,
address timerAddress
)
Ovm_SpokePool(
address _timerAddress
) public initializer {
__OvmSpokePool_init(
_initialDepositId,
_crossDomainAdmin,
_hubPool,
Lib_PredeployAddresses.OVM_ETH,
0x4200000000000000000000000000000000000006,
timerAddress
)
{}
_timerAddress
);
}
}
39 changes: 27 additions & 12 deletions contracts/Ovm_SpokePool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pragma solidity ^0.8.0;
import "./SpokePool.sol";
import "./interfaces/WETH9Interface.sol";

import "@eth-optimism/contracts/libraries/bridge/CrossDomainEnabled.sol";
import "@openzeppelin/contracts-upgradeable/crosschain/optimism/LibOptimismUpgradeable.sol";
import "@eth-optimism/contracts/libraries/constants/Lib_PredeployAddresses.sol";
import "@eth-optimism/contracts/L2/messaging/IL2ERC20Bridge.sol";

Expand All @@ -16,13 +16,21 @@ interface SynthetixBridgeToBase {
/**
* @notice OVM specific SpokePool. Uses OVM cross-domain-enabled logic to implement admin only access to functions. * Optimism and Boba each implement this spoke pool and set their chain specific contract addresses for l2Eth and l2Weth.
*/
contract Ovm_SpokePool is CrossDomainEnabled, SpokePool {
contract Ovm_SpokePool is SpokePool {
// "l1Gas" parameter used in call to bridge tokens from this contract back to L1 via IL2ERC20Bridge. Currently
// unused by bridge but included for future compatibility.
uint32 public l1Gas = 5_000_000;
uint32 public l1Gas;

// ETH is an ERC20 on OVM.
address public immutable l2Eth;
address public l2Eth;

// Address of the Optimism L2 messenger.
address public messenger;

// Reserve storage slots for future versions of this base contract to add state variables without
// affecting the storage layout of child contracts. Decrement the size of __gap whenever state variables
// are added.
uint256[1000] private __gap;

// Stores alternative token bridges to use for L2 tokens that don't go over the standard bridge. This is needed
// to support non-standard ERC20 tokens on Optimism, such as DIA and SNX which both use custom bridges.
Expand All @@ -38,19 +46,20 @@ contract Ovm_SpokePool is CrossDomainEnabled, SpokePool {
* relay hash collisions.
* @param _crossDomainAdmin Cross domain admin to set. Can be changed by admin.
* @param _hubPool Hub pool address to set. Can be changed by admin.
* @param timerAddress Timer address to set.
* @param _timerAddress Timer address to set.
*/
constructor(
function __OvmSpokePool_init(
uint32 _initialDepositId,
address _crossDomainAdmin,
address _hubPool,
address _l2Eth,
address _wrappedNativeToken,
address timerAddress
)
CrossDomainEnabled(Lib_PredeployAddresses.L2_CROSS_DOMAIN_MESSENGER)
SpokePool(_initialDepositId, _crossDomainAdmin, _hubPool, _wrappedNativeToken, timerAddress)
{
address _timerAddress
) public onlyInitializing {
l1Gas = 5_000_000;
__SpokePool_init(_initialDepositId, _crossDomainAdmin, _hubPool, _wrappedNativeToken, _timerAddress);
messenger = Lib_PredeployAddresses.L2_CROSS_DOMAIN_MESSENGER;
require(_l2Eth != address(0), "Invalid L2 ETH address");
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: Slither recommends we add these zero address checks. It does add a fixed gas cost to construction but adds some safety. Thoughts?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure about the value of these checks. 0 is only one possible invalid address, but there are ~infinitely many other invalid addresses as well. The only way we can really be confident that the address is set correctly is via post-deployment verification.

I'm not firmly against it or anything, it just seems a bit redundant and might give a false sense of security.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think I agree with your comment and will remove these zero address checks

l2Eth = _l2Eth;
}

Expand Down Expand Up @@ -139,6 +148,7 @@ contract Ovm_SpokePool is CrossDomainEnabled, SpokePool {
// this logic inside a fallback method that executes when this contract receives ETH because ETH is an ERC20
// on the OVM.
function _depositEthToWeth() internal {
//slither-disable-next-line arbitrary-send-eth
if (address(this).balance > 0) wrappedNativeToken.deposit{ value: address(this).balance }();
}

Expand Down Expand Up @@ -172,5 +182,10 @@ contract Ovm_SpokePool is CrossDomainEnabled, SpokePool {
}

// Apply OVM-specific transformation to cross domain admin address on L1.
function _requireAdminSender() internal override onlyFromCrossDomainAccount(crossDomainAdmin) {}
function _requireAdminSender() internal view override {
require(
LibOptimismUpgradeable.crossChainSender(messenger) == crossDomainAdmin,
"OVM_XCHAIN: wrong sender of cross-domain message"
);
}
}
21 changes: 12 additions & 9 deletions contracts/PolygonTokenBridger.sol
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.0;

import "./Lockable.sol";
import "./interfaces/WETH9Interface.sol";

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

// Polygon Registry contract that stores their addresses.
interface PolygonRegistry {
Expand All @@ -18,7 +18,7 @@ interface PolygonERC20Predicate {
}

// ERC20s (on polygon) compatible with polygon's bridge have a withdraw method.
interface PolygonIERC20 is IERC20 {
interface PolygonIERC20Upgradeable is IERC20Upgradeable {
function withdraw(uint256 amount) external;
}

Expand All @@ -38,9 +38,9 @@ interface MaticToken {
* This ultimately allows create2 to generate deterministic addresses that don't depend on the transaction count of the
* sender.
*/
contract PolygonTokenBridger is Lockable {
using SafeERC20 for PolygonIERC20;
using SafeERC20 for IERC20;
contract PolygonTokenBridger is ReentrancyGuard {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Slither had some complaints about Lockable but i figured this was a good time to use OZ's ReentrancyGuard instead

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this contract will be in scope for the audit. Are we okay making this change?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point, removed

using SafeERC20Upgradeable for PolygonIERC20Upgradeable;
using SafeERC20Upgradeable for IERC20Upgradeable;

// Gas token for Polygon.
MaticToken public constant maticToken = MaticToken(0x0000000000000000000000000000000000001010);
Expand Down Expand Up @@ -90,6 +90,8 @@ contract PolygonTokenBridger is Lockable {
uint256 _l1ChainId,
uint256 _l2ChainId
) {
require(_destination != address(0), "invalid dest address");
require(_l2WrappedMatic != address(0), "invalid wmatic address");
destination = _destination;
l1PolygonRegistry = _l1PolygonRegistry;
l1Weth = _l1Weth;
Expand All @@ -104,7 +106,7 @@ contract PolygonTokenBridger is Lockable {
* @param token Token to bridge.
* @param amount Amount to bridge.
*/
function send(PolygonIERC20 token, uint256 amount) public nonReentrant onlyChainId(l2ChainId) {
function send(PolygonIERC20Upgradeable token, uint256 amount) public nonReentrant onlyChainId(l2ChainId) {
token.safeTransferFrom(msg.sender, address(this), amount);

// In the wMatic case, this unwraps. For other ERC20s, this is the burn/send action.
Expand All @@ -119,9 +121,10 @@ contract PolygonTokenBridger is Lockable {
* @notice Called by someone to send tokens to the destination, which should be set to the HubPool.
* @param token Token to send to destination.
*/
function retrieve(IERC20 token) public nonReentrant onlyChainId(l1ChainId) {
function retrieve(IERC20Upgradeable token) public nonReentrant onlyChainId(l1ChainId) {
if (address(token) == address(l1Weth)) {
// For WETH, there is a pre-deposit step to ensure any ETH that has been sent to the contract is captured.
//slither-disable-next-line arbitrary-send-eth
l1Weth.deposit{ value: address(this).balance }();
}
token.safeTransfer(destination, token.balanceOf(address(this)));
Expand Down
Loading