UnshETH #2

CertiKFiles named in the report

CertiK reviewed unshETH (UnshETH #2) on 4 Apr 2023: 7 findings, 0 critical; 5 marked fixed, 2 acknowledged.

No open critical findings
Open the reportOpen unshETH’s security record

Report hosted on skynet.certik.com.

Trustblock does not review code.

Findings7

Statuses are the security firm’s own, from its report.

SeverityOpenAcknowledgedFixed
Critical000
High001
Medium001
Low023
HighLack Of `payable` KeywordFixed

The `payable` keyword is missing from the `sgReceive()` function, making it impossible to receive ETH and pay the gas fee to execute the function `IOFTCore(proxyUnshethAddress).sendFrom` to send the unshETH back to `_toAddr` on BSC. ```solidity IOFTCore(proxyUnshethAddress).sendFrom{value:unsheth_gas_cost}( //TODO really think about this address(this), //current owner of the unsheth _chainId, //chain Id where the proxy of the unsheth exists (we recieve this in sgRecieve) abi.encode(toAddr), //the address we want the unsheth to end up in unshethMinted, //the amount of unsheth to send payable(address(this)), //the refund address if something goes wrong or excess address(0x0), //the ZRO Payment Address abi.encode("") //Adapter Params //TODO check on this ); ```

MediumPotential Overwrite `lsdIndex[_lsd]`Fixed

After adding the first `_lsd`, the `lsdIndex[_lsd]` variable retains its initial value of 0 since the length of the `supportedLSDs` array is 1. Consequently, if the same `_lsd` is added again, the `require` statement in the `addLSD` function will evaluate to true, causing the `lsdIndex[_lsd]` variable can be overwritten. ```solidity=194 function addLSD(address _lsd) public onlyOwner onlyWhenPaused { require(lsdIndex[_lsd] == 0, "Lsd has already been added"); //fyi fails on the first lsd being duplicated since it has actual index 0 supportedLSDs.push(_lsd); lsdIndex[_lsd] = supportedLSDs.length-1; //reverse mapping of supportedLSDs indices isEnabled[_lsd] = false; lsdConfigs[_lsd] = LSDConfig(0, 0, 0); emit LSDAdded(_lsd); } ``` ```solidity=448 function exit(uint256 amount) external nonReentrant { require(migrated = false, "Already migrated, use v2 vault to exit"); require(block.timestamp > shanghaiTime, "Cannot exit until shanghaiTime"); require(!withdrawalsPaused || block.timestamp > withdrawalUnpauseTime, "Withdrawals are paused"); require(IERC20(unshETHAddress).balanceOf(msg.sender) >= amount, "Insufficient unshETH"); uint256 shareOfUnsheth = 1e18*amount/IERC20(unshETHAddress).totalSupply(); uint256 fee = shareOfUnsheth*redeemFee/10000; //redeem fees are 100% retained by remaining unshETH holders IunshETH(unshETHAddress).minter_burn_from(msg.sender, amount); for (uint256 i = 0; i < supportedLSDs.length; i = unchkIncr(i)) { uint256 lsdBalance = IERC20(supportedLSDs[i]).balanceOf(address(this)); uint256 amountPerLsd = (shareOfUnsheth-fee)*lsdBalance/1e18; IERC20(supportedLSDs[i]).safeTransfer(msg.sender, amountPerLsd); } } ```

Low`shanghaiTime` may not be modifiedFixed

As per the validation of `_newTime` below, the variable `shanghaiTime` can be updated when `block.timestamp` < `_newTime` < `shanghaiTime` + 4 weeks. ```solidity=530 function updateShanghaiTime(uint256 _newTime) external onlyOwnerOrAdmin { require(_newTime < shanghaiTime + 4 weeks, "Cannot extend more than 4 weeks" ); require(_newTime > block.timestamp, "Cannot set shanghaiTime in the past" ); shanghaiTime = _newTime; emit ShanghaiTimeUpdated(shanghaiTime); } ``` However, if the `block.timestamp` >= `shanghaiTime` + 4weeks, there is no way to set the `_newTime` as the `shanghaiTime` no matter what the `_newTime` is.

LowThe Purpose Of the Function `setVdAmm`Fixed

There is a function `setVdAmm` which only changes the `swapperAddress` and `ammEnabled`. However, we can not find any usage of the `swapperAddress` and `ammEnabled`. We would like the team to elaborate more about the usages of the `swapperAddress` and `ammEnabled`.

LowUsage of `transfer()`/`send()` for sending EtherFixed

Using Solidity's `transfer()` and `send()` functions for transferring Ether is not recommended, since some contracts may not be able to receive the funds. These functions forward only a fixed amount of gas (2300 specifically) and the receiving contracts may run out of gas before finishing the transfer. Additionally, gas costs may increase in the future. Thus, some contracts that can receive now may stop working in the future due to the gas limitation.

Contracts in scope

No deployed address is recorded for this audit.

The report names a repository or files. Open the report to see its scope.

Repository and files the report names (7)

  • src/sgSender.sol
  • src/USH-OFT-BSC.sol
  • src/USH-Proxy-ETH.sol
  • src/LSDVault.sol
  • src/unshETH-OFT-BSC.sol
  • src/sgReciever.sol
  • src/unshETH-Proxy-ETH.sol

Part of unshETH’s security record

unshETH
Audits3 audits by 2 firmsLast audit Jun 2023, by Paladin.
Critical findingsNo open critical findingsAcross 3 reports.
IncidentIncident 30 May 2023Source: De.Fi incident database.
Open unshETH’s security record

Security firm

CertiK

1,044 projects reviewed · 1,375 reports · latest May 2025

See CertiK’s audits

Something wrong on this page? A missing report, a wrong date, a finding that was fixed since. Tell us and we check it against its source.

Tell us