Overview
This report covers the security review for Sentio. This audit covered the Solidity smart contracts of the Sentio Token. It consisted of bridgeable tokens that are compatible with LayerZero and Optimism L2s. Our security assessment was a full review of the code, spanning a total of 1 day. During our review, we did not identify any major or minor security vulnerabilities. We did identify several code optimizations. All reported issues were fixed or acknowledged by the development team and subsequently verified by us. We can confidently say that the overall security and code quality have increased after completion of our audit.
Scope
The analyzed resources are located on:
The issues described in this report were fixed in the following commit:
https://github.com/sentioxyz/sentio-token-audit/tree/30b0f4edbfa255f3adb6f2c44fdd622626ce527c
Summary
Weaknesses
This section contains the list of discovered weaknesses.
SENT1-1 | INCOMING BRIDGE MINTS ARE NOT BLOCKED WHEN CONTRACT IS PAUSED
Severity:
Status:
Acknowledged
Path:
SentioTokenBSC.sol:_update#L85-L93
Description:
The _update function of the SentioTokenBSC contract has been overridden to block any transfers to and from non-whitelisted addresses when the contract has been paused.
It does allow for minting to be continued (where from == address(0)), which allows the continued usage of the privileged mint function.
However, since the contract inherits from OFT, this also has the consequence that bridge transfers can continue to work to non-whitelisted accounts when the contract is paused. This is because the OFT _credit function will call the internal _mint function to transfer the tokens to the recipient of the LZ message.
Outgoing bridge transfers would be blocked, as it would check the whitelist for both the from address and the to address (address(0)).
function _update(address from, address to, uint256 amount) internal override {
if (paused()) {
bool minting = from == address(0);
bool whitelistTransfer = _isWhitelisted[from] && _isWhitelisted[to];
require(minting || whitelistTransfer, "paused and not whitelisted");
}
super._update(from, to, amount);
}
Remediation:
If this is unintended, then these 2 types of mints should be handled separately such that bridge mints are blocked when the contract is paused.
Commentary from the client
"If the pause will also stop OFT _creadit to call _mint on bsc, it will only stop the minting on BSC, but not the burning on the other chain, so this seems a very strange state. So we think it's better to let people see token transfered to BSC but not just be able to immedidately use when pausing.
Also pausing is only supposed to be used in the initial IDO phase, so in practice we will not pause the second time."
SENT1-2 | WHITELISTING CHECK WHEN MINTING CAN BE OPTIMIZED
Severity:
Status:
Fixed
Path:
SentioTokenBSC.sol:_update#L85-L93
Description:
The SentioTokenBSC contract overrides the _update function to implement whitelisted transfers when the contract is paused. It does allow for minting and transfers from/to whitelisted addresses.
It checks for minting by comparing from with address(0) and then also reads from the _isWhitelisted storage mapping for both from and to to check for whitelisted status. Because it first stores both results in a boolean and only then for a require check, the code will performs the 1 or 2 SLOADs for the whitelist check no matter what.
If these were to be combined into 1 comparison statement, then the code execution could return early from the boolean expression and return true or false depending on the values.
function _update(address from, address to, uint256 amount) internal override {
if (paused()) {
bool minting = from == address(0);
bool whitelistTransfer = _isWhitelisted[from] && _isWhitelisted[to];
require(minting || whitelistTransfer, "paused and not whitelisted");
}
super._update(from, to, amount);
}
Remediation:
For example:
if (paused()) {
require(from == address(0) || (_isWhitelisted[from] && _isWhitelisted[to]), "paused and not whitelisted");
}