Skip to content
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

rETH onboarding #506

Merged
merged 21 commits into from
Oct 21, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,42 @@ The process of adding new collaterals depends on the token type used. This is du
- if the script terminates with an error, please submit the report to the repository at https://github.com/sidestream-tech/unified-auctions-ui via an issue so that the support could be added.
- Read more about the collateral oracle configurations at `./README.md#collateral-oracle-configs`

### Onboarding not yet deployed collateral

When a completely new collateral type support is being prepared, we need to ensure that it will work even before the Maker Protocol is changed via a [`spell`](https://docs.makerdao.com/smart-contract-modules/governance-module/spell-detailed-documentation). Usually a new spell is prepared in the [spells-mainnet](https://github.com/makerdao/spells-mainnet/pulls) repository. When it is there we need to fork the repository, compile the spell and deploy it into the hardhat fork. Currently the setup is as follows:

1. `rsync` or clone the repo to the desired x86 machine (tested with Docker 19.03.12 on Ubuntu 20.04)
2. `cd` into the `core/simulations/docker` and run `docker-compose up` to start the hardhat fork in one container and another container with installed [`dapp-tools`](https://github.com/dapphub/dapptools)
3. Shell into the `spells` container
- List avialble containers `docker container ls` (copy `CONTAINER ID` of the `docker_spells`)
- Shell into the container `docker exec -it 277a8d793341 sh`
4. Fix future `Invalid argument` `nix` error via `echo 'filter-syscalls = false' >> /etc/nix/nix.conf`
5. Open `nix` shell with useful tools `nix-shell -p cacert cachix curl git jq nix gnumake nano`
6. Update `dapp --version` to the supported version (currently 0.35.0)
- Install `duppgrade` via `curl https://rari-capital.github.io/duppgrade/install | bash`
- Execute `duppgrade` and wait
7. Clone branch containing the spell
- Clone the repo, eg `git clone https://github.com/makerdao/spells-mainnet spells && cd spells`
- Checkout correct branch `git checkout CES-795`
8. Fetch all libraries (that are linked via git submodules) using `dapp update`
9. Create keystore (that will be used by dapp-tools)
- Set hardhat private key into the file `echo 'ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80' > ../private-key`
- Set password `echo '' > ../key-password`
- Import key to create a keystore folder `(printf "\n\n" && cat) | geth account import --datadir /root ../private-key`
- Press enter to finish the process
10. Prepare env vars
- `export ETH_KEYSTORE="/root/keystore"`
- `export ETH_PASSWORD="/root/key-password"`
- `export ETH_RPC_URL=http://core:8545`
- `export ETH_FROM="0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"` - the address of the key from above
- `export ETH_GAS=3000000` - might need to be adjusted based on the `make estimate` output
- `export ETH_GAS_PRICE=1000000000000`
11. Compile the spell via `make`
12. Deploy the spell via `dapp create DssSpell`
13. Copy the bitecode of the spell into the `core/bytecode/compiledSpells.json` file, which will automatically update the `Onboard new collateral` simulation
14. Run `Onboard new collateral` locally to deploy compiled bytecode and execute the spell, create vault, liquidate vault to create auction
15. Run keeper and frontend against the simulation to validate that it worked

### Collateral oracle configs

Each collateral has the source where its price is fetched from. These values are stored on the blockchain, however they are not exposed via public access methods.
Expand Down
3 changes: 3 additions & 0 deletions core/bytecode/compiledSpells.json

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion core/helpers/hardhat/erc20.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { overwriteUintMappingInAddress, runBalanceSlotDiscoveryLoopForERC20Token
export const determineBalanceSlot = async (
collateralType: CollateralType
): Promise<[string, 'vyper' | 'solidity'] | [null, null]> => {
console.info('Determining balance slot...');
const collateralConfig = getCollateralConfigByType(collateralType);
const tokenContractAddress = await getContractAddressByName(TEST_NETWORK, collateralConfig.symbol);
try {
Expand Down
21 changes: 21 additions & 0 deletions core/helpers/hardhat/overwrites.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import BigNumber from '../../src/bignumber';
import { ethers } from 'ethers';
import { getCollateralConfigByType } from '../../src/constants/COLLATERALS';
import { DAI_NUMBER_OF_DIGITS } from '../../src/constants/UNITS';
import executeTransaction from '../../src/execute';
import createStructCoder from '../../src/helpers/createStructCoder';
import { getOracleAddressByCollateralType } from '../../src/oracles';
import { overwriteUintValueInAddress } from './slotOverwrite';

export const overwriteCurrentOraclePrice = async (network: string, collateralType: string, amount: BigNumber) => {
const collateralConfig = getCollateralConfigByType(collateralType);
const oracleAddress = await getOracleAddressByCollateralType(network, collateralType);
const amoutInteger = amount.shiftedBy(DAI_NUMBER_OF_DIGITS).toFixed();
const valueWithValidity = createStructCoder().encode(['uint128', 'uint128'], ['1', amoutInteger]);
await overwriteUintValueInAddress(
oracleAddress,
collateralConfig.oracle.currentPriceSlotAddress,
valueWithValidity
);
await executeTransaction(network, 'MCD_SPOT', 'poke', [ethers.utils.formatBytes32String(collateralType)]);
};
4 changes: 2 additions & 2 deletions core/helpers/hardhat/slotOverwrite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ export const generateMappingSlotAddress = (
export const overwriteUintValueInAddress = async (
address: string,
slotAddress: string,
newValue: BigNumber,
newValue: BigNumber | string,
provider: EthereumProvider = hre.network.provider
) => {
const hexValue = formatToHex(newValue, 32);
const hexValue = typeof newValue === 'string' ? newValue : formatToHex(newValue, 32);
const storageToWrite = [address, slotAddress, hexValue];
await provider.send('hardhat_setStorageAt', storageToWrite);
};
Expand Down
4 changes: 3 additions & 1 deletion core/simulations/config.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { Simulation } from './types';
import debtAuctionSimulation from './configs/createDebtAuctionSimulation';
import surplusAuctionBlockchain from './configs/surplusAuctionSimulation';
import wstethAuctionSimulation from './configs/wstethAuctionSimulation';
import surplusAuctionSimulation from './configs/createSurplusAcutionSimulation';
import blockWithVaultsInAllStates from './configs/blocksWithVaultsInAllStates';
import vaultLiquidation from './configs/vaultLiquidation';
import { Simulation } from './types';
import onboardNewCollateral from './configs/onboardNewCollateral';

export const SIMULATIONS: Simulation[] = [
debtAuctionSimulation,
Expand All @@ -13,4 +14,5 @@ export const SIMULATIONS: Simulation[] = [
wstethAuctionSimulation,
blockWithVaultsInAllStates,
vaultLiquidation,
onboardNewCollateral,
];
76 changes: 76 additions & 0 deletions core/simulations/configs/onboardNewCollateral.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import BigNumber from '../../src/bignumber';
import { warpTime, resetNetworkAndSetupWallet } from '../../helpers/hardhat/network';
import { addDaiToBalance } from '../../helpers/hardhat/balance';
import { Simulation } from '../types';
import { getAllCollateralTypes } from '../../src/constants/COLLATERALS';
import { collectStabilityFees, fetchVault, liquidateVault } from '../../src/vaults';
import { TEST_NETWORK } from '../../helpers/constants';
import createVaultWithCollateral, {
calculateMinCollateralAmountToOpenVault,
} from '../helpers/createVaultWithCollateral';
import deploySpell, { getAllSpellNames } from '../helpers/deploySpell';
import executeSpell from '../helpers/executeSpell';
import { getCurrentOraclePriceByCollateralType } from '../../src/oracles';
import { overwriteCurrentOraclePrice } from '../../helpers/hardhat/overwrites';
import promptToSelectOneOption from '../helpers/promptToSelectOneOption';

const simulation: Simulation = {
title: 'Onboard new collateral',
steps: [
{
title: 'Reset blockchain fork and add balances',
entry: async () => {
await resetNetworkAndSetupWallet(15791971);
await addDaiToBalance();
},
},
{
title: 'Deploy the spell',
entry: async () => {
const spellName = await promptToSelectOneOption(
'Select the spell you want to deploy',
getAllSpellNames()
);
const spellAddress = await deploySpell(TEST_NETWORK, spellName);
return {
spellAddress,
};
},
},
{
title: 'Execute the spell',
entry: async context => {
await executeSpell(context.spellAddress);
},
},
{
title: 'Create new auction',
entry: async () => {
const collateralType = await promptToSelectOneOption(
'Select the collateral symbol to add to the VAT',
getAllCollateralTypes()
);
// overwrite oracle price
await overwriteCurrentOraclePrice(TEST_NETWORK, collateralType, new BigNumber(1000));
const oraclePrice = await getCurrentOraclePriceByCollateralType(TEST_NETWORK, collateralType);
console.info(`New ${collateralType} oracle price is ${oraclePrice.toFixed()} DAI`);
// create and liquidate vault
const collateralOwned = await calculateMinCollateralAmountToOpenVault(collateralType);
const vaultId = await createVaultWithCollateral(collateralType, collateralOwned);
await warpTime(60 * 24 * 30, 60);
await collectStabilityFees(TEST_NETWORK, collateralType);
const vault = await fetchVault(TEST_NETWORK, vaultId);
await liquidateVault(TEST_NETWORK, vault.collateralType, vault.address);
},
},
{
title: 'Skip time',
entry: async context => {
await warpTime(60, 60);
return context;
},
},
],
};

export default simulation;
56 changes: 12 additions & 44 deletions core/simulations/configs/vaultLiquidation.ts
Original file line number Diff line number Diff line change
@@ -1,53 +1,13 @@
import { warpTime, resetNetworkAndSetupWallet } from '../../helpers/hardhat/network';
import { addDaiToBalance, addMkrToBalance } from '../../helpers/hardhat/balance';
import { Simulation } from '../types';
import prompts from 'prompts';
import COLLATERALS from '../../src/constants/COLLATERALS';
import { collectStabilityFees, fetchVault, liquidateVault } from '../../src/vaults';
import { TEST_NETWORK } from '../../helpers/constants';
import createVaultWithCollateral, {
calculateMinCollateralAmountToOpenVault,
getLiquidatableCollateralTypes,
} from '../helpers/createVaultWithCollateral';

const UNSUPPORTED_COLLATERAL_TYPES = [
'CRVV1ETHSTETH-A', // collateral handled differently
'UNIV2DAIUSDC-A', // Liquidation limit too high (fails with "Dog/liquidation-limit-hit")
'WSTETH-B', // does not accumulate stability fee rate at all.
];

export const getLiquidatableCollateralTypes = () => {
return Object.keys(COLLATERALS).filter(collateralType => !UNSUPPORTED_COLLATERAL_TYPES.includes(collateralType));
};

const getCollateralType = async () => {
const { collateralType } = await prompts([
{
type: 'select',
name: 'collateralType',
message: 'Select the collateral symbol to add to the VAT.',
choices: getLiquidatableCollateralTypes()
.sort()
.map(collateral => ({
title: collateral,
value: collateral,
})),
},
]);
return collateralType;
};

const getBaseContext = async () => {
const collateralType = await getCollateralType();
const decimals = COLLATERALS[collateralType].decimals;
const collateralOwned = await calculateMinCollateralAmountToOpenVault(collateralType);

console.info(`Collateral in the VAT initially: ${collateralOwned.toFixed()}`);
return {
collateralType,
decimals,
collateralOwned,
};
};
import promptToSelectOneOption from '../helpers/promptToSelectOneOption';

const simulation: Simulation = {
title: 'Simulate liquidation Auctions',
Expand All @@ -56,13 +16,21 @@ const simulation: Simulation = {
title: 'Reset blockchain fork',
entry: async () => {
await resetNetworkAndSetupWallet();
return getBaseContext();
const collateralType = await promptToSelectOneOption(
'Select the collateral symbol to add to the VAT.',
getLiquidatableCollateralTypes()
);
return {
collateralType,
};
},
},
{
title: 'Create the vault',
entry: async context => {
const latestVaultId = await createVaultWithCollateral(context.collateralType, context.collateralOwned);
const collateralOwned = await calculateMinCollateralAmountToOpenVault(context.collateralType);
console.info(`Minimum collateral amount to open vault: ${collateralOwned.toFixed()}`);
const latestVaultId = await createVaultWithCollateral(context.collateralType, collateralOwned);
return { ...context, latestVaultId };
},
},
Expand Down
3 changes: 3 additions & 0 deletions core/simulations/docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
FROM vulcanize/dapptools:v0.30.0-v1.10.16-statediff-3.0.2a

ENTRYPOINT ["tail", "-f", "/dev/null"]
21 changes: 21 additions & 0 deletions core/simulations/docker/docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
version: '3'

services:

spells:
restart: "no"
build:
context: .

core:
restart: "no"
ports:
- '8545:8545'
expose:
- 8545
env_file:
- '../../.env'
build:
context: ../../../
dockerfile: ./core/Dockerfile
target: hardhat
16 changes: 14 additions & 2 deletions core/simulations/helpers/createVaultWithCollateral.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ethers } from 'ethers';
import BigNumber from '../../src/bignumber';
import { setCollateralInVat } from '../../helpers/hardhat/balance';
import { getCollateralConfigByType } from '../../src/constants/COLLATERALS';
import BigNumber from '../../src/bignumber';
import { changeVaultContents, fetchVault, openVault, fetchVaultCollateralParameters } from '../../src/vaults';
import { HARDHAT_PUBLIC_KEY, TEST_NETWORK } from '../../helpers/constants';
import {
Expand All @@ -17,9 +18,20 @@ import {
} from '../../src/wallet';
import { MAX } from '../../src/constants/UNITS';
import { CollateralConfig, CollateralType } from '../../src/types';
import { ethers } from 'ethers';
import { roundDownToFirstSignificantDecimal, roundUpToFirstSignificantDecimal } from '../../helpers/hex';
import { determineBalanceSlot, setCollateralInWallet } from '../../helpers/hardhat/erc20';
import { getAllCollateralTypes } from '../../src/constants/COLLATERALS';

const UNSUPPORTED_COLLATERAL_TYPES = [
'CRVV1ETHSTETH-A', // Collateral handled differently
'UNIV2DAIUSDC-A', // Liquidation limit too high (fails with "Dog/liquidation-limit-hit")
'WSTETH-B', // Does not accumulate stability fee rate at all
'RETH-A', // [temporary] this collateral is not yet deployed, tested via different flow
];

export const getLiquidatableCollateralTypes = () => {
return getAllCollateralTypes().filter(collateralType => !UNSUPPORTED_COLLATERAL_TYPES.includes(collateralType));
};

const setAndCheckCollateralInVat = async (collateralType: CollateralType, collateralOwned: BigNumber) => {
console.info(`Setting ${collateralType} balance in VAT...`);
Expand Down
25 changes: 25 additions & 0 deletions core/simulations/helpers/deploySpell.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { ethers } from 'ethers';
import getSigner from '../../src/signer';
import compiledSpells from '../../bytecode/compiledSpells.json';

export const getAllSpellNames = function (): string[] {
return Object.keys(compiledSpells).sort();
};

const deploySpell = async function (network: string, name: string): Promise<string> {
console.info(`Deploying spell "${name}"`);

if (!(name in compiledSpells)) {
throw new Error(`spel "${name}" not found in compiled spells`);
}
const bytecode: string = (compiledSpells as Record<string, string>)[name];

const signer = await getSigner(network);
const factory = new ethers.ContractFactory([], bytecode, signer);
const contract = await factory.deploy();
await contract.deployTransaction.wait();
console.info(`Spell has been deployed at ${contract.address}`);
return contract.address;
};

export default deploySpell;
Loading