USDT0 engaged Guardian to review the security of their OneSig multi-chain multisig EVM implementation. From the 10th of March to the 12th of March, a team of 3 auditors reviewed the source code in scope.
- Published
- Review window
- March 10 to 12, 2025
- Language
- Solidity
- Chains
- Ethereum, Arbitrum
- Sector
- Wallets and custody
- 0 Critical
- 0 High
- 0 Medium
- 6 Low
- 21 Informational
Scope
Overview
USDT0 engaged Guardian to review the security of their OneSig multi-chain multisig EVM implementation. From the 10th of March to the 12th of March, a team of 3 auditors reviewed the source code in scope.
Findings 27
-
L-01 Low Seed Invalidations Can Be Frontrun Frontrunning Acknowledged
Description
There is no access control which prevents arbitrary callers from using the
executeTransactionfunction. This is by design in theOneSigsystem, however this behavior introduces several risks.Notably, when the multisig signers wish to invalidate a previous merkle proof with a seed change it is possible for an arbitrary user to frontrun the execution of this seed change or even the signature collection process for the merkle root of this seed change and execute the transactions which the signers wish to invalidate.
This can result in unexpected outcomes from the transactions which were desired to be skipped and furthermore the unexpected increment of the nonce for the
OneSigcontract.Recommendation
Consider tracking a set of executor addresses which are allowed to invoke the
executeTransactionfunction. The list of executors can be initialized to the set of signers and extended/decreased if the signers wish with anonlyMultiSigfunction.Resolution
USDT0 Team: Acknowledged.
-
L-02 Low Unexpected Execution Order With Reentrancy Reentrancy Resolved
Description
The nonce for the
OneSigcontract is incremented directly before the execution of the set of external calls for the current transaction in theexecuteTransactionfunction. This successfully prevents replay of the currentTransactionobject through reentrancy in theexecuteTransactionfunction.However if an arbitrary untrusted address were to gain control of the transaction execution during the execution of one of the external calls of the current
Transaction, then that arbitrary untrusted address could re-enter and invoke theexecuteTransactionfunction again, now supplying the subsequent transaction in the same merkle root.This would result in the transaction from the subsequent nonce, say nonce 2, being processed entirely in the middle of the external calls of the current nonce, say nonce 1.
This breaks the guarantee of atomicity for the external calls in a given
Transactionobject and ultimately could lead to unexpected behavior or even stolen funds/bricked systems.Recommendation
Consider adding a
nonReentrantmodifier to theexecuteTransactionfunction.Resolution
USDT0 Team: Resolved.
-
L-03 Low Lacking Gas Validations Allows Censoring Validation Acknowledged
Description
The
executeTransactionfunction may be invoked by any arbitrary address and does not include validation on the amount of gas that has been provided for each individual external call made as a part of theTransactionobject.Some external calls that are made from the
OneSigcontract may have different execution results depending on the amount of gas supplied.For instance, if an invoked function includes a
try/catchblock around deeper logic, then thecatchblock can be trivially triggered by a malicious actor intentionally providing an insufficient amount of gas.Additionally, there is no gas amount specified for each external call, therefore 63/64 of the transaction’s remaining gas is automatically forwarded to each external call.
This allows prior external calls to consume gas in such a way that can impact the execution of later external calls in a
Transactionobject.Recommendation
Consider including an optional amount of gas that is to be present and forwarded to each external call in a
Transactionin the leaf data so that if signers must ensure the amount of gas provided to an external call then this can be validated and provided as such.Resolution
USDT0 Team: Acknowledged.
-
L-04 Low Network Forks Enable Replay Attacks Replay Acknowledged
Description
The
OneSigIdserves to differentiate deployments ofOneSig Multisigsacross chains, thus preventing replay.However in the event that a network fork occurs, transactions can be replayed across instances of a
OneSigcontract on both resulting chains because theOneSigimmutable value remains the same in both instances.Recommendation
Consider including the block.chainId as an enforced portion of the
OneSigIdand updating theOneSigIdif theblock.chainidis determined to have changed from an originally cached value. Similarly to the logic used in the _domainSeparatorV4 function of the OpenZeppelin EIP712 contract.Resolution
USDT0 Team: Acknowledged.
-
L-05 Low Stale Transaction Execution Allowed When Sequencer Down Warning Acknowledged
Description
For L2 networks which use a sequencer it is possible for the
block.timestampused in a transaction to be out of date in cases where the sequencer is down for an extended period of time.From the Arbitrum docs:
As mentioned, block timestamps are usually set based on the sequencer's clock. Because there's a possibility that the sequencer fails to post batches on the parent chain (i.e, Ethereum) for a period of time, it should have the ability to slightly adjust the timestamp of the block to account for those delays and prevent any potential reorganisations of the chain. To limit the degree to which the sequencer can adjust timestamps, some boundaries are set, currently to 24 hours earlier than the current time, and one hour in the future.The behavior described here is that if the sequencer goes down for less than 24 hours, the sequencer can pick up any transactions from the Delayed Inbox and
block.timestampwill be equal to the current time.However, if the sequencer is down for more than 24 hours in extreme scenarios, then any
forceInclusiontransaction will be given a timestamp of the most recent L2 block, which is likely delayed significantly in this case, or the timestamp of the L1 from when the tx was placed in Delayed Inbox, whichever is greater. Consequently, a stale transaction could be executed past the expiry.Recommendation
Be aware of this risk during an extended sequencer outage, and consider implementing a sequencer uptime check for L2 deployments which use a sequencer.
Resolution
USDT0 Team: Acknowledged.
-
L-06 Low Multiple Signed Merkle Roots Allows Unexpected Ordering Unexpected Behavior Acknowledged
Description
In the event that multiple valid and signed merkle roots exist for the same
OneSigsafe that have overlapping nonces in their transactions, an arbitrary user can invoke theexecuteTransactionfunction mixing and matching transactions from each merkle root to create an unexpected execution outcome.Recommendation
Ensure that users are made aware of this risk and thus are careful to not sign merkle roots with overlapping nonces for the same
OneSigcontract.Resolution
USDT0 Team: Acknowledged.
-
I-01 Informational Unexpected OneSig Balance Usage Validation Acknowledged
Description
The
OneSigcontract may be used to store native tokens on behalf of the signers for the contract, however because theexecuteTransactionfunction can be called by anyone this amount may be unexpectedly decreased in some scenarios.If a merkle root which has been signed by the signers contains a function call with nonzero value, the signers may expect that the execution of
executeTransactionwould include that value as themsg.valueof the transaction and thus not decrease the balance of theOneSig.However an arbitrary user may invoke
executeTransactionwithout providing anymsg.valueand thus decrease theOneSignative token balance by the transactions value. This may be unexpected for the signers and can deviate from exactly how they would like this transaction to be executed.Recommendation
If this behavior is acceptable, then be sure to document it clearly for users of
OneSig. Otherwise consider allowing the signers to specify as a part of the leaf data whether the native tokens should be supplied by the caller or theOneSigcontract.Alternatively, consider tracking a set of executor addresses which are allowed to invoke the
executeTransactionfunction.Resolution
USDT0 Team: Acknowledged.
-
I-02 Informational Signature Length Magic Number Best Practices Resolved
Description
In the
MultiSigcontract the value of65is referred to multiple times to indicate the length of the signatures provided. However rather than using a “magic number” to represent this length, a constant value can be declared in the contract.Recommendation
Consider declaring a
SIGNATURE_LENGTHconstant to replace the bespoke instances of65.Resolution
USDT0 Team: Resolved.
-
I-03 Informational Excess Signatures Are Not Allowed Validation Acknowledged
Description
In the
verifyNSignaturesfunction the amount of signatures is validated to be exactly the_thresholdprovided. However in the documentation it is described that:Modifying signers must require the same signerThreshold (or more) of signatures as executing a
transaction.The optionality for a
OneSigcontract to require more than the threshold to initiate a signer change does not currently exist in the EVM version and is specifically disallowed by the validation performed on the_thresholdand_signaturesvalues in theverifyNSignaturesfunction.Recommendation
Consider if this optionality of using a higher threshold for signer changes should be supported for the EVM version. If so then the validation and logic in
verifyNSignaturesshould be refactored to allow greater than or equal to the threshold of signatures to be provided.Resolution
USDT0 Team: Acknowledged.
-
I-04 Informational Unnecessary returnData Optimization Acknowledged
Description
The external call made in the
executeTransactionfunction uses a.callinvocation which automatically loads in thereturnDatainto memory, regardless of if thereturnDataparameter is named in the returned tuple unpacking.This is currently a waste of gas since the returned data is unused, and could potentially be a source of gas griefing for the executor in the case that the external call is made to an untrusted contract which may return a large amount of bytes unexpectedly.
Recommendation
Consider implementing a low level assembly call and specifying 0 as the length of return data to copy into memory.
Resolution
USDT0 Team: Acknowledged.
-
I-05 Informational Exclusion Of Smart Contract Signers Due To ECDSA Verification Best Practices Acknowledged
Description
The current implementation uses
ECDSA.recoverto verify signers, a method that only supports externally owned accounts (EOAs) signatures. As a result, any contract-based address cannot produce valid signatures under this scheme, effectively excluding multi signature wallets.multi signature wallets and contract-based managers are incapable of directly signing in the same manner as EOAs, preventing them from providing their signatures to the
OneSigcontract.This exclusion narrows the potential user base by disallowing participation from important categories such as DAO-managed treasuries or advanced contract-driven accounts, which are common in decentralized ecosystems.
Recommendation
Consider using the
SignatureCheckerlibrary instead ofECDSAfor signature verification.OpenZeppelin’sSignatureChecker.isValidSignatureNowsupports both EOAs and contract wallets.This approach allows both types of accounts to produce valid signatures, enabling a broader range of automated and contract-based use cases without excluding any part of the user base.
Resolution
USDT0 Team: Acknowledged.
-
I-06 Informational Missing Event Emission In The Receive Function Best Practices Acknowledged
Description
The
OneSig’sreceivefunction currently allows native assets to be sent directly to the contract. While thereceivefunction successfully collects and holds the native assets, there is no corresponding event to track deposits.Recommendation
Consider emitting an event within the
receivefunction that captures essential information, such asmsg.sender, themsg.valueand potentially the timestamp.Resolution
USDT0 Team: Acknowledged.
-
I-07 Informational Time Drift Across Networks May Be Unexpected Warning Acknowledged
Description
The expiration time is associated with the signatures for the merkle root, hence the expiry applies to all transactions equally. However, transactions within the tree can be intended for different chains, and chains do not have a perfectly synchronized
block.timestampacross them.When
if (block.timestamp > _expiry) revert MerkleRootExpired();is validated upon transaction execution, situations may arise where a transaction is meant to be expired on all chains, but is still executable on a subset of chains.Recommendation
Be aware of this risk.
Resolution
USDT0 Team: Acknowledged.
-
I-08 Informational Low-Level Calls Do Not Validate Contract Code Validation Acknowledged
Description
The
executeTransactionfunction uses low-level calls (e.g.,address.call{ value: ... }(data)) without checking whether the targettoaddress is a deployed smart contract that actually implements the function being called.As a result, sending a call to an Externally Owned Account (EOA) or a non-existent contract would still succeed, even though the call effectively does nothing.
This can create a misleading indication of success, similar to historical issues (e.g. in
Solady’ssafeTransferlibrary), where the transaction appears to execute successfully but no real action took place.Recommendation
Consider extending the
Callstruct to include abool isSmartContractfield that signals whether the target should contain code. During execution, ifisSmartContractis set totrue, validate thatto.code.length > 0(available in Solidity 0.8.18+).If the address lacks code, revert the transaction. Conversely, if
isSmartContractisfalse, allow the call to proceed.Resolution
USDT0 Team: Acknowledged.
-
I-09 Informational Stuck Nonce Blocks Following Transactions Logical Error Acknowledged
Description
Currently function
executeTransactiononly increments the nonce if the transaction call was successful. This poses an issue for all transactions encoded with a future nonce, in anticipation of the prior execution going through successfully.If a single transaction is unable to be executed, all future transactions (leaves) must be updated and the merkle root must be updated so those following transactions would be executed.
Recommendation
Consider incrementing the nonce even if the transaction call was unsuccessful. However, it is important to take caution with this approach since arbitrary users can call
executeTransaction.An arbitrary user may not send enough value, cause the transaction to fail purposefully, and then allow all the following transactions to move on in a potentially unintended state.
This may be remedied by having a set of trusted executors to call
executeTransactionor adding validation for all relevant values such asmsg.valueorgasleft().Furthermore, it would be important to ensure
eth_estimateGascan function appropriately ifexecuteTransactionno longer reverts on failure.Resolution
USDT0 Team: Acknowledged.
-
I-10 Informational Unexpected Reinstatement Of Transactions Validation Acknowledged
Description
When a seed is changed to invalidate a particular merkle root there is no logic to blacklist the seed from ever being reassigned.
As a result it is possible for signers of a multisig to accidentally reinstate a seed which was previously used and inadvertently reinstate a previously revoked merkle root.
Recommendation
Consider tracking a blacklist of past seeds so that they cannot be accidentally re-instated. Otherwise document this risk to users.
Resolution
USDT0 Team: Acknowledged.
-
I-11 Informational Useful Error Data Errors Acknowledged
Description
The
ExecutionFailederror contains the index of the call but neglects to include the nonce of theTransactionbeing executed. This data may be useful when debugging transaction failures or examining simulations.Recommendation
Consider including the
Transactionnonce in theExecutionFaileddata.Resolution
USDT0 Team: Acknowledged.
-
I-12 Informational Unused VERSION Constant Best Practices Acknowledged
Description
In the
OneSigcontract theVERSIONconstant value is declared and unused. The same version string is repeated in the declaration of theDOMAIN_SEPARATORconstant.This poses a risk if the
VERSIONconstant were to be updated without changing theDOMAIN_SEPARATORto match.Recommendation
Use the
VERSIONconstant in the declaration of theDOMAIN_SEPARATORvalue.Resolution
USDT0 Team: Acknowledged.
-
I-13 Informational OneSigs Must Have Version Compatibility Warning Acknowledged
Description
The
DOMAIN_SEPARATORincludes theOneSigversion as a field, therefore allOneSiginstances that are expected to be used in unison across networks by the same signers must have the sameVERSIONapplied.Recommendation
Be aware of this requirement and document it for users.
Resolution
USDT0 Team: Acknowledged.
-
I-14 Informational Token Transfers May Silently Fail Unexpected Behavior Acknowledged
Description
The external call return data is not validated in the
executeTransactionfunction which can result in silent failure of transactions which return false on failure instead of reverting.For example, if the signers were to initiate a withdrawal of USDT which failed for some reason the
executeTransactionfunction call would still succeed and emit theTransactionExecutedevent while the USDT transfer actually failed.Recommendation
Be aware of this behavior and document it for users.
Resolution
USDT0 Team: Acknowledged.
-
I-15 Informational Lacking EIP5267 Support Compatibility Acknowledged
Description
The
OneSigcontract does not expose functionality to read the signing domain. This functionality was standardized with EIP5267 which introduces theeip712Domainfunction.Recommendation
Consider implementing the EIP5267 defined interface for better discoverability for signers.
Resolution
USDT0 Team: Acknowledged.
-
I-16 Informational Floating Pragma Best Practices Acknowledged
Description
The
OneSigandMultiSigcontracts use a floating pragma version instead of a fixed pragma. This can introduce unexpected outcomes since the contracts can be compiled across multiple Solidity versions which may have slight known or unknown differences in behavior.Recommendation
Remove this ambiguity by using a fixed pragma version.
Resolution
USDT0 Team: Acknowledged.
-
I-17 Informational Incomplete verifyNSignatures Documentation Documentation Acknowledged
Description
The
NatSpecfor theverifyNSignaturesfunction lists the ways in which theverifyNSignaturesfunction reverts. However it does not include reverts which occur due to an invalid signature verification with the ECDSA library.Namely, the
verifyNSignaturesfunction can also revert when the provided s value for the signature is from the top half of the range or when the recovered signer address is the zero address.Recommendation
Consider including these revert scenarios in the function documentation.
Resolution
USDT0 Team: Acknowledged.
-
I-18 Informational Unwieldy Seed Behavior Warning Acknowledged
Description
The documentation in the
PROTOCOL.mdfile indicates that seeds are used to prevent signature replay, however this cannot be the case.Seeds must be identical across
OneSiginstances which are to be used in unison, otherwise a new signature for the merkle tree would have to be generated for each chain.This results in an unwieldy use-case when the signers want to invalidate the merkle on one chain but not on all chains. The signer must first update the seed on one chain and then execute the desired transactions on all other chains.
Finally the signer must update the seed across all of the companion deployments of
OneSigso they are all in unison again.Recommendation
Signers can instead execute new merkles on a target chain to advance the nonce past the desired transaction to skip. This method allows for granularity on the chain and transactions which the signers desire to skip.
Without requiring that all seeds are updated across all companion
OneSigdeployments. The seed should then only be used to invalidate a merkle root across all companionOneSigdeployments at once, and the seed should always be updated in unison and never vary across deployments.Resolution
USDT0 Team: Acknowledged.
-
I-19 Informational Arbitrary Transaction Ordering Risk MEV Acknowledged
Description
The
executeTransactionfunction may be called by any arbitrary address once the merkle signatures are available. An arbitrary actor may take advantage of this by controlling the ordering in which actions take place with respect to theMultiSigtransaction.For example, if the multisig aims to execute a swap with a slippage of 5%, an arbitrary user can guarantee that they are able to sandwich that swap by executing it in a multicall with their own frontrun and backrun swaps.
In another scenario, the signers of a
OneSigmultisig could have signed off on an important protocol altering transaction which should only be applied when the protocol is paused. The paused state could be controlled by a separatePAUSER_ROLEaddress which is not theOneSigmultisig.In this case an arbitrary address can execute the
OneSigaction before the protocol is paused and potentially cause damage this way.Recommendation
Be aware of the risk involved in allowing arbitrary executors for signed transactions. If this risk should be mitigated then consider implementing a whitelisted set of executors. Otherwise be sure to clearly document this risk to users of
OneSig.Resolution
USDT0 Team: Acknowledged.
-
I-20 Informational Documentation Issues Documentation Acknowledged
Description
In the
PROTOCOL.mdfile there are several misleading comments or grammatical errors:Firstly, it is mentioned
“Note: Although the domain-separator parameters are static across differentdeployments of OneSig, the risk of replay/unintentional signing attacks is mitigated due to the use ofunique seeds, unique signers, and incrementing nonces.”But not mentioned here is that the contract address is included in the leaf encoding, which removes the ability for replay across
OneSigdeployments on the same chain.The
PROTOCOL.mdfile still mentions theChainIDwhich has been replaced with theOneSigId. TheSignerWithAddressinterfaces is misspelled asSingerWithAddress.unsignedis misspelled asunisgnedon line 86.The description for the
contractentry in the leaf encoding section reads,a 32 byte identifierrepresenting a deploymentwhich is a copy of the description for theoneSigIdentry.Recommendation
Consider correcting these documentation issues.
Resolution
USDT0 Team: Acknowledged.
-
I-21 Informational Threshold And Signers Should Be Consistent Warning Acknowledged
Description
At the contract level it is possible for companion deployments of
OneSigsto have a different threshold and varying signer sets, however this may present complications from a UI/UX perspective.Recommendation
For maximum simplicity and to avoid potential confusion during the transaction execution process, consider requiring on the frontend that all companion
OneSigdeployments have the same signer threshold and set.Resolution
USDT0 Team: Acknowledged.
No findings match.
More from USDT0
All 20 reportsPut your code through the same review.
This review started with a conversation about scope. Tell us what you are building and we will plan yours with you.
