Skip to content

Latest commit

 

History

History
266 lines (186 loc) · 18 KB

2024-01-Decent.md

File metadata and controls

266 lines (186 loc) · 18 KB

Logo

Stage Topic
1 Overview
2 System Overview
3 Approach Taken-in Evaluating The Decent protocol
4 Codebase Explanation & Examples Scenarios of Intended Protocol Flow
5 Architecture Recommendations
6 Codebase Quality Analysis
7 Test analysis
8 Systemic & Centralization Risks
9 Conclusion

🛠️ Analysis - Decent protocol

Overview

Decent protocol is focused on cross-chain communication and transaction execution, from bridging assets between chains to minting NFTs and swapping tokens on different chains. Build with the help of LayerZero infrastructure and adopt seamless transaction execution with proper refunds in case of failure. The development team’s idea is to limit the complexity for the end user and ensure an enjoyable and error-free user experience.

The key contracts of Decent protocol for this audit are:

  • UTB: Contract serving as an entry point for users who want to bridge assets and execute transactions on another chain. It does token swaps, validates addresses and forwards calls to UTBExecutor. Another use-case is that it is called after tokens are bridged, executions start and end in this contract.
  • UTBExecutor: Contract that executes transactions on behalf of the users who are bridging their assets, and can execute payloads of data with either native (ETH) or non-native tokens.
  • StargateBridgeAdapter: Contract used as a middleman to initiate bridge transactions. It also can receive transactions and route them to the UTB contract for further processing. This contract will be deployed on all the chains and will forward calls to StargateRouter when bridging to make the LayerZero integration possible.
  • DecentBridgeAdapter: Contract used as a middleman to initiate bridge transactions. It also can receive transactions and route them to the UTB contract for further processing. This contract will be deployed on all the chains and will forward calls to DecentEthRouter when bridging to make the LayerZero integration possible.
  • DecentEthRouter: Contract that ensures LZ integration, it calls the dcnETH (token used for bridging) and ensures all the needed payload is forwarded. This contract holds dcnETH in the form of staked ETH and WETH and makes infrastructure payments in the form of token burning.
  • UniSwapper: Contract, implementing ISwapper exposing abstraction over the Uniswap functions for swapping assets. Used from UTB from post and pre-bridging swap when needed.

System Overview

Scope

  • UTB - core contract used as an entry point in the source chain and as a final point on the destination chain. Can be used to swap assets and execute arbitrary actions on the same chain or swap assets and execute arbitrary actions in one of the supported destination chains.
  • UTBExecutor - Contract that does all the external calls to an arbitrary target contract and refunds the user when there are unused funds.
  • UTBFeeCollector - Contract used to collect and store fees, admin-owned functions are making it possible to withdraw them to a chosen address.
  • BaseAdapter - Base contract, for verifying and setting the bridge executor (DecentBridgeExecutor.sol).
  • DecentBridgeAdapter - Contract serving as LayerZero bridge initiator, it holds information about supported chains and their adapter addresses and also overrides the payload appending information about the function that has to be called on the destination chain.
  • StargateBridgeAdapter - Contract serving as LayerZero bridge initiator, it holds information about supported chains and their adapter addresses and also overrides the payload appending information about the function that has to be called on the destination chain.
  • SwapParams - Helper contract, consisting of struct used to swap in Uniswap.
  • UniSwapper - Contract, used to swap assets, either to wrap/unwrap WETH or do a user-selected path swap.
  • DcntEth - Contract implementing OFTV2 from LayerZero used as a mediator token between 2 chains, will be deployed on every chain. Will be burned on the source and minted on the destination to represent the tokens that users bridged.
  • DecentEthRouter - Contract that glues the system with the LayerZero infrastructure, additionally modifying the payload to support LZ bridging.
  • DecentBridgeExecutor - Contract, that is called on the destination chain, call the target and further process the transaction execution.

Privileged Roles

  1. Owner - main role, inherited from Solmate Owned.sol .
  • sets the various protocol critical parameters, such as:
  • executor
  • dcntEth addresses
  • destination bridges
  • dcnEth routers
  • transaction signer
  • register new Swappers - UniSwapper, for example
  1. Executor - role, assigned to LayerZero contract, used to:
  • allow fallback function sgReceive in StargateBridgeAdapter
  • allow fallback function receiveFromBridge in DecentBridgeAdapter

Approach Taken-in Evaluating The Decent protocol

Stage Action Details Information
1 Compile and Run Test Installation Not that easy setup, a lot of chain RPCs has to be provided, slow run time of the tests.
2 Documentation review docs.decent.xyz Lack of documentation, only emphasising the front-end integration, without any clarity on what will happen on chain. Still, there is value for the auditors.
3 Contest details Audit Details Basic explanation of the contracts in scope. If there were any examples and flow charts it would be a much easier.
4 Diagramming Excalidraw Drawing diagrams through the entire process of codebase evaluation.
5 Test Suits Tests In this section, the scope and content of the tests of the project are analyzed.
6 Manual Code Review Scope Reviewed all the contracts in scope
7 Special focus on Areas of Concern Areas of concern Observing the known issues and bot report

Codebase Explanation & Examples Scenarios of Intended Protocol Flow

Due to the nature of the protocol that we audited and not defined scenarios, we can see how the system will perform for basic executions.

  • Bridging tokens between chains.
  • Bridging tokens between chains and executing arbitrary actions.
  • Swapping assets and executing arbitrary actions.

1. Bridging tokens between chains

On source chain:

Everyone can initiate bridging by pre-configuring the params needed to bridgeAndExecute function in UTB.

The process is simple as there are no on-chain complexities for the user. He has to pass the following args:

BridgeInstructions calldata instructions,

These are the instructions holding both pre and post bridge params, there user will specify the desired bridge which has to be used (Stargate or Decent), on which destination chain to be executed, what is the end target contract, to who will be funds returned in case of revert, what will be the payload (the actions that will be performed from UTBExecutor on destination chain) and additional arguments that will be different depending on the bridge adapter that is chosen:

If Stargate - additionalArgs will hold LayerZero transaction object.

If Decent - additionalArgs will hold only destination gas amount that should be added on top of the 100k provided as LayerZero cross-chain execution.

There are 2 more params passed to bridgeAndExecute but they are configured from the executor, who is assigned as signer in UTBFeeCollector:

FeeStructure calldata fees,
bytes calldata signature

Fees is a struct that contains the feeToken, amount of bridgeFee based on the bridge chosen and feeAmount, which is for the Decent’s team.

Signature is specially created by the signer and used for transaction execution validation when accruing fees, otherwise tx won’t be possible. Note about the signature:

If signer is not set everyone should be able to execute anything to be bridged because ecrecover will return address 0 for a wrong signature and signer will be address 0 initially.

After transaction is successfully signed, optional UniSwap swap is being executed, it is used for converting the input token to the bridgeToken.

Payload is wrapped in LayerZero payload that serves the purpose to know on which chain and which destination (DecentBridgeAdapter on the destination chain) to process the message.

Then router is called that transfers the bridgeToken to the LayerZero infrastructure, encodes the message with instructions how to be decoded when onOFTReceived is called and calls sendAndCall where the actual bridging happens. If you want to know what happens there you can check out the LayerZero docs.

On destination chain:

All begins with onOFTReceived in the DecentEthRouter, this function is used to decode the message and decide whether to execute the payload with executor if msgType is MT_ETH_TRANSFER_WITH_PAYLOAD or if msgType is MT_ETH_TRANSFER, received WETH is transferred to the _to address specified in the payload.

2. Bridging tokens between chains and executing arbitrary actions.

Execution on the source chain is similar with one caveat:

in the payload provided by user

struct BridgeInstructions {
    SwapInstructions postBridge;
}

there will be the functions and contracts that has to be called on the destination chain.

On destination chain:

Again onOFTReceived is called but now the payload is decoded as well:

/// @inheritdoc IOFTReceiverV2
    function onOFTReceived(uint16 _srcChainId, bytes calldata, uint64, bytes32, uint256 _amount, bytes memory _payload)
        external
        override
        onlyLzApp
    {
        (uint8 msgType, address _from, address _to, bool deliverEth) =
            abi.decode(_payload, (uint8, address, address, bool));

        bytes memory callPayload = "";

        if (msgType == MT_ETH_TRANSFER_WITH_PAYLOAD) {
**--> decoding the payload**
            (,,,, callPayload) = abi.decode(_payload, (uint8, address, address, bool, bytes));
        }

        emit ReceivedDecentEth(msgType, _srcChainId, _from, _to, _amount, callPayload);

        if (weth.balanceOf(address(this)) < _amount) {
            dcntEth.transfer(_to, _amount);
            return;
        }

        if (msgType == MT_ETH_TRANSFER) {
            if (!gasCurrencyIsEth || !deliverEth) {
                weth.transfer(_to, _amount);
            } else {
                weth.withdraw(_amount);
                payable(_to).transfer(_amount);
            }
        } else {
            weth.approve(address(executor), _amount);
            executor.execute(_from, _to, deliverEth, _amount, callPayload);
        }
    }

Then execution enters the last else condition on forwards the data to the DecentBridgeExecutor. On his side one of the two functions: _executeEth or _executeWeth is called and _to address which is the adapter containing receiveFromBridge function is called, tokens are pulled once again from router to bridge adapter and then swapAndExecute in UTB is called with user defined payload passed at the beginning:

struct BridgeInstructions {
    SwapInstructions postBridge;
}

In case failure occurs there is a refund address passed to the BridgeInstructions which is responsible for handling the potential refund.

Architecture Recommendations

Overall architecture is pretty complex and in some parts, it is hard to understand from the first pass what happens in the code. Contracts are built as both senders and receivers of cross-chain messages. In our opinion making the system more modular will benefit the developers and auditors in the future. There is a lot of attack surface, but the team did a great job by using the best practices and validating all the inputs in the system.

There are a lot of token transfers between the contracts, which can cause problems for certain ERC20 tokens. Care must be taken to avoid some unexpected problems with fees, reentrancy, and more known issues.

Note: consider using other ERC20 implementation as the Solmate doesn’t check for address(0) on both to and from params.

Also try to add extensive documentation what and why should happen, make sure to cover these topics:

  • What is the flow - draw diagrams, flowchart, etc?
  • What are the assets used for cross-chain execution?
  • What swaps are supported in the swappers?
  • What is the role of the contracts?
  • View functions and in what scenarios they should be used?
  • How the LayerZero infrastructure is configured?

By adding these improvements there is a possibility to find weak spots of the system and they can be fixed before something bad happens.

Codebase Quality Analysis

We consider the quality of the Decent complex but versatile with room for improvement.

There is a lot of extendability given to the users in the form to not limit their transaction executions. Various actions can be performed on the destination chain, which will definitely attract users. In our opinion, this protocol will be under heavy usage from MEV because of its atomic swaps in different AMMs and the way that the transactions are being constructed.

LayerZero integrating contracts are well written, but there are missing NatSpec comments explaining what the functions do.

A lot of work has to be put into the tests, as they do not cover complex scenarios, integration tests have to be written to ensure seamless execution.

Codebase Quality Categories Comments Score (1-10)
Code Maintainability and Reliability The codebase demonstrates moderate maintainability with well-structured functions and shared modularity across various contracts constructing all the calls in the system. Well-designed system and extendability. However, the concepts used are complex, but this can’t be mitigated due to the external integration and the nature of the protocol. 6
Code Comments There are not a lot of comments across the contracts, without explaining clearly the usage of the functions, and no NatSpec to showcase the params and return values. As a cross-chain protocol, the amount of complexity is decent, and adding extensive comments to the code will be beneficial for everyone. That way time for understanding will be reduced and onboarding new developers will be an easier process. 4
Documentation Mostly non-technical documentation is provided on Decent’s website and a brief overview of the smart contracts in scope is on the contest page.In this type of protocol having good documentation will remove the unclarities and will reduce significantly the questions asked from wardens. Documentation also will help with easier visualization of the flow and help the auditors while drawing their flowcharts. 4
Code Structure and Formatting The codebase is lacking some structure, following the Solidity Style Guide will make the code look cleaner as the current state looks a bit out of order.The formatting is good, the same style is followed by all the developers and there are no concerns. 8

Test analysis

The audit scope of the contracts to be audited is 75% which is low for such a protocol. The development team has to ensure all the simple and most complex scenarios are covered. That will give the confidence for a finished product. A good amount of integration tests has to be added also. Even fuzzing can be beneficial to ensure that multi-contract transactions are being properly executed.

Systemic & Centralization Risks

The Decent has only two roles in every contract and they are all with distinct responsibilities:

  • Owner
  • Executor

They expose some sort of centralization risk because they can freely withdraw the accumulated funds to an arbitrary address, but this is not concerning as there won’t be any losses for users, and fees, acquired before every contract execution, because they belong to the team.

A possible issue is to change one of the parameters across the contract, which can be a router, destination bridge, or dcntEth address, and send user funds directly to their addresses.

Team Findings

Severity Findings
High 2
Medium 3
Low/NC 3

Most of the findings discovered were due to logical errors and insufficient cross-contract validation.

New insights and learning from this audit

Learned about:

Conclusion

In general, the Decent project exhibits a standard and well-developed architecture we believe the team has done a good job regarding the code, but the identified risks need to be addressed, and measures should be implemented to protect the protocol from potential malicious use cases. Additionally, it is recommended to improve the tests as there is a need for proper testing covering complex test scenarios. It is also highly recommended that the team continues to invest in security measures such as mitigation reviews, audits, and bug bounty programs to maintain the security and reliability of the project.

Time spent:

40 hours