A collection of scripts and utilities for deplying and migrating Orbs PoS contracts on Ethereum.
See here for the full list of deployed contracts. The contracts' ABIs and source code are taken from the @orbs-network/orbs-ethereum-contracts-v2 npm package.
-
run
npm install
-
Upgrade
@orbs-network/orbs-ethereum-contracts-v2
if neccessary. This npm package contains the actual contracts and ABIs used by this script.npm install @orbs-network/orbs-ethereum-contracts-v2
-
Set the following env vars:
export ETHEREUM_URL=<Ethereum endpoint URL, e.g. infura> export GAS_PRICE_DEPLOY=<Gas price in WEI for contract deployment transactions> export GAS_PRICE=<Gas price in WEI for contract call transactions> # optional export CHAIN_ID=<chain id (defaults to 1)> export GAS_LIMIT=<Single transaction gas limit (defaults to 7000000)> export WEB3_DRIVER_VERBOSE=true # for more visibility on progress
- To test using
ganache-cli
simply setETHEREUM_URL
with your localganache
endpoint. - To test with
ganache-core
(an in-process ganache instance), run withexport GANACHE_CORE=true
. - To test with a mainnet-fork
ganache-core
, run withexport GANACHE_CORE=true
andexport ETHEREUM_FORK_URL=<Ethereum endpoint URL>
.
** IMPORTANT - These settings DO NOT apply to the v1 delegation migration script**
- To test using
-
In the repo root, create a file called
.secret
that conatins the Ethereum mnemonic you intend to use to send transactions. All contract managers are initialy set to (and assumed to be) the first mnemonic account (with the exception ofRegistryAdmin
, which is set in the configuration).
-
cd contract-deployment
npm install
-
Update
contract-deployment/config.ts
according to the desired contract configuration. See the default config for full list of configuration options. -
Run the staking contract deployment script:
npm run deploy-staking-contract
, update the stakingContract address incontract-deployment/config.ts
after deployment -
Run the contract deployment script:
npm run deploy-contracts
This will deploy all PoS V2 contracts, and generate a JSON file with addresses for all deployed contracts and managers under the repo root, at
deployed-contracts.json
. -
At this point, the contracts are fully deployed but still at an initialization state (
initializationAdmin
has root access, managers are not set, reward distribution is not active, staking contract is disconnected). At this point you take care of:- Topping-up the reward wallets (
stakingRewardsWallet
,bootstrapRewardsWallet
) according to the configured rates (see ProtocolWallet.sol). - Setting the
migrationManager
and thefunctionalManager
mangaers in theContractRegistry
(see IContractRegistry.sol). - Perform necessary data migrations if needed (further explained in the rest of this doc). That may include:
- Registered guardians
- Delegations
- Active committee
- ..and any other desired initial state. Since the contracts are at initialization state, the deployer account is permitted to perform any action on the contracts.
- Topping-up the reward wallets (
-
Activate reward distribution:
npm run activate-reward-distribution
This will call
activateRewardDistribtion(startTime = now)
on the fees, bootstrap and staking reward contracts. -
Finalize initialization.
- Caution: this cannot be undone! After this step the initialization admin (the deployer account) will have no permission in the system, unless explicitly set as a manager.
npm run finalize-initialization
This will call
initializationComplete()
on all managed contracts. -
Connect the staking contract to the staking contract handler (see setStakeChangeNotifier in the StakingContract). Note - in the time frame between delegation migration there may have been staking notifications that the new contract have missed. See Fixing discrepenceis between the StakingContract and DelegationsContract on how to close these gaps.
In order to verify contracts on Etherscan you can use the script std-json-input.js which converts truffle output to a json input format used by Etherscan for contracts verification.
To verify for exmaple the ContractRegistry, you need to do the following actions:
- Generate Standard-Json-Input file by running:
node std-json-input.js node_modules/@orbs-network/orbs-ethereum-contracts-v2/contracts node_modules/@orbs-network/orbs-ethereum-contracts-v2/build/contracts/ContractRegistry.json
- Go to Etherscan, choose contract verification and choose Solidity (Standard-Json-Input)
- Select the liscense type (MIT)
- Uploaded the generated json file in first step
- Go to contract creation tx hash and extract the constructor parameters. You can do it by clicking
Click to see More
in the Transaction Details page and from Input Data field. Contract registry for example has 2 addresses in the constructor. - Press Upload
This repo contains several script for upgrading specific contracts. Some Orbs PoS v2 contracts are equipped with data migration mechanisms that allows a priviliged user (typically the initialization admin) to initialze the contract with existing state (such as a list of registered guardians, delegations, and so on). This repo contains helper scripts that perform such migrations.
As for existing contracts, most migration priviliges are granted to the migrationManager (a role set in the contract registry). Since the registryAdmin (the contracts super user) is typically stored in a cold wallet, using it for migrations that require multiple transaction (e.g. a migration script) is not straight forward. Instead, the recommended flow is to assign the migratonManager role to a new, temporary address created for the specific migration, and revoke the permissions after the migration is complete.
The current registryAdmin is 0xf1fD5233E60E7Ef797025FE9DD066d60d59BcB92.
The typical upgrade flow is as follows:
- Prepare a new migrator account and set the migrationManager to that account, by calling
ContractRegistry.setManager("migrationManager", <account>)
from the registryAdmin address. - Deploy the new contract.
- Verify the contract in Etherscan. This ensures everyone has access to the contract ABI and source code (some clients may depend on it).
- Update
@orbs-network/orbs-ethereum-contracts-v2
'sgetAbiByContractAddress()
function so that it would return the contract ABI when given the contract address (see the following Pitfalls section). - Lock the previous contract using the
Lockable
interface, to avoid state changes in the old contract during migration. Locking can be done by the migrationMangaer. - Perform any neccessary state migration from the previous contract (e.g. by using priviliges initialization function in the old and/or new contracts). In case of a large state, split over several transactions.
- Set the address of the new contract in the contract registry, using
ContractRegistry.setContract
. The migrationManager is allowed to make this call. - Revoke the migrationManager account, by calling
ContractRegistry.setManager("migrationManager", address(0))
. This also requires the registryAdmin account. - Update the list of deployed contracts.
- It is crucial that updating the contract registry is the final step. This is the point in time where the new contract is officialy integrated with the PoS ecosystem. The state of the new and old contracts should be equivalent at the time of the registry update.
- Any events emitted by the new contract prior to setting in the registry should not be assumed to be available to clients. Most clients only start tracking contract events starting from the registry update block number.
- Events backward compatibility: Many clinets track the PoS history using events. If an event signature changes (e.g. by adding a new field), querying the event using the old signature will no longer work. It is therefore important to ensure that all clients have the knowlege of which signature to use when querying a contract. Simply put, the clients must use the correct ABI for the queried contract. One way to achieve this is to query etherscan for the ABI of the specific address, assuming the contract is verified. Another option is to use the
getAbiByContractAddress()
function from the contracts package (@orbs-network/orbs-ethereum-contracts-v2
), assuming it was updated with the address and ABI of the new contract. - Many of these script interact with both and older and newer versions of the same contract. However, in many cases, the scripts assume the same ABI for both. Beware of breaking changes, and if neccessary modify the script to use and old ABI version when interacting with an older version.
Important - all upgrade script below assume that accounts[0] (the first account derived from the mnemonic) is the migration manager, so it is able to update the contract registry.
Executes the committee contract upgrade flow:
- Locks all contracts
- Deployes a new committee contract
- Migrates the committee from the old contract to the new, using the
importMembers()
function. - Sets the new contract in the registry.
- Unlocks all contracts.
Running instructions:
- Edit
contract-deployment/upgrade-committee.ts
. ModifyCONTRACT_REGISTRY_ADDR
to contain the address of the contract registry. - Run the upgrade script:
cd contract-deployment && npm run upgrade-committee
Similarly to the main contract deployment script, the contract configration is taken from contract-deployment/config.ts
.
Executes the elections contract upgrade flow (no state migration):
- Locks all contracts.
- Deployes a new elections contract.
- Sets the elections contract in the registry.
- Unlocks all contracts.
Running instructions:
- Edit
contract-deployment/upgrade-elections.ts
. ModifyCONTRACT_REGISTRY_ADDR
to contain the address of the contract registry. - Run the upgrade script:
cd contract-deployment && npm run upgrade-elections
Similarly to the main contract deployment script, the contract configration is taken from contract-deployment/config.ts
.
- Note - no state migration is performed (vote-out and vote-unready state is reset).
Executes the guardians-registration contract upgrade flow (no state migration):
- Deployes a new guardians-registration contract.
- Migrates registered guardians from the previous contract using the
migrateGuardians()
function. - sets the new contract in the regsitry.
Running instructions:
- Edit
contract-deployment/upgrade-guardian-registration.ts
. ModifyCONTRACT_REGISTRY_ADDR
andPREVIOUS_GUARDIAN_REGISTRATION_CONTRACT_ADDR
to contain the corresponding addresses. - Run the upgrade script:
cd contract-deployment && npm run upgrade-guardians-registration
Similarly to the main contract deployment script, the contract configration is taken from contract-deployment/config.ts
.
Executes the guardians-registration contract upgrade flow (no state migration):
- Deployes a new guardians-registration contract.
- Migrates registered guardians from the previous contract using the
migrateGuardians()
function. - sets the new contract in the regsitry.
Running instructions:
- Edit
contract-deployment/upgrade-guardian-registration.ts
. ModifyCONTRACT_REGISTRY_ADDR
andPREVIOUS_GUARDIAN_REGISTRATION_CONTRACT_ADDR
to contain the corresponding addresses. - Run the upgrade script:
cd contract-deployment && npm run upgrade-guardians-registration
Similarly to the main contract deployment script, the contract configration is taken from contract-deployment/config.ts
.
Executes the upgrade flow for both fee wallet contracts (general fees and certified fees):
- Deployes two instances of the FeeWallet contract - general and certified.
- Locks the previous fee wallets.
- Migrates a given list of fee buckets (hardcoded in the script) from the old contracts to the new ones by calling the
migrateBucket()
function on the old contracts. The function transfers the amount using the new contractsacceptBucketMigration()
function. - sets the new contracts in the regsitry.
Running instructions:
- Edit
contract-deployment/upgrade-guardian-registration.ts
. ModifyCONTRACT_REGISTRY_ADDR
andBUCKETS_TO_MIGRATE
to contain the contract registry address and list of buckets to migrate respectivly. Each bucket is identifeid by the timestamp of the first second in the month it represents. - Run the upgrade script:
cd contract-deployment && npm run upgrade-guardians-registration
Similarly to the main contract deployment script, the contract configration is taken from contract-deployment/config.ts
.
Executes the upgrade flow for both reward contracts (StakingRewards and FeesAndBootstrapRewards):
- Deactivate reward distribution on both contracts using
deactivateRewardDistribution()
. This ensures that no new rewrads are distributed, and that the contracts have withdrawn the neccessary amount of funds for the previously assigned rewards. This means that the old contracts are no longer dependent on the protocol wallets, and we can safely switch their clients to the new contracts. - Deployes the new contracts.
- Activates reward distribution on the new contracts using
activateRewardDistribution(startTime=lastAssignmentTime)
.lastAssignmentTime
is obtained from the old contracts. - Updates the client of the stakingRewardsWallet to the new stakingRewards contract. The client is the address that is allowed to withdraw funds from the wallet.
- Updates the client of the bootstrapRewardsWallet to the new feesAndBootstrapRewards contract.
- Sets the new contracts in the registry.
Note - migrating existing reward balances between the old and new contracts is done in a separate step after this upgrade flow - see "Migrate rewrad balances".
Running instructions:
- Edit
contract-deployment/upgrade-guardian-registration.ts
. ModifyCONTRACT_REGISTRY_ADDR
andOLD_STAKING_REWARDS_ABI
,OLD_FEES_AND_BOOTSTRAP_REWARDS_ABI
to contain the contract registry address and ABIs of the currently deployed stakingRewrads and feesAndBootstrapRewards contracts. - Run the upgrade script:
cd contract-deployment && npm run upgrade-rewards
Similarly to the main contract deployment script, the contract configration is taken from contract-deployment/config.ts
.
Migrates reward balances from old reward contracts to the currently deployed ones:
- Builds a list of all addresses with existing balances by checking the balance of any past guardians and delegators.
- For each delegator and guardian, migrates the staking rewards balance to the new contract by calling
migrateRewardsBalance()
on the old stakingRewards contract. - For each guardian, migrate the fees and bootstrap reward balances to the new contract by calling
migrateRewardsBalance()
on the old feesAndBootstrapRewards contract.
Important - this flow assumes the both the new and old contracts share the same contract registry, and that the new contracts are set in the registry. This allows the migration function to be permissionless, as the contracts migrate the balance to the one currently set in the registry (by calling acceptRewardsBalanceMigration()
on the new contract).
Running instructions:
- Edit
contract-deployment/migrate-reward-balances.ts
. ModifyCONTRACT_REGISTRY_ADDR
,OLD_STAKING_REWARDS_ADDR
,OLD_FEES_AND_BOOTSTRAP_REWARDS_ADDR
,OLD_STAKING_REWARDS_ABI
,OLD_FEES_AND_BOOTSTRAP_REWARDS_ABI
to contain the contract registry address and ABIs of the currently deployed stakingRewrads and feesAndBootstrapRewards contracts. - Run the migration script:
cd contract-deployment && npm run migrate-reward-balances
IMPORTANT - the global configuration (the environment variables defined at the top of this readme) do no apply to the delegation migration scripts
The delegations-migration
folder contains a collection of scripts and logic for migrating delegations from Orbs PoS v1.
The v2 delegations contract has two init functions used for delegation import: initDelegation()
and importDelegations()
. Both can only be called during initialization, by the initializationAdmin.
initDelegation(from, to)
is used to import an existing delegation. It can be used at any time during initialization, and on any address, even if the address already has an existing delegation. It performs a full delegation flow (notifying the rewards and election contracts on the changes), which makes it a relativly expensive operation).importDelegations(from[], to[])
is used to initialize a batch of delegations. It can only be used for delegators without an existing delegation, and it assumes it is not a new delegation so it does not notify other contracts. It can only be used before reward distribution is activated, to avoid discrepencies in reward distribution over the time period before the import took place.
Typically, importDelegations()
should be used for the initial import, and initDelegation()
should be used to update delegations that changed since the first import.
Regardless of the flow selected (initDelegaiton/ importDelegations), the script start by listing all stake holders an v1 delegations to construct a list of delegations to import and addresses which need refreshStake()
. It then filters out delegations that are already present in the v2 delegations contract. It also takes care of guardian address conversion as defined by this contract. Additionaly it cancels any v1 delegation done by a guardian, to make sure guardians are self delegated.
The final lists of delegations are stored in delegations-migration/migrationSnapshot.json
and can be used later in a second invocation of the script if needed (instead of rebuilding the list).
Running instructions:
- Make sure the root of the repo contains a file named
deployed-contracts.json
with the following:
{
"contractRegistry": <The address of the contract registry>
}
This is the file generated by the contract deployment script, and it may contain addresses for other contracts as well. This script only needs the contract registry address.
- Make sure the
.secret
file is set with the correct mnemonic, which includes the account of the initialization admin.
-
Start a local ganache instance:
npm run start-ganache
-
Run the migration script (one of the following):
npm run migrate-delegations-local # Migration using importDelegation()
npm run migrate-delegations-diff-local # Migration using initDelegation(), to update with diff since last import
- Run one of:
npm run migrate-delegations-mainnet # Migration using importDelegation()
npm run migrate-delegations-diff-mainnet # Migration using initDelegation(), to update with diff since last import
The v1 delegation migration script described above also takes care of finding discrepencies between a stakers amount as seen by the delegations contract and the staking contract. It compares the amounts for each staker, fixes each discrepency by using the delegations contract refreshStake()
function. refreshStake()
reads the current balance from the staking contract and updates.
This process is useful regardless of v1 delegations import - it is neccessary whenever StakingContract and DelegationContract are out of sync (for example due to the delegations contract being temporary disconnected from stake change notifications). The script above can be easily altered so that it would only take care of such issues.
This section describes upgrade and migration flows which are not yet implemneted as migration scripts in this repo. Existing script can easily be modified to implement these flows.
-
Deploy the new contract.
-
Lock the existing certification contract.
-
Set the certification of all currently certified guardians using an initialization function (In the current contract version there isn't one, so it would need to be added to the new version).
-
Set the new contract in the registry.
-
Deploy the new contract.
-
Lock the existing protocol contract.
-
Set the new protocol contract state to reflect the current version, next version and upgrade timestamp of all deployment subsets (using
createDeploymentSubset()
andsetProtocolVersion()
, or a new initialization function). -
Set the new contract in the registry.
-
Lock the existing subscriptions contract.
-
Deploy the new contract. In the constructor, pass the address of the current subscriptions contract and the IDs of the virtual chains to migrate (typically all chains that exist in the previous contract). If the list of VCs is to large for a single transaction, they can be imported after deployment using
importSubscriptions()
. -
Set the new protocol contract state to reflect the current version, next version and upgrade timestamp of all deployment subsets (using
createDeploymentSubset()
andsetProtocolVersion()
, or a new initialization function). -
Set the new contract in the registry.
Careful - this flow is not yet supported by the PoS clients used by Orbs Guardians.
-
Deploy the new registry contract.
-
Set all contracts in the new registry.
-
Move all contracts to the new registry using the previous registry contract's
setNewContractRegistry
. This function can only be called by the registry admin.
This is similar to the flow described in Migrating delegations from Orbs PoS V1 delegations contract. The only differnece is that the list of existing delegations should be taken from the current delegations contract.
-
Deploy the new delegations contract.
-
Lock current delegations contract.
-
Construct a list of all current delegations (e.g. by tracking the
Delegated
event). -
Initialize the delegations in the new contract using the contract's
importDelegations()
. -
Set the new delegations contract in the registry.