Omniscia Trustworks Audit
Multisig Manual Review Findings
Multisig Manual Review Findings
MUL-01M: Convoluted Logic
Type | Severity | Location |
---|---|---|
Indeterminate Code | Minor | Multisig.sol:L138-L179 |
Description:
The multisignature wallet implementation at hand is a manually crafted one with a basic logical structure, however, it can be significantly simplified and rendered more secure for off-chain processes.
Example:
138function initializeTransferToken(address _recipientForToken, IBEP20 _tokenToTransfer,uint256 _amountToTransferToken) external nonReentrant {139 require(msg.sender == owner2, "Only owner2 can initializeTransfer");140 recipientForToken = _recipientForToken;141 tokenToTransfer = _tokenToTransfer;142 amountToTransferToken = _amountToTransferToken;143 canTransferToken = true;144}145146function transferToken(address _recipientForToken, IBEP20 _tokenToTransfer, uint256 _amountToTransferToken) external nonReentrant {147 require(msg.sender == owner1, "Only owner1 can transferToken");148 require(canTransferToken, "Trnsfer is not initialized");149 require(recipientForToken != address(0));150 require(tokenToTransfer != IBEP20(0));151 require(_recipientForToken == recipientForToken, "Recptient is not the same as the in the initialized recipient");152 require(_tokenToTransfer == tokenToTransfer, "tokenToTransfer is not the same as the in the initialized tokenToTransfer");153 require(_amountToTransferToken == amountToTransferToken, "amountToTransfer is not the same as the in the initialized amountToTransfer");154 canTransferToken = false;155 tokenToTransfer = IBEP20(0);156 amountToTransferToken = 0;157 recipientForToken = address(0);158 IBEP20(_tokenToTransfer).transfer(_recipientForToken, _amountToTransferToken);159}160161162function initializeTransferBNB(address payable _recipientForBNB, uint256 _amountToTransferBNB) external nonReentrant {163 require(msg.sender == owner2, "Only owner2 can initializeTransfer");164 recipientForBNB = _recipientForBNB;165 amountToTransferForBNB = _amountToTransferBNB;166 canTransferBNB = true;167}168169function transferBNB(address payable _recipientForBNB, uint256 _amountToTransferBNB) external nonReentrant {170 require(msg.sender == owner1, "Only owner1 can transferToken");171 require(canTransferBNB, "Trnsfer is not initialized");172 require(recipientForBNB != address(0));173 require(recipientForBNB == _recipientForBNB, "Recptient is not the same as the in the initialized recipient");174 require(amountToTransferForBNB == _amountToTransferBNB, "amountToTransfer is not the same as the in the initialized amountToTransfer");175 canTransferBNB = false;176 amountToTransferForBNB = 0;177 recipientForBNB = address(0);178 _recipientForBNB.transfer(_amountToTransferBNB);179}
Recommendation:
Firstly, the transfer functions can be grouped into two endpoints, initializeTransfer
and executeTransfer
, without duplicating the storage variables and utilizing convoluted storage variables. This can be done by simply retaining the additional tokenToTransfer
variable as a "flag" which when reflecting the zero address signals a BNB transfer and otherwise signals a token transfer. The system currently utilizes a race-condition protection by having the executor of a transaction specify all arguments of it to ensure they have not been changed between the transaction's broadcast and the transaction's inclusion into a block. While this is sufficient as race condition protection, it introduces a bad user experience that can also be exploited in case a user assumes their transaction will fail if they broadcast another one to "replace" their previous one. To avoid this situation, we advise the introduction and utilization of a nonce
variable which is the sole input of the transfer execution. Every time the owner2
introduces a new transfer, the nonce
variable is incremented by one. This prohibits race conditions as the owner1
would validate a transaction by specifying the nonce
which would fail if a new transaction was attempted to be introduced. Additionally, we advise the owner1
variable to be renamed executor
and the owner2
variable to be renamed proposer
to increase the legibility of the codebase.
Alleviation:
While the function simplification and renaming we suggested was not applied, the nonce-based system was introduced as a security feature. Given this, we assume the finding as dealt with.
MUL-02M: Potentially Breaking Functionality
Type | Severity | Location |
---|---|---|
Standard Conformity | Minor | Multisig.sol:L255, L256, L275 |
Description:
The transfer
opcode assigns a static gas amount to the external call that transfers funds outwards which can be changed in a consequent fork of Binance and thus cause such transfer
calls to fail.
Example:
169function transferBNB(address payable _recipientForBNB, uint256 _amountToTransferBNB) external nonReentrant {170 require(msg.sender == owner1, "Only owner1 can transferToken");171 require(canTransferBNB, "Trnsfer is not initialized");172 require(recipientForBNB != address(0));173 require(recipientForBNB == _recipientForBNB, "Recptient is not the same as the in the initialized recipient");174 require(amountToTransferForBNB == _amountToTransferBNB, "amountToTransfer is not the same as the in the initialized amountToTransfer");175 canTransferBNB = false;176 amountToTransferForBNB = 0;177 recipientForBNB = address(0);178 _recipientForBNB.transfer(_amountToTransferBNB);179}
Recommendation:
We advise that an OpenZeppelin wrapper implementation is instead used safely, such as the sendValue
function of the Address
library, to ensure compatibility at the EVM level perpetually.
Alleviation:
The instances of transfer
were properly replaced by their sendValue
counterpart.