Platform: Code4rena
Start Date: 10/02/2022
End Date: 12/02/2022
Period: 3 days
Status: Completed
Pot Size: $30,000 USDC
Participants: 24
Reporter: liveactionllama
Judge: harleythedog
Id: 86
League: ETH
Auditor per page
All the Solidity files are included in the audit scope, expect the ones in the contracts/mocks
folder.
The preview version has been audited 3 times (at the end of 2021):
⚠️ We will "dispute" all the issues that were already surfaced in the previous audit => https://github.com/code-423n4/2021-11-nested-findings
This new version includes:
NestedFactory
refactoring (interface simplication + expanding the possibilities).A user can copy his own portfolio to reduce the fees, however a require statement won't fix this issue...
This problem cannot be corrected but only mitigated, since the user can use two different wallets. Currently the front-end doesn't allow to duplicate a portfolio with the same address.
The protocol is no fully compatible with deflationary/rebase tokens. In fact, you can add a deflationary/rebase token to your portfolio but it can lead to unpredictable behaviors (positive or negative).
We have chosen to manage the tokens with a fixed amount (the input) after considering several solutions.
So, how can we mitigate that ?
We're maintaining a list of all rebase tokens (source coingecko, which is well maintained) and prevent users from adding them to their portfolio on the platform.
npx hardhat coverage
Wardens! If you have any questions, please contact us!
If you want to access the beta version of Nested Finance, go to : https://app.nested.fi/.
It can help to better understand the protocol context.
Nested Finance is a decentralized protocol providing customizable financial products in the form of NFTs.
The platform allows users to put several digital assets, i.e. ERC20 tokens, inside an NFT (abbreviated as NestedNFT
).
<br/>
Each NestedNFT is backed by underlying assets:
The main idea is to allow adding modules (operators) to interact with new protocols and enable new assets, without re-deploying.
The tokens are stored on a self-custodian smart contract.
At the end of the creation process, the user receives the NFT which allows to control all underlying assets of the portfolio. Furthermore, we allow users to copy other users NestedNFTs. The creator of the initial NestedNFT earns royalties.
Name | LOC | Purpose |
---|---|---|
NestedFactory | 404 | Entry point to the protocol. Holds the business logic. Responsible for interactions with operators (submit orders). |
NestedAsset | 66 | Collection of ERC721 tokens. Called NestedNFT across the codebase. |
NestedReserve | 17 | Holds funds for the user. Transferred from the NestedFactory. |
NestedRecords | 110 | Tracks underlying assets of NestedNFTs. (Amount, NestedReserve). |
FeeSplitter | 188 | Receives payments in ERC20 tokens from the factory when fees are sent. Allows each party to claim the amount they are due. |
The contracts NestedAsset
, NestedReserve
, and NestedRecords
are whitelisting multiple factories (to create NFTs, update records, withdraw from reserve,...).
However, we are also using the TransparentUpgradeableProxy for NestedFactory
. Then, the users doesn't have to approve multiple times.
We have kept both mechanisms to get the best flexibility.
The users can lock their NFTs until a certain date (timestamp) by calling updateLockTimestamp
. This feature allows the "hold by design".
NestedFactory
is the main smart contract, but it can't work without the Operators.
As mentioned in the introduction, we designed the protocol to be modular. We want to be able to interact with any protocol in exchange for an ERC20 token.
So, we had to deal with two issues :
NestedFactory
contract?Our solution is called the "Operator"... A new interaction is a new operator and can be added on the fly.
They kind of work like libraries, but since we don't want to redeploy the factory,
they are contracts that are called via delegatecall
and referenced by the OperatorResolver
.
An operator allows performing a precise action, like "swap my token A for a token B" with a specific function, but the operator/interface will change depending on the action/context. To interact with new operators on the fly, we must expose new interfaces to the Factory.
The OperatorResolver
will whitelist all the Operator (address
) with the selectors (bytes4
) since we can't trust the caller to provide these informations.
struct Operator { address implementation; bytes4 selector; }
The caller will send the (imported) bytes32
name of the Operator/Function, for example "ZeroEx::performSwap".
The OperatorResolver
will return the address
+ selector
if the call is whitelisted and revert if not.
Since the operators are called via delegatecall
: how can we store/retrieve useful data?
<br>In fact, we cannot trust the Factory to provide all the data, like the address of the protocol. It must be stored and managed by the owner.
When deploying an operator, it will also deploy the storage contract and transfer the ownership to msgSender()
.
Name | LOC | Purpose |
---|---|---|
OperatorResolver | 59 | Allows the factory to identify which operator to interact with. |
MixinOperatorResolver | 67 | Abstract contract to load authorized operators in cache (instead of calling OperatorResolver ). |
ZeroExOperator | 33 | Performs token swaps through 0x (read more). |
ZeroExStorage | 11 | ZeroExOperator storage contract. Must store the 0x swapTarget . |
FlatOperator | 19 | Handles deposits and withdraws. No interaction with any third parties (read more). |
More operators will be added. e.g. CurveOperator or SynthetixOperator
The NestedFactory
is using the operators to interact with other protocols. The call from the Factory to an Operator is an "Order".
An Order has several information:
struct Order { bytes32 operator; address token; bytes callData; }
It help us to make one interaction, but we want to make multiple interactions. For example, to create a portfolio with multiple tokens, we need to "batch" these orders.
There are two types of "Batched Orders" processed by the Factory to create or edit Portfolios :
struct BatchedInputOrders { IERC20 inputToken; uint256 amount; Order[] orders; bool fromReserve; }
struct BatchedOutputOrders { IERC20 outputToken; uint256[] amounts; Order[] orders; bool toReserve; }
Some functions of the protocol require admin rights (onlyOwner
).
The contracts are owned by the TimelockController contract from OpenZeppelin, set with a 7-days delay. This ensures the community has time to review any changes made to the protocol.
The owner of the TimelockController is a three-party multisignature wallet.
During the next phase of the protocol, the ownership will be transferred to a fully decentralized DAO.
yarn install
.env.example
to a new file .env
and insert a dummy mnemonic and a mainnet api keyStart a local blockchain
yarn run
Start a hardhat console
yarn console
Compile
yarn compile
Generate typechain files
yarn typechain
Run tests
yarn test