A time-boxed security review of the Portals protocol was done by pashov, with a focus on the security aspects of the application's implementation.
A smart contract security review can never verify the complete absence of vulnerabilities. This is a time, resource and expertise bound effort where I try to find as many vulnerabilities as possible. I can not guarantee 100% security after the review or even if the review will find any problems with your smart contracts. Subsequent security reviews, bug bounty programs and on-chain monitoring are strongly recommended.
Krum Pashov, or pashov, is an independent smart contract security researcher. Having found numerous security vulnerabilities in various protocols, he does his best to contribute to the blockchain ecosystem and its protocols by putting time and effort into security research & reviews. Reach out on Twitter @pashovkrum
Portals is a platform that aggregates multiple calls allowing for best-route swap, stake, LP etc mechanisms. It optimizes for highest USD output + least gas consumption. A user requests a quote from the Portals API by choosing input & output tokens, input amount and sender/recipient. A Smart Order Routing algorithm is used to find the best path for the swap or action. Example use case is adding liquidity to a Curve pool (swapping to the underlying token and calling add_liquidity
) or executing a folding strategy (borrow, swap, borrow etc). The protocol has a slippage tolerance mechanism implemented.
PortalsRouter
is the contract handling all approvals/allowances. The contracts make heavy usage of signed approvals & ERC20's permits.
The protocol does revenue sharing, where 50% of the fees go to the partner
or the front-end from which the on-chain orders come from. The partner
is only logged in an event and then the revenue sharing is executed off-chain, which is not trustless.
The protocol is non-custodial, meaning it doesn't hold funds in between any transaction calls. Only time it holds funds/fees is intra-transaction. This drastically narrows down the attack surface.
The PortalsMulticall
contract allows for arbitrary code execution by anyone.
- Router owner - can pause/unpause the
PortalsRouter
contract, set theMulticall
contract address and also receives recovered stuck tokens in the router - Multicall fees receiver - set by the back end off-chain (added in the
calls
array inaggregate
), receives some fee - Portal broadcaster - executes
SignedOrder
s by callingPortalsRouter
- Portal user - executes
Order
s by callingPortalsRouter
, givesPortalsRouter
spending allowance
Severity | Impact: High | Impact: Medium | Impact: Low |
---|---|---|---|
Likelihood: High | Critical | High | Medium |
Likelihood: Medium | High | Medium | Low |
Likelihood: Low | Medium | Low | Low |
Impact - the technical, economic and reputation damage of a successful attack
Likelihood - the chance that a particular vulnerability gets discovered and exploited
Severity - the overall criticality of the risk
review commit hash - d8be537304c8cd0bd3433b3aca5770d9375fc2ce
fixes review commit hash - c1c223697b1a796623162148e1fd821e109c4107
The following smart contracts were in scope of the audit:
portals/multicall/interface/**
portals/multicall/PortalsMulticall
portals/router/interface/**
portals/router/RouterBase
portals/router/PortalsRouter
The following number of issues were found, categorized by their severity:
- Critical & High: 0 issues
- Medium: 0 issues
- Low: 3 issues
ID | Title | Severity |
---|---|---|
[L-01] | Fees recipient can arbitrage slippage tolerance | Low |
[L-02] | The chainId is cached but might change |
Low |
[L-03] | The protocol is using a vulnerable library | Low |
The calls
array in PortalsMulticall::aggregate
are always expected to include a fee payment transaction, where fees from the swaps would be transferred to a protocol-controlled account. Also it is sometimes expected that a "sweep ETH" call is included in the end of the array, so that all leftover ETH is swept back to the original caller. The problem is that in this specific scenario, when the fees are sent before the sweep, the fees recipient can reenter the contract by calling transferEth
and transfer just enough ETH that the caller balance would still be at the slippage tolerance level.
This attack requires multiple conditions (Low likelihood) and is also limited to up to slippage tolerance values (Low to Medium impact), hence the Low severity, but it is still a possible attack vector. One possible solution is to add the nonReentrant
modifier to the transferEth
function, but this will disallow calling it as part of the aggregate
calls.
pashov: Acknowledged.
Caching the chainId
value (RouterBase
's constructor) is not a good practice as hard forks might change the chainId for a network. The better solution is to always check if the current block.chainid
is the same as the cached one and if not, to update it. Follow the approach in OpenZeppelin's EIP712 implementation or just inherit from the OpenZeppelin's EIP712 contract.
pashov: Fixed.
The 4.8.0 version of the OpenZeppelin library has security vulnerabilities that are listed here. While currently the vulnerable code is not used in the codebase, the library should be updated to contain the latest security patches.
pashov: Fixed.