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 toStargateRouter
when bridging to make theLayerZero
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 toDecentEthRouter
when bridging to make theLayerZero
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 holdsdcnETH
in the form of stakedETH
andWETH
and makes infrastructure payments in the form of token burning. - UniSwapper: Contract, implementing
ISwapper
exposing abstraction over theUniswap
functions for swapping assets. Used fromUTB
from post and pre-bridging swap when needed.
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 asLayerZero
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 asLayerZero
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 inUniswap
.UniSwapper
- Contract, used to swap assets, either to wrap/unwrapWETH
or do a user-selected path swap.DcntEth
- Contract implementingOFTV2
fromLayerZero
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 theLayerZero
infrastructure, additionally modifying the payload to supportLZ
bridging.DecentBridgeExecutor
- Contract, that is called on the destination chain, call the target and further process the transaction execution.
Owner
- main role, inherited fromSolmate
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
Executor
- role, assigned toLayerZero
contract, used to:
- allow fallback function
sgReceive
inStargateBridgeAdapter
- allow fallback function
receiveFromBridge
inDecentBridgeAdapter
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 |
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.
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 returnaddress 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.
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.
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.
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 |
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.
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.
Severity | Findings |
---|---|
High | 2 |
Medium | 3 |
Low/NC | 3 |
Most of the findings discovered were due to logical errors and insufficient cross-contract validation.
Learned about:
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.
40 hours