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 all 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
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
1 change: 1 addition & 0 deletions contracts/Arbitrum_SpokePool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -83,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
2 changes: 2 additions & 0 deletions contracts/Ovm_SpokePool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ contract Ovm_SpokePool is SpokePool {
l1Gas = 5_000_000;
__SpokePool_init(_initialDepositId, _crossDomainAdmin, _hubPool, _wrappedNativeToken, _timerAddress);
messenger = Lib_PredeployAddresses.L2_CROSS_DOMAIN_MESSENGER;
//slither-disable-next-line missing-zero-check
l2Eth = _l2Eth;
}

Expand Down Expand Up @@ -147,6 +148,7 @@ contract Ovm_SpokePool is 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
3 changes: 3 additions & 0 deletions contracts/PolygonTokenBridger.sol
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,11 @@ contract PolygonTokenBridger is Lockable {
uint256 _l1ChainId,
uint256 _l2ChainId
) {
//slither-disable-next-line missing-zero-check
destination = _destination;
l1PolygonRegistry = _l1PolygonRegistry;
l1Weth = _l1Weth;
//slither-disable-next-line missing-zero-check
l2WrappedMatic = _l2WrappedMatic;
l1ChainId = _l1ChainId;
l2ChainId = _l2ChainId;
Expand Down Expand Up @@ -122,6 +124,7 @@ contract PolygonTokenBridger is Lockable {
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
5 changes: 5 additions & 0 deletions contracts/Polygon_SpokePool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ contract Polygon_SpokePool is IFxMessageProcessor, SpokePool {
callValidated = false;
__SpokePool_init(_initialDepositId, _crossDomainAdmin, _hubPool, _wmaticAddress, _timerAddress);
polygonTokenBridger = _polygonTokenBridger;
//slither-disable-next-line missing-zero-check
fxChild = _fxChild;
}

Expand All @@ -92,6 +93,7 @@ contract Polygon_SpokePool is IFxMessageProcessor, SpokePool {
* @param newFxChild New FxChild.
*/
function setFxChild(address newFxChild) public onlyAdmin nonReentrant {
//slither-disable-next-line missing-zero-check
fxChild = newFxChild;
emit SetFxChild(newFxChild);
}
Expand Down Expand Up @@ -127,8 +129,10 @@ contract Polygon_SpokePool is IFxMessageProcessor, SpokePool {
// This uses delegatecall to take the information in the message and process it as a function call on this contract.
/// This is a safe delegatecall because its made to address(this) so there is no risk of delegating to a
/// selfdestruct().
//slither-disable-start low-level-calls
/// @custom:oz-upgrades-unsafe-allow delegatecall
(bool success, ) = address(this).delegatecall(data);
//slither-disable-end low-level-calls
require(success, "delegatecall failed");
}

Expand Down Expand Up @@ -227,6 +231,7 @@ contract Polygon_SpokePool is IFxMessageProcessor, SpokePool {

function _wrap() internal {
uint256 balance = address(this).balance;
//slither-disable-next-line arbitrary-send-eth
if (balance > 0) wrappedNativeToken.deposit{ value: balance }();
}

Expand Down
20 changes: 13 additions & 7 deletions contracts/SpokePool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,11 @@ abstract contract SpokePool is
* to ensure that a small input range doesn't limit which indices this method is able to reach.
*/
function emergencyDeleteRootBundle(uint256 rootBundleId) public override onlyAdmin nonReentrant {
// Deleting a struct containing a mapping does not delete the mapping in Solidity, therefore the bitmap's
Copy link
Member Author

Choose a reason for hiding this comment

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

This warning was the most interesting in this PR IMO

Copy link
Contributor

@pxrl pxrl Feb 6, 2023

Choose a reason for hiding this comment

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

Agree - interesting find. It seems OK to suppress the warning:

  • In the event that a bad bundle makes it to the SpokePool and is subsequently deleted, rootBundle.relayerRefundRoot and rootBundle.slowRelayRoot will be zeroed.
  • Proof verification is one of the first things to occur in _executeRelayerRefundLeaf() and _executeSlowRelayLeaf().
  • Any immediate attempt to execute its leaves via executeRelayerRefundRoot() or executeSlowRelayLeaf() should revert on failed proof verification.
  • This array entry will, at some later time, be overwritten with different data, but the bitmap will indefinitely record that the old deleted leaves remain unclaimed. Any attempt to execute those would rely on collisions and should therefore still fail on proof verification. Wrong & irrelevant, as identified by Matt.

Additionally:

  • emergencyDeleteRootBundle() is an admin function, so this can't be initiated by any arbitrary caller. To end up here, a specific sequence of unlikely of events needs to occur. It seems highly improbable that a malicious third party could orchestrate that.

Copy link
Contributor

Choose a reason for hiding this comment

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

I agree with @pxrl. Just to add one point: we will never reuse this array slot (because deleting an element of an array does not actually reduce the array.length I believe, meaning the slot is still reserved), so the mapping will never be read or written to again, by definition, no? So we shouldn't need to worry about this at all right?

Copy link
Contributor

Choose a reason for hiding this comment

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

Just to add one point: we will never reuse this array slot (because deleting an element of an array does not actually reduce the array.length I believe, meaning the slot is still reserved), so the mapping will never be read or written to again, by definition, no? So we shouldn't need to worry about this at all right?

You're right - I'd understood that the array element was actually removed, but I can see that it's only zeroed. So my 4th point was garbage!

// data will still remain potentially leading to vulnerabilities down the line. The way around this would
// be to iterate through every key in the mapping and resetting the value to 0, but this seems expensive and
// would require a new list in storage to keep track of keys.
//slither-disable-next-line mapping-deletion
delete rootBundles[rootBundleId];
emit EmergencyDeleteRootBundle(rootBundleId);
}
Expand Down Expand Up @@ -332,11 +337,17 @@ abstract contract SpokePool is
// buffer of this contract's block time to allow for this variance.
// Note also that quoteTimestamp cannot be less than the buffer otherwise the following arithmetic can result
// in underflow. This isn't a problem as the deposit will revert, but the error might be unexpected for clients.

//slither-disable-next-line timestamp
require(
getCurrentTime() >= quoteTimestamp - depositQuoteTimeBuffer &&
getCurrentTime() <= quoteTimestamp + depositQuoteTimeBuffer,
"invalid quote time"
);

// Increment count of deposits so that deposit ID for this spoke pool is unique.
Copy link
Member Author

Choose a reason for hiding this comment

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

Move the storage variable modification to numberOfDeposits above the external call safeTransferFrom to align with CEI pattern

uint32 newDepositId = numberOfDeposits++;

// If the address of the origin token is a wrappedNativeToken contract and there is a msg.value with the
// transaction then the user is sending ETH. In this case, the ETH should be deposited to wrappedNativeToken.
if (originToken == address(wrappedNativeToken) && msg.value > 0) {
Expand All @@ -352,18 +363,12 @@ abstract contract SpokePool is
chainId(),
destinationChainId,
relayerFeePct,
numberOfDeposits,
newDepositId,
quoteTimestamp,
originToken,
recipient,
msg.sender
);

// Increment count of deposits so that deposit ID for this spoke pool is unique.
// @dev: Use pre-increment to save gas:
// i++ --> Load, Store, Add, Store
// ++i --> Load, Add, Store
++numberOfDeposits;
}

/**
Expand Down Expand Up @@ -798,6 +803,7 @@ abstract contract SpokePool is
IERC20Upgradeable(address(wrappedNativeToken)).safeTransfer(to, amount);
} else {
wrappedNativeToken.withdraw(amount);
//slither-disable-next-line arbitrary-send-eth
to.transfer(amount);
}
}
Expand Down
2 changes: 1 addition & 1 deletion contracts/interfaces/WETH9Interface.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@ interface WETH9Interface {

function balanceOf(address guy) external view returns (uint256 wad);

function transfer(address guy, uint256 wad) external;
function transfer(address guy, uint256 wad) external returns (bool);
Copy link
Member Author

Choose a reason for hiding this comment

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

This interface was incorrect and inconsistent with how we implement WETH9

}
7 changes: 7 additions & 0 deletions contracts/upgradeable/MultiCallerUpgradeable.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,24 @@ contract MultiCallerUpgradeable {

function multicall(bytes[] calldata data) external returns (bytes[] memory results) {
results = new bytes[](data.length);

//slither-disable-start calls-loop
for (uint256 i = 0; i < data.length; i++) {
// Typically, implementation contracts used in the upgradeable proxy pattern shouldn't call `delegatecall`
// because it could allow a malicious actor to call this implementation contract directly (rather than
// through a proxy contract) and then selfdestruct() the contract, thereby freezing the upgradeable
// proxy. However, since we're only delegatecall-ing into this contract, then we can consider this
// use of delegatecall() safe.

//slither-disable-start low-level-calls
/// @custom:oz-upgrades-unsafe-allow delegatecall
(bool success, bytes memory result) = address(this).delegatecall(data[i]);
//slither-disable-end low-level-calls

if (!success) {
// Next 5 lines from https://ethereum.stackexchange.com/a/83577
if (result.length < 68) revert();
//slither-disable-next-line assembly
assembly {
result := add(result, 0x04)
}
Expand All @@ -38,5 +44,6 @@ contract MultiCallerUpgradeable {

results[i] = result;
}
//slither-disable-end calls-loop
}
}
1 change: 1 addition & 0 deletions contracts/upgradeable/TestableUpgradeable.sol
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ abstract contract TestableUpgradeable is Initializable {
* Must be set to 0x0 for production environments that use live time.
*/
function __Testable_init(address _timerAddress) public onlyInitializing {
//slither-disable-next-line missing-zero-check
timerAddress = _timerAddress;
}

Expand Down