-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
V1.1.7 internal audit4.1 #121
Merged
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
# Internal audit of autonolas-registries | ||
The review has been performed based on the contract code in the following repository:<br> | ||
`https://github.com/valory-xyz/autonolas-registries` <br> | ||
commit: `v1.1.7.pre-internal-audit` <br> | ||
|
||
## Objectives | ||
The audit focused on `staking a service` contracts in this repo. | ||
|
||
### Flatten version | ||
Flatten version of contracts. [contracts](https://github.com/valory-xyz/autonolas-registries/blob/main/audits/internal4/analysis/contracts) | ||
|
||
### ERC20/ERC721 checks | ||
N/A | ||
|
||
### Security issues. Updated 04-10-23 | ||
#### Problems found instrumentally | ||
Several checks are obtained automatically. They are commented. Some issues found need to be fixed. <br> | ||
All automatic warnings are listed in the following file, concerns of which we address in more detail below: <br> | ||
[slither-full](https://github.com/valory-xyz/autonolas-registries/blob/main/audits/internal4/analysis/slither_full.txt) | ||
|
||
Bad pattern (ref: reentrancy): <br> | ||
```solidity | ||
unstake: | ||
Doesn't match the pattern Checks, Effects, and Interactions (CEI): | ||
_withdraw(sInfo.multisig, sInfo.reward); in middle | ||
``` | ||
|
||
Reentrancy critical issue: <br> | ||
```solidity | ||
ServiceStaking.unstake -> _withdraw -> to.call{value: amount}("") -> ServiceStaking.unstake (sInfo.reward > 0 ??) -> _withdraw -> to.call{value: amount}("") | ||
``` | ||
|
||
Reentrancy medium issue: <br> | ||
```solidity | ||
ServiceStakingToken.unstake -> _withdraw -> SafeTransferLib.safeTransfer(stakingToken, to, amount); -> ServiceStaking.unstake | ||
via custom stakingToken | ||
``` | ||
|
||
Reentrancy low issue: <br> | ||
```solidity | ||
ServiceStakingToken.deposit -> SafeTransferLib.safeTransfer(stakingToken, to, amount); -> ServiceStaking.deposit | ||
via custom stakingToken | ||
``` | ||
|
||
Pointer bug (thanks Aleksandr Kuperman) <br> | ||
``` | ||
ServiceInfo memory sInfo = mapServiceInfo[serviceId]; | ||
(uint256[] memory serviceIds, , , , , ) = checkpoint(); // mapServiceInfo[curServiceId].reward += updatedReward; | ||
sInfo.reward; | ||
Incorrect state when updating in checkpoint() | ||
``` | ||
|
||
Low problem: <br> | ||
```solidity | ||
function checkpoint() public returns () | ||
uint256 curServiceId; | ||
vs | ||
// Get the current service Id | ||
uint256 curServiceId = serviceIds[i]; | ||
Details: https://github.com/crytic/slither/wiki/Detector-Documentation#variable-names-too-similar | ||
Reusing a same variable name in different scopes. | ||
``` | ||
|
||
|
||
```solidity | ||
if (state != 4) { | ||
revert WrongServiceState(state, serviceId); | ||
} | ||
It's better to use the original type enum. | ||
Details: https://github.com/pessimistic-io/slitherin/blob/master/docs/magic_number.md | ||
``` | ||
|
||
# Low optimization | ||
``` | ||
if (size > 0) { | ||
for (uint256 i = 0; i < size; ++i) { | ||
// Agent Ids must be unique and in ascending order | ||
if (_stakingParams.agentIds[i] <= agentId) { | ||
revert WrongAgentId(_stakingParams.agentIds[i]); | ||
} | ||
agentId = _stakingParams.agentIds[i]; | ||
agentIds.push(agentId); | ||
} | ||
} | ||
if size == 0 then for(i = 0; i < 0; i++) -> no loop | ||
``` | ||
``` | ||
// Transfer the service for staking | ||
IService(serviceRegistry).safeTransferFrom(msg.sender, address(this), serviceId); | ||
Last operation? | ||
``` | ||
|
||
### General considerations | ||
Measuring the number of live transactions through a smart contract has fundamental limitations: <br> | ||
Ethereum smart contracts only have access to the current state - not to historical states at previous blocks. <br> | ||
Also, there's currently no EVM opcode (hence no Solidity function) to look up the amount of transactions by an address. <br> | ||
Therefore, we have to rely on the internal counter "nonce" to measure tps (tx per sec). <br> | ||
https://github.com/safe-global/safe-contracts/blob/1cfa95710057e33832600e6b9ad5ececca8f7839/contracts/Safe.sol#L167 <br> | ||
|
||
In this contract, we assume that the services are mostly honest and are not trying to tamper with this counter. <br> | ||
ref: IService(serviceRegistry).safeTransferFrom(msg.sender, address(this), serviceId); <br> | ||
|
||
There are two ways to fake it: <br> | ||
1. Fake multisig. Open question | ||
```solidity | ||
(uint96 stakingDeposit, address multisig, bytes32 hash, uint256 agentThreshold, uint256 maxNumInstances, , uint8 state) = | ||
IService(serviceRegistry).mapServices(serviceId); | ||
sInfo.multisig = multisig; | ||
sInfo.owner = msg.sender; | ||
uint256 nonce = IMultisig(multisig).nonce(); | ||
Thus, it is trivial to tweak nonce it now if there is some trivial contract with the method setNonce(uint256); | ||
|
||
If you wish, you can fight, for example, using this method | ||
hash(multisig.code) vs well-know hash | ||
``` | ||
Is it possible to replace the multisig with some kind of fake one without losing the opportunity to receive rewards? <br> | ||
Notes: re-checking logic in contracts\multisigs\GnosisSafeSameAddressMultisig.sol (!) and update logic if necessary. | ||
|
||
2. "Normal" multisig. Open question | ||
``` | ||
https://github.com/safe-global/safe-contracts/blob/main/contracts/Safe.sol#L139 | ||
We can call execTransaction from another contract as owner in loop. | ||
txHash = getTransactionHash( // Transaction info | ||
... | ||
nonce++ | ||
); | ||
checkSignatures(txHash, "", signatures); | ||
|
||
I don't see a way to deal with this. Moreover, it is practically indistinguishable from normal use. | ||
Estimating gas consumption will not give us anything either. Only by off-chain measurement can we understand what is happening. | ||
``` | ||
Suggestion: Do nothing. <br> | ||
|
||
|
||
|
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Once the service has been staked, there is no possibility to update the service multisig.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Before staking.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see, good point. Added this in registries vulnerabilities list as well!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So basically for staking to be safe we first need to upgrade the protocol to ensure all services use Safe multisigs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@DavidMinarsch There's no immediate urgency for an upgrade. Specifically, we can perform a direct check on the staking contract when a service is staked under this initial version. Subsequently, we can consider upgrading the GnosisSafeSameAddressMultisig
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, the verification in this contract will be enough to close the problem now.
Then we will need to add this check to old contracts.
Everything is as @mariapiamo said. @kupermind has already made such a fix in post-audit PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We will do it in the staking contract, however it would be good to update GnosisSafeSameAddressMultisig contract at some point to check for the multisig proxy code.