# Smart Contracts
# Introduction
The Rocket Pool Smart Contracts (opens new window) form the foundation of the Rocket Pool protocol. They are the base layer of infrastructure which all other elements of the network are built on top of, including the JavaScript library, the Smart Node software stack, and all web or application interfaces.
Direct interaction with the contracts is usually not necessary, and is facilitated through the use of other software (such as the JavaScript library). This section provides a detailed description of the contract design, and information on how to build on top of Rocket Pool for developers wishing to extend it. All code examples are given as Solidity v0.7.6
.
# Contract Design
The Rocket Pool network contracts are built with upgradability in mind, using a hub-and-spoke architecture. The central hub of the network is the RocketStorage
contract, which is responsible for storing the state of the entire protocol. This is implemented through the use of maps for key-value storage, and getter and setter methods for reading and writing values for a key.
The RocketStorage
contract also stores the addresses of all other network contracts (keyed by name), and restricts data modification to those contracts only. Using this architecture, the protocol can be upgraded by deploying new versions of an existing contract, and updating its address in storage. This gives Rocket Pool the flexibility required to fix bugs or implement new features to improve the protocol.
# Interacting With Rocket Pool
To begin interacting with the Rocket Pool network, first create an instance of the RocketStorage
contract using its interface (opens new window):
import "RocketStorageInterface.sol";
contract Example {
RocketStorageInterface rocketStorage = RocketStorageInterface(0);
constructor(address _rocketStorageAddress) public {
rocketStorage = RocketStorageInterface(_rocketStorageAddress);
}
}
The above constructor should be called with the address of the RocketStorage
contract on the appropriate network.
Because of Rocket Pool's architecture, the addresses of other contracts should not be used directly but retrieved from the blockchain before use. Network upgrades may have occurred since the previous interaction, resulting in outdated addresses. RocketStorage
can never change address, so it is safe to store a reference to it.
Other contract instances can be created using the appropriate interface taken from the Rocket Pool repository (opens new window), e.g.:
import "RocketStorageInterface.sol";
import "RocketDepositPoolInterface.sol";
contract Example {
RocketStorageInterface rocketStorage = RocketStorageInterface(0);
constructor(address _rocketStorageAddress) public {
// It is safe to store reference to RocketStorage
rocketStorage = RocketStorageInterface(_rocketStorageAddress);
}
exampleMethod() public {
// All other contracts should be queried each time they are used
address rocketDepositPoolAddress = rocketStorage.getAddress(keccak256(abi.encodePacked("contract.address", "rocketDepositPool")));
RocketDepositPoolInterface rocketDepositPool = RocketDepositPoolInterface(rocketDepositPoolAddress);
...
}
}
The Rocket Pool contracts, as defined in RocketStorage
, are:
rocketVault
- Stores ETH held by network contracts (internal, not upgradeable)rocketAuctionManager
- Handles the auctioning of RPL slashed from node operators' stakerocketDepositPool
- Accepts user-deposited ETH and handles assignment to minipoolsrocketMinipoolManager
- Creates & manages all minipools in the networkrocketMinipoolQueue
- Organises minipools into a queue for ETH assignmentrocketMinipoolStatus
- Handles minipool status updates from watchtower nodesrocketMinipoolPenalty
- Stores penalties applied to node operators by the oDAOrocketNetworkBalances
- Handles network balance updates from watchtower nodesrocketNetworkFees
- Calculates node commission rates based on network node demandrocketNetworkPrices
- Handles RPL price and effective stake updates from watchtower nodesrocketNetworkWithdrawal
- Handles processing of beacon chain validator withdrawalsrocketRewardsPool
- Handles the distribution of rewards to each rewards contractrocketClaimDAO
- Handles the claiming of rewards for the pDAOrocketClaimNode
- Handles the claiming of rewards for node operatorsrocketClaimTrustedNode
- Handles the claiming of rewards for the oDAOrocketNodeDeposit
- Handles node deposits for minipool creationrocketNodeManager
- Registers & manages all nodes in the networkrocketNodeStaking
- Handles node staking and unstakingrocketDAOProposal
- Contains common oDAO and pDAO functionalityrocketDAONodeTrusted
- Handles oDAO related proposalsrocketDAONodeTrustedProposals
- Contains oDAO proposal functionality (internal)rocketDAONodeTrustedActions
- Contains oDAO action functionality (internal)rocketDAONodeTrustedUpgrade
- Handles oDAO contract upgrade functionality (internal)rocketDAONodeTrustedSettingsMembers
- Handles settings relating to trusted membersrocketDAONodeTrustedSettingsProposals
- Handles settings relating to proposalsrocketDAONodeTrustedSettingsMinipool
- Handles settings relating to minipoolsrocketDAOProtocol
- Handles pDAO related proposalsrocketDAOProtocolProposals
- Handles pDAO proposal functionality (internal)rocketDAOProtocolActions
- Handles pDAO action functionality (internal)rocketDAOProtocolSettingsInflation
- Handles settings related to inflationrocketDAOProtocolSettingsRewards
- Handles settings related to rewardsrocketDAOProtocolSettingsAuction
- Handles settings related to auction systemrocketDAOProtocolSettingsNode
- Handles settings related to node operatorsrocketDAOProtocolSettingsNetwork
- Handles settings related to the networkrocketDAOProtocolSettingsDeposit
- Handles settings related to depositsrocketDAOProtocolSettingsMinipool
- Handles settings related to minipoolsrocketTokenRETH
- The rETH token contract (not upgradeable)rocketTokenRPL
- The RPL token contract (not upgradeable)addressQueueStorage
- A utility contract (internal)addressSetStorage
- A utility contract (internal)
Contracts marked as “internal” do not provide methods which are accessible to the general public, and so are generally not useful for extension. For information on specific contract methods, consult their interfaces in the Rocket Pool repository (opens new window).
# Deposits
The main reason for extending the Rocket Pool network is to implement custom deposit logic which funnels user deposits into the deposit pool. For example, a fund manager may wish to stake their users’ ETH in Rocket Pool via their own smart contracts, and abstract the use of Rocket Pool itself away from their users.
Note: the RocketDepositPool
contract address should not be hard-coded in your contracts, but retrieved from RocketStorage
dynamically. See Interacting With Rocket Pool for more details.
# Implementation
The following describes a basic example contract which forwards deposited ETH into Rocket Pool and minted rETH back to the caller:
import "RocketStorageInterface.sol";
import "RocketDepositPoolInterface.sol";
import "RocketETHTokenInterface.sol";
contract Example {
RocketStorageInterface rocketStorage = RocketStorageInterface(0);
mapping(address => uint256) balances;
constructor(address _rocketStorageAddress) public {
rocketStorage = RocketStorageInterface(_rocketStorageAddress);
}
deposit() external payable {
// Check deposit amount
require(msg.value > 0, "Invalid deposit amount");
// Load contracts
address rocketDepositPoolAddress = rocketStorage.getAddress(keccak256(abi.encodePacked("contract.address", "rocketDepositPool")));
RocketDepositPoolInterface rocketDepositPool = RocketDepositPoolInterface(rocketDepositPoolAddress);
address rocketETHTokenAddress = rocketStorage.getAddress(keccak256(abi.encodePacked("contract.address", "rocketETHToken")));
RocketETHTokenInterface rocketETHToken = RocketETHTokenInterface(rocketETHTokenAddress);
// Forward deposit to RP & get amount of rETH minted
uint256 rethBalance1 = rocketETHToken.balanceOf(address(this));
rocketDepositPool.deposit{value: msg.value}();
uint256 rethBalance2 = rocketETHToken.balanceOf(address(this));
require(rethBalance2 > rethBalance1, "No rETH was minted");
uint256 rethMinted = rethBalance2 - rethBalance1;
// Update user's balance
balances[msg.sender] += rethMinted;
}
// After 24 hours it's possible to transfer the tokens
withdraw() external {
// Load contracts
address rocketETHTokenAddress = rocketStorage.getAddress(keccak256(abi.encodePacked("contract.address", "rocketETHToken")));
RocketETHTokenInterface rocketETHToken = RocketETHTokenInterface(rocketETHTokenAddress);
// Transfer rETH to caller
uint256 balance = balances[msg.sender];
balances[msg.sender] = 0;
require(rocketETHToken.transfer(msg.sender, balance), "rETH was not transferred to caller");
}
}