MakerDAO has a proposal to add an rETH oracle. In the proposal, the author links to his personal blog where he has a post entitled Can you get rugged by RocketPool?
In the blog post, he says that it was theoretically possible for Rocket Pool to sneak in another address that could potentially modify the rETH exchange rate.
For example, in this transaction the key is a hash and it is not possible to reverse it to determine what the real value was.
Here is a script that does the following:
- goes through all blocks since the start and end of the
rocketStorage
deployment - finds all transactions from the deployer/guardian that set values on the contract
- looks up the unhashed key
- prints a summary of all function calls, their hashed and unhashed keys
const ethers = require('ethers') // npm install ethers
const { keccak256, toUtf8Bytes, concat } = ethers.utils
;(async function () {
const provider = new ethers.providers.StaticJsonRpcProvider('http://...') // archive node needed
// https://etherscan.io/tx/0x5d30e1082de780e5fd231f507ed9809cf17847e4e46be319fe218eb2847cfafa
const firstBlock = 13325233 // rocketStorage created
// https://etherscan.io/tx/0x90c7f390f1315e98c1217c57b49399fc7013dd54b6f71635296ecb7fd6546ced
const lastBlock = 13325559 // setDeployedStatus
// https://etherscan.io/tx/0x5d30e1082de780e5fd231f507ed9809cf17847e4e46be319fe218eb2847cfafa
const guardian = '0x0cCF14983364A7735d369879603930Afe10df21e'
// https://etherscan.io/address/0x1d8f8f00cfa6758d7be78336684788fb0ee0fa46
const storage = '0x1d8f8f00cfa6758d7bE78336684788Fb0ee0Fa46'
// https://github.com/rocket-pool/rocketpool/blob/master/contracts/interface/RocketStorageInterface.sol
// https://github.com/rocket-pool/rocketpool/blob/master/contracts/contract/RocketStorage.sol
const contract = new ethers.Contract(
storage,
[
'function getAddress(bytes32 key) external view returns (address)',
'function setAddress(bytes32 key, address value)',
'function setUint(bytes32 key, uint value)',
'function setString(bytes32 key, string calldata value)',
'function setBytes(bytes32 key, bytes calldata value)',
'function setBool(bytes32 key, bool value)',
'function setInt(bytes32 key, int value)',
'function setBytes32(bytes32 key, bytes32 value)',
'function setDeployedStatus()'
],
provider
)
// https://github.com/rocket-pool/rocketpool/blob/master/migrations/2_deploy_contracts.js#L35
const contracts = [
'rocketVault',
'rocketAuctionManager',
'rocketDepositPool',
'rocketMinipoolDelegate',
'rocketMinipoolManager',
'rocketMinipoolQueue',
'rocketMinipoolStatus',
'rocketMinipoolPenalty',
'rocketNetworkBalances',
'rocketNetworkFees',
'rocketNetworkPrices',
'rocketRewardsPool',
'rocketClaimDAO',
'rocketClaimNode',
'rocketClaimTrustedNode',
'rocketNodeDeposit',
'rocketNodeManager',
'rocketNodeStaking',
'rocketDAOProposal',
'rocketDAONodeTrusted',
'rocketDAONodeTrustedProposals',
'rocketDAONodeTrustedActions',
'rocketDAONodeTrustedUpgrade',
'rocketDAONodeTrustedSettingsMembers',
'rocketDAONodeTrustedSettingsProposals',
'rocketDAONodeTrustedSettingsMinipool',
'rocketDAOProtocol',
'rocketDAOProtocolProposals',
'rocketDAOProtocolActions',
'rocketDAOProtocolSettingsInflation',
'rocketDAOProtocolSettingsRewards',
'rocketDAOProtocolSettingsAuction',
'rocketDAOProtocolSettingsNode',
'rocketDAOProtocolSettingsNetwork',
'rocketDAOProtocolSettingsDeposit',
'rocketDAOProtocolSettingsMinipool',
'rocketTokenRETH',
'rocketTokenRPL',
'addressQueueStorage',
'addressSetStorage',
'rocketStorage', // https://github.com/rocket-pool/rocketpool/blob/master/migrations/2_deploy_contracts.js#L32
'casperDeposit', // https://github.com/rocket-pool/rocketpool/blob/master/migrations/2_deploy_contracts.js#L130
'rocketMinipool' // https://github.com/rocket-pool/rocketpool/blob/master/migrations/2_deploy_contracts.js#L95
]
const keys = {}
keys[keccak256(toUtf8Bytes('deploy.block'))] = 'deploy.block' // https://github.com/rocket-pool/rocketpool/blob/master/migrations/2_deploy_contracts.js#L298
for (const contractName of contracts) {
// contracts may have been upgraded in the future and their address may have changed since the deployment
// that's why getting the address at the time of the deployment
const contractAddress = await contract.getAddress(keccak256(toUtf8Bytes('contract.address' + contractName)), { blockTag: lastBlock })
// https://github.com/rocket-pool/rocketpool/blob/master/migrations/2_deploy_contracts.js#L193
keys[keccak256(concat([toUtf8Bytes('contract.exists'), contractAddress]))] = 'contract.exists' + contractAddress
keys[keccak256(concat([toUtf8Bytes('contract.name'), contractAddress]))] = 'contract.name' + contractAddress
keys[keccak256(toUtf8Bytes('contract.address' + contractName))] = 'contract.address' + contractName
keys[keccak256(toUtf8Bytes('contract.abi' + contractName))] = 'contract.abi' + contractName
}
const txs = []
for (let i = firstBlock; i <= lastBlock; i++) {
const block = await provider.getBlockWithTransactions(i)
for (const tx of block.transactions) {
if (tx.from == guardian && tx.to == storage) {
txs.push({
blockNumber: block.number,
hash: tx.hash,
from: tx.from,
to: tx.to,
data: tx.data
})
}
}
}
console.log(['tx', 'function', 'key hash', 'key', 'value'].join('\t'))
for (const tx of txs) {
try {
const { name } = contract.interface.getFunction(tx.data.substring(0, 10))
const { key, value } = contract.interface.decodeFunctionData(name, tx.data)
if (name == 'setDeployedStatus') {
console.log([tx.hash, name].join('\t'))
} else {
console.log([tx.hash, name, key, keys[key] || '<unknown>', value].join('\t'))
}
} catch (e) {
console.error(e)
}
}
})()
You can see the result here: Rocket Storage Audit - Google Sheets
I was able to look up all unhashed keys used in setBool
, setAddress
, etc. and determine their unhashed values. This means that all these function calls were done by the deployment script and were expected.
There are no function calls with keys that I could not look up which means everything is accounted for and the team did not maliciously give write access to unexpected addresses.