diff --git a/.eslintrc.json b/.eslintrc.json index 291fa48..58c2f6f 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -17,6 +17,7 @@ "@typescript-eslint/consistent-type-assertions": "off", "@typescript-eslint/no-unused-expressions": "off", "@typescript-eslint/space-before-function-paren": "off", + "@typescript-eslint/no-var-requires": "off", "@typescript-eslint/member-delimiter-style": [ "error", { diff --git a/addresses.json b/addresses.json index e80ae62..29c2fce 100644 --- a/addresses.json +++ b/addresses.json @@ -1,11 +1,11 @@ { "42069": { "core": { - "PHO": "0x8be34D7830d446747B95Aa3ace0eaA4FF19b2007", - "TON": "0x45D97250E4F261c4116b2ebDBA1b963e55B068d3", - "Kernel": "0xB49BafEc7095d2d337A9e7DaaAE2DAA0F73d5b5A", - "ModuleManager": "0xB56Ab229bD4d9459e61BC7Bb34E394Ed6c1a8e39", - "ChainlinkPriceFeed": "0x36A7f9ca3EBD83E00c1bca3A1db378bcF039Bf85", + "PHO": "0xB7ca895F81F20e05A5eb11B05Cbaab3DAe5e23cd", + "TON": "0xd0EC100F1252a53322051a95CF05c32f0C174354", + "Kernel": "0x2d13826359803522cCe7a4Cfa2c1b582303DD0B4", + "ModuleManager": "0xCa57C1d3c2c35E667745448Fef8407dd25487ff8", + "ChainlinkPriceFeed": "0xaB837301d12cDc4b97f1E910FC56C9179894d9cf", "CurvePool": "0xe9123CBC5d1EA65301D417193c40A72Ac8D53501" }, "modules": { @@ -14,7 +14,9 @@ "StablecoinDepositModuleFRAX": "0x70F804060040bAb7E443b0F4334d356B7a6D4bAc", "StablecoinDepositModuleLUSD": "0x009eb8A8a1B7C4d48C721E47894346477d2f6647", "MapleDepositModuleUSDC": "0x7511fAE41153Fad8A569d7Ebdcc76c120D3d5AAb", - "ZCBModuleUSDC": "0x7c098E457DA8108527bdba11da981100d5293A92" + "ZCBModuleUSDC": "0x7c098E457DA8108527bdba11da981100d5293A92", + "CDPPool_wstETH": "0x4ff1f64683785E0460c24A4EF78D582C2488704f", + "wstETHCDPWrapper": "0x0F527785e39B22911946feDf580d87a4E00465f0" } }, "11155111": { @@ -27,4 +29,4 @@ }, "modules": {} } -} \ No newline at end of file +} diff --git a/cli/abis.ts b/cli/abis.ts index b4177c4..775c530 100644 --- a/cli/abis.ts +++ b/cli/abis.ts @@ -6,6 +6,8 @@ import abiMM from '../build/abis/ModuleManager.sol/ModuleManager.json' import abiKernel from '../build/abis/Kernel.sol/Kernel.json' import abiCLPF from '../build/abis/ChainlinkPriceFeed.sol/ChainlinkPriceFeed.json' import abiCP from '../build/abis/ICurvePool.sol/ICurvePool.json' +import abiWstETHCDPWrapper from '../build/abis/wstETHCDPWrapper.sol/wstETHCDPWrapper.json' +import abiCDPPool from '../build/abis/CDPPool.sol/CDPPool.json' export const loadABI = (name: string): ContractInterface => { switch (name) { @@ -21,6 +23,10 @@ export const loadABI = (name: string): ContractInterface => { return abiCLPF.abi case 'CurvePool': return abiCP.abi + case 'wstETHCDPWrapper': + return abiWstETHCDPWrapper.abi + case 'CDPPool': + return abiCDPPool.abi default: return 'ERROR_NO_ABI_FOUND' // TODO - DK - Improve error } diff --git a/cli/cli.ts b/cli/cli.ts index 861036a..c81292f 100755 --- a/cli/cli.ts +++ b/cli/cli.ts @@ -1,7 +1,7 @@ #!/usr/bin/env ts-node import * as dotenv from 'dotenv' import yargs from 'yargs' -import { evmCommand, coreCommand, deployCommand } from './commands' +import { evmCommand, coreCommand, deployCommand, modulesCommand } from './commands' import { adminCommand } from './commands/admin' import { cliOpts } from './defaults' @@ -24,5 +24,6 @@ yargs .command(evmCommand) .command(adminCommand) .command(deployCommand) + .command(modulesCommand) .demandCommand(1, 'Choose a command from the above list') - .help().argv + .help().argv.catch diff --git a/cli/commands/deploy.ts b/cli/commands/deploy.ts index 398fc03..46dc033 100644 --- a/cli/commands/deploy.ts +++ b/cli/commands/deploy.ts @@ -1,5 +1,4 @@ import { exec } from 'child_process' -import { copyFile } from 'fs/promises' import { writeFileSync, readdirSync, lstatSync, existsSync, mkdirSync } from 'fs' import path from 'path' import { logger } from '../logging' @@ -42,7 +41,7 @@ export const deploy = async (cli: CLIEnvironment, cliArgs: CLIArgs): Promise { - await copyFile('deployments/addresses_last.example.json', 'deployments/addresses_last.json', 0) - const tempAddresses = await import('../../deployments/addresses_last.json') + // await copyFile('deployments/addresses_last.example.json', 'deployments/addresses_last.json', 0) + const tempAddresses = require('../../deployments/addresses_last.json') const updated: MasterAddresses = prepareAddressesJson(addresses, p.networkId) const latestLog: string | undefined = getMostRecentFile( `broadcast/${p.contractName}.s.sol/${getCorrectNetworkId(p.networkId)}/` @@ -124,15 +123,15 @@ export async function updateAddresses(p: AddressParams): Promise { resolve({ updated, tempAddresses }) }).then((res) => { const { updated, tempAddresses } = res - writeFileSync('addresses.json', JSON.stringify(updated), { flag: 'w+' }) - writeFileSync('deployments/addresses_last.json', JSON.stringify(tempAddresses), { flag: 'w+' }) + writeFileSync('addresses.json', JSON.stringify(updated)) + writeFileSync('deployments/addresses_last.json', JSON.stringify(tempAddresses)) if (!existsSync(`deployments/${p.networkId}`)) { mkdirSync(`deployments/${p.networkId}`) } writeFileSync( `deployments/${p.networkId}/addresses_latest.json`, JSON.stringify(tempAddresses), - { flag: 'w+' } + { flag: 'w' } ) }) } diff --git a/cli/commands/evm/mine.ts b/cli/commands/evm/mine.ts new file mode 100644 index 0000000..fdda2f0 --- /dev/null +++ b/cli/commands/evm/mine.ts @@ -0,0 +1,26 @@ +import yargs, { Argv } from 'yargs' +import { loadEnv } from '../../env' +import { CLIArgs, CLIEnvironment } from '../../types' +import { execute } from '../deploy' + +const buildHelp = (): string => { + const help = 'To fast forward -> evm fast-forward [seconds] [minutes] [hours]' + return help +} + +export const mine = async (cli: CLIEnvironment): Promise => { + await execute( + `curl -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"evm_mine","params":[],"id":67}' ${cli.providerUrl}` + ) +} + +export const mineCommand = { + command: 'mine', + describe: 'deploy contracts from deployParams.json', + builder: (yargs: Argv): yargs.Argv => { + return yargs.usage(buildHelp()) + }, + handler: async (argv: CLIArgs): Promise => { + return await mine(await loadEnv(argv)) + } +} diff --git a/cli/commands/index.ts b/cli/commands/index.ts index 56c0ecb..37a58b1 100644 --- a/cli/commands/index.ts +++ b/cli/commands/index.ts @@ -1,5 +1,6 @@ import { coreCommand } from './core' import { deployCommand } from './deploy' import { evmCommand } from './evm' +import { modulesCommand } from './modules' -export { coreCommand, deployCommand, evmCommand } +export { coreCommand, deployCommand, evmCommand, modulesCommand } diff --git a/cli/commands/modules/cdp/index.ts b/cli/commands/modules/cdp/index.ts new file mode 100644 index 0000000..82f4feb --- /dev/null +++ b/cli/commands/modules/cdp/index.ts @@ -0,0 +1,30 @@ +import yargs, { Argv } from 'yargs' +import { overviewCommand } from './read/overview' +import { positionCommand } from './read/position' +import { addCollateralCommand } from './write/addCollateral' +import { addDebtCommand } from './write/addDebt' +import { closeCommand } from './write/close' +import { liquidateCommand } from './write/liquidate' +import { openCommand } from './write/open' +import { removeCollateralCommand } from './write/removeCollateral' +import { removeDebtCommand } from './write/removeDebt' + +export const cdpCommand = { + command: 'cdp', + describe: 'Photon protocol modules', + builder: (yargs: Argv): yargs.Argv => { + return yargs + .command(openCommand) + .command(addCollateralCommand) + .command(removeCollateralCommand) + .command(addDebtCommand) + .command(removeDebtCommand) + .command(closeCommand) + .command(liquidateCommand) + .command(overviewCommand) + .command(positionCommand) + }, + handler: (): void => { + yargs.showHelp() + } +} diff --git a/cli/commands/modules/cdp/read/overview.ts b/cli/commands/modules/cdp/read/overview.ts new file mode 100644 index 0000000..16b5991 --- /dev/null +++ b/cli/commands/modules/cdp/read/overview.ts @@ -0,0 +1,54 @@ +import Table from 'cli-table3' +import { logger } from 'ethers' +import { moduleDictionary } from '../../../../defaults' +import { loadEnv } from '../../../../env' +import { getModuleAddress, toReadablePrice } from '../../../../helpers' +import { CLIArgs, CLIEnvironment } from '../../../../types' +import { execute } from '../../../deploy' + +const getOverview = async (cli: CLIEnvironment, cliArgs: CLIArgs): Promise => { + const moduleName = moduleDictionary.cdp[cliArgs.tokenType].default + const cdpAddress = getModuleAddress(cliArgs.c, 'cdp', cliArgs.tokenType, 'default') + + const moduleData = await cli.contracts.ModuleManager.modules(cdpAddress) + const [phoMinted, startTime, status] = moduleData.slice(-3) + const balances = await execute( + `cast call --rpc-url ${cli.providerUrl} ${cdpAddress} "pool()((uint256,uint256))"` + ) + const feesCollected = await execute( + `cast call --rpc-url ${cli.providerUrl} ${cdpAddress} "feesCollected()(uint256)"` + ) + + const table = new Table({ + head: [moduleName, 'Result'], + colWidths: [30, 50] + }) + + const [totalDebt, totalCollateral]: string[] = balances + .substring(1, balances.length - 1) + .split(',') + const collRatio: string = await execute( + `cast call --rpc-url ${cli.providerUrl} ${cdpAddress} "computeCR(uint256,uint256)(uint256)" ${totalCollateral} ${totalDebt}` + ) + + table.push(['Address', cdpAddress]) + table.push(['PHO Mined', toReadablePrice(phoMinted.toLocaleString())]) + table.push([ + 'startTime', + startTime.toString().concat(` (${new Date(Number(startTime) * 1000).toLocaleDateString()})`) + ]) + table.push(['status', status.toString()]) + table.push(['Total Collateral', toReadablePrice(totalCollateral)]) + table.push(['Total Debt', toReadablePrice(totalDebt)]) + table.push(['Collateral Ratio', collRatio.slice(0, -3).concat('%')]) + table.push(['feesCollected', toReadablePrice(feesCollected)]) + logger.info(table.toString()) +} + +export const overviewCommand = { + command: 'overview [tokenType]', + describe: 'CDP Mechanism overview', + handler: async (argv: CLIArgs): Promise => { + return await getOverview(await loadEnv(argv), argv) + } +} diff --git a/cli/commands/modules/cdp/read/position.ts b/cli/commands/modules/cdp/read/position.ts new file mode 100644 index 0000000..3373c53 --- /dev/null +++ b/cli/commands/modules/cdp/read/position.ts @@ -0,0 +1,40 @@ +import Table from 'cli-table3' +import { logger } from 'ethers' +import { moduleDictionary } from '../../../../defaults' +import { loadEnv } from '../../../../env' +import { getModuleAddress, toReadablePrice } from '../../../../helpers' +import { CLIArgs, CLIEnvironment } from '../../../../types' +import { execute } from '../../../deploy' + +const getPosition = async (cli: CLIEnvironment, cliArgs: CLIArgs): Promise => { + const cdpOwner: string = cliArgs.cdpOwner + const moduleName = moduleDictionary.cdp[cliArgs.tokenType].default + const cdpAddress = getModuleAddress(cliArgs.c, 'cdp', cliArgs.tokenType, 'default') + + const cdpData: string = await execute( + `cast call --rpc-url ${cli.providerUrl} ${cdpAddress} "cdps(address)((uint256,uint256))" ${cdpOwner}` + ) + const [debt, collateral]: string[] = cdpData.substring(1, cdpData.length - 1).split(',') + const collRatio: string = await execute( + `cast call --rpc-url ${cli.providerUrl} ${cdpAddress} "computeCR(uint256,uint256)(uint256)" ${collateral} ${debt}` + ) + + const table = new Table({ + head: [moduleName, 'Result'], + colWidths: [30, 50] + }) + + table.push(['CDP Owner', cdpOwner]) + table.push(['Debt', toReadablePrice(debt)]) + table.push(['Collateral', toReadablePrice(collateral)]) + table.push(['Collateral Ratio', collRatio.slice(0, -3).concat('%')]) + logger.info(table.toString()) +} + +export const positionCommand = { + command: 'position [tokenType] [cdpOwner]', + describe: 'Get information regarding an open position', + handler: async (argv: CLIArgs): Promise => { + return await getPosition(await loadEnv(argv), argv) + } +} diff --git a/cli/commands/modules/cdp/write/addCollateral.ts b/cli/commands/modules/cdp/write/addCollateral.ts new file mode 100644 index 0000000..cabaeaf --- /dev/null +++ b/cli/commands/modules/cdp/write/addCollateral.ts @@ -0,0 +1,106 @@ +import { ethers } from 'ethers' +import { moduleDictionary, tokenAddresses } from '../../../../defaults' +import { loadEnv } from '../../../../env' +import { getModuleAddress } from '../../../../helpers' +import { logger } from '../../../../logging' +import { CDPCollateralParams, CLIArgs, CLIEnvironment } from '../../../../types' +import { execute } from '../../../deploy' +import addresses from '../../../../../addresses.json' + +const getParams = (cliArgs: CLIArgs): CDPCollateralParams => { + let { _: parameters, c: networkId } = cliArgs + parameters = parameters.slice(3) + const collateralToken = parameters[0] + if (!moduleDictionary.cdp[collateralToken]) { + logger.error('addCollateral: Collateral token does not have a corresponding CDP') + return {} as CDPCollateralParams + } + const contractAddress: string = getModuleAddress(networkId, 'cdp', collateralToken, 'deposit') + + if (collateralToken === 'wsteth') { + parameters = parameters.slice(1) + if (!['steth', 'weth', 'eth'].includes(parameters[0])) { + logger.error('Deposit token is not supported') + return {} as CDPCollateralParams + } + } + + if (parameters.length !== 2) { + logger.error('addCollateral: Not enough parameters were supplied') + return {} as CDPCollateralParams + } + + if (isNaN(parameters[1])) { + logger.error('addCollateral: parameters supplied are in the wrong type') + return {} as CDPCollateralParams + } + + const collateralAmount = ethers.utils.parseUnits(parameters[1], 18) + + return { + contractAddress, + collateralToken, + depositToken: parameters[0], + collateralAmount + } +} + +const addCollateral = async (cli: CLIEnvironment, cliArgs: CLIArgs): Promise => { + const params: CDPCollateralParams = getParams(cliArgs) + if (!params.collateralToken) { + logger.error('addCollateral: bad parameters') + return + } + if (params.collateralToken === 'wsteth') { + return await depositWithWrapper(params, cli) + } +} + +const depositWithWrapper = async ( + params: CDPCollateralParams, + cli: CLIEnvironment +): Promise => { + const depositTokenAddress: string = tokenAddresses[params.depositToken] + if (!depositTokenAddress) { + logger.error('addCollateral: deposit token address not found') + return + } + + if (cli.argv.c === 42069) { + const approveCommand: string = `cast send --rpc-url ${ + cli.providerUrl + } ${depositTokenAddress} "approve(address,uint256)" ${ + params.contractAddress + } ${params.collateralAmount.toString()} --from ${cli.wallet.address} --json` + const res = JSON.parse(await execute(approveCommand)) + if (res.status === '0x1') { + logger.info( + `${ + cli.wallet.address + } approved ${params.collateralAmount.toString()} for ${depositTokenAddress}` + ) + } + } + + const addCollateralCommand = `cast send --rpc-url ${cli.providerUrl} ${ + params.contractAddress + } "addCollateral(uint256,address)" ${params.collateralAmount.toString()} ${depositTokenAddress} --from ${ + cli.wallet.address + } --json` + const receipt = JSON.parse(await execute(addCollateralCommand)) + if (receipt.status === '0x1') { + logger.info(`Added collateral to position for ${cli.wallet.address} successfully.`) + const cdpAddress: string = addresses[cli.argv.c].modules.CDPPool_wstETH + const positionCommand: string = `cast call --rpc-url ${cli.providerUrl} ${cdpAddress} "cdps(address)((uint256,uint256))" ${cli.wallet.address}` + const positionReceipt: string = await execute(positionCommand) + logger.info(positionReceipt) + } +} + +export const addCollateralCommand = { + command: 'add-collateral', + describe: 'Add collateral to a position', + handler: async (argv: CLIArgs): Promise => { + return await addCollateral(await loadEnv(argv), argv) + } +} diff --git a/cli/commands/modules/cdp/write/addDebt.ts b/cli/commands/modules/cdp/write/addDebt.ts new file mode 100644 index 0000000..f0a1942 --- /dev/null +++ b/cli/commands/modules/cdp/write/addDebt.ts @@ -0,0 +1,73 @@ +import { ethers } from 'ethers' +import { moduleDictionary } from '../../../../defaults' +import { loadEnv } from '../../../../env' +import { getModuleAddress } from '../../../../helpers' +import { logger } from '../../../../logging' +import { CDPDebtParams, CLIArgs, CLIEnvironment } from '../../../../types' +import { execute } from '../../../deploy' +import addresses from '../../../../../addresses.json' + +const getParams = (cliArgs: CLIArgs): CDPDebtParams => { + let { _: parameters, c: networkId } = cliArgs + parameters = parameters.slice(3) + const collateralToken = parameters[0] + if (!moduleDictionary.cdp[collateralToken]) { + logger.error('addDebt: Collateral token does not have a corresponding CDP') + return {} as CDPDebtParams + } + const contractAddress: string = getModuleAddress(networkId, 'cdp', collateralToken, 'default') + + if (collateralToken === 'wsteth') { + parameters = parameters.slice(1) + } + + if (parameters.length !== 1) { + logger.error('addDebt: Not enough parameters were supplied') + return {} as CDPDebtParams + } + + if (isNaN(parameters[0])) { + logger.error('addDebt: parameters supplied are in the wrong type') + return {} as CDPDebtParams + } + + const debtAmount = ethers.utils.parseUnits(parameters[0], 18) + + return { + contractAddress, + collateralToken, + depositToken: '', + debtAmount + } +} + +const addDebt = async (cli: CLIEnvironment, cliArgs: CLIArgs): Promise => { + const params: CDPDebtParams = getParams(cliArgs) + if (!params.collateralToken) { + logger.error('addDebt: bad parameters') + return + } + const moduleName: string = moduleDictionary.cdp[params.collateralToken].default + const cdpAddress: string = addresses[cli.argv.c].modules[moduleName] + + const addDebtCommand: string = `cast send --rpc-url ${ + cli.providerUrl + } ${cdpAddress} "addDebt(uint256)" ${params.debtAmount.toString()} --from ${ + cli.wallet.address + } --json` + const receipt = JSON.parse(await execute(addDebtCommand)) + if (receipt.status === '0x1') { + logger.info(`Added debt to position for ${cli.wallet.address} successfully.`) + const positionCommand: string = `cast call --rpc-url ${cli.providerUrl} ${cdpAddress} "cdps(address)((uint256,uint256))" ${cli.wallet.address}` + const positionReceipt = await execute(positionCommand) + logger.info(positionReceipt) + } +} + +export const addDebtCommand = { + command: 'add-debt', + describe: 'Add debt to a position', + handler: async (argv: CLIArgs): Promise => { + return await addDebt(await loadEnv(argv), argv) + } +} diff --git a/cli/commands/modules/cdp/write/close.ts b/cli/commands/modules/cdp/write/close.ts new file mode 100644 index 0000000..d9e81ad --- /dev/null +++ b/cli/commands/modules/cdp/write/close.ts @@ -0,0 +1,64 @@ +import { moduleDictionary } from '../../../../defaults' +import { loadEnv } from '../../../../env' +import { getModuleAddress } from '../../../../helpers' +import { logger } from '../../../../logging' +import { CDPBaseParams, CLIArgs, CLIEnvironment } from '../../../../types' +import { execute } from '../../../deploy' +import addresses from '../../../../../addresses.json' + +const getParams = (cliArgs: CLIArgs): CDPBaseParams => { + let { _: parameters, c: networkId } = cliArgs + parameters = parameters.slice(3) + const collateralToken = parameters[0] + if (!moduleDictionary.cdp[collateralToken]) { + logger.error('close: Collateral token does not have a corresponding CDP') + return {} as CDPBaseParams + } + const contractAddress: string = getModuleAddress(networkId, 'cdp', collateralToken, 'default') + + return { + contractAddress, + collateralToken, + depositToken: '' + } +} + +const close = async (cli: CLIEnvironment, cliArgs: CLIArgs): Promise => { + const params: CDPBaseParams = getParams(cliArgs) + if (!params.collateralToken) { + logger.error('close: bad parameters') + return + } + const { PHO: phoAddress, Kernel: kernelAddress }: Record = + addresses[cli.argv.c].core + const moduleName: string = moduleDictionary.cdp[params.collateralToken].default + const cdpAddress: string = addresses[cli.argv.c].modules[moduleName] + + const positionCommand: string = `cast call --rpc-url ${cli.providerUrl} ${cdpAddress} "cdps(address)((uint256,uint256))" ${cli.wallet.address}` + const positionResponse = await execute(positionCommand) + const debtAmount: string = positionResponse + .substring(1, positionResponse.length - 1) + .split(',')[0] + + if (cli.argv.c === 42069) { + const approveCommand: string = `cast send --rpc-url ${cli.providerUrl} ${phoAddress} "approve(address,uint256)" ${kernelAddress} ${debtAmount} --from ${cli.wallet.address} --json` + const res = JSON.parse(await execute(approveCommand)) + if (res.status === '0x1') { + logger.info(`${cli.wallet.address} approved ${debtAmount} for ${params.contractAddress}`) + } + } + + const closeCommand: string = `cast send --rpc-url ${cli.providerUrl} ${cdpAddress} "close()" --from ${cli.wallet.address} --json` + const receipt = JSON.parse(await execute(closeCommand)) + if (receipt.status === '0x1') { + logger.info(`Closed position for ${cli.wallet.address} successfully.`) + } +} + +export const closeCommand = { + command: 'close', + describe: 'close a position', + handler: async (argv: CLIArgs): Promise => { + return await close(await loadEnv(argv), argv) + } +} diff --git a/cli/commands/modules/cdp/write/liquidate.ts b/cli/commands/modules/cdp/write/liquidate.ts new file mode 100644 index 0000000..f96955d --- /dev/null +++ b/cli/commands/modules/cdp/write/liquidate.ts @@ -0,0 +1,95 @@ +import { moduleDictionary } from '../../../../defaults' +import { loadEnv } from '../../../../env' +import { getModuleAddress } from '../../../../helpers' +import { logger } from '../../../../logging' +import { CDPLiquidationParams, CLIArgs, CLIEnvironment } from '../../../../types' +import { execute } from '../../../deploy' +import addresses from '../../../../../addresses.json' + +const getParams = (cli: CLIEnvironment, cliArgs: CLIArgs): CDPLiquidationParams => { + let { _: parameters, c: networkId } = cliArgs + parameters = parameters.slice(3) + const collateralToken = parameters[0] + if (!moduleDictionary.cdp[collateralToken]) { + logger.error('liquidate: Collateral token does not have a corresponding CDP') + return {} as CDPLiquidationParams + } + const contractAddress: string = getModuleAddress(networkId, 'cdp', collateralToken, 'default') + const cdpOwner = parameters[1] + + if (!cdpOwner) { + logger.error('liquidate: missing cdp owner address') + return {} as CDPLiquidationParams + } + + return { + contractAddress, + collateralToken, + depositToken: '', + cdpOwner, + liquidator: cli.wallet.address + } +} + +const liquidate = async (cli: CLIEnvironment, cliArgs: CLIArgs): Promise => { + const params: CDPLiquidationParams = getParams(cli, cliArgs) + if (!params.collateralToken) { + logger.error('liquidate: bad parameters') + return + } + const { PHO: phoAddress, Kernel: kernelAddress }: Record = + addresses[cli.argv.c].core + const moduleName: string = moduleDictionary.cdp[params.collateralToken].default + const cdpAddress: string = addresses[cli.argv.c].modules[moduleName] + + const positionCommand: string = `cast call --rpc-url ${cli.providerUrl} ${cdpAddress} "cdps(address)((uint256,uint256))" ${params.cdpOwner}` + const positionResponse = await execute(positionCommand) + const [debtAmount, collateralAmount]: string[] = positionResponse + .substring(1, positionResponse.length - 1) + .split(',') + + const collRatio: string = await execute( + `cast call --rpc-url ${cli.providerUrl} ${cdpAddress} "computeCR(uint256,uint256)(uint256)" ${collateralAmount} ${debtAmount}` + ) + const minCR: string = await execute( + `cast call --rpc-url ${cli.providerUrl} ${cdpAddress} "minCR()(uint256)"` + ) + + if (Number(collRatio) >= Number(minCR)) { + logger.error( + `Collateral ratio is ${collRatio.substring( + 0, + 3 + )}% and not in liquidation zone. Liquidation aborted` + ) + return + } + + logger.info(`Collateral Ratio: ${collRatio.substring(0, 3)}%. Executing liquidation...`) + + if (cli.argv.c === 42069) { + const approveCommand: string = `cast send --rpc-url ${cli.providerUrl} ${phoAddress} "approve(address,uint256)" ${kernelAddress} ${debtAmount} --from ${params.liquidator} --json` + const res = JSON.parse(await execute(approveCommand)) + if (res.status === '0x1') { + logger.info( + `${params.liquidator} approved ${debtAmount.toString()} for ${params.contractAddress}` + ) + } + } + + const liquidateCommand: string = `cast send --rpc-url ${cli.providerUrl} ${cdpAddress} "liquidate(address)" ${params.cdpOwner} --from ${params.liquidator} --json` + const receipt = JSON.parse(await execute(liquidateCommand)) + if (receipt.status === '0x1') { + logger.info( + `Liquidated position for ${cli.wallet.address} by ${params.liquidator} successfully.` + ) + } +} + +export const liquidateCommand = { + command: 'liquidate', + describe: 'liquidate a position', + handler: async (argv: CLIArgs): Promise => { + return await liquidate(await loadEnv(argv), argv) + } +} diff --git a/cli/commands/modules/cdp/write/open.ts b/cli/commands/modules/cdp/write/open.ts new file mode 100644 index 0000000..72b23ee --- /dev/null +++ b/cli/commands/modules/cdp/write/open.ts @@ -0,0 +1,101 @@ +import { ethers } from 'ethers' +import { moduleDictionary, tokenAddresses } from '../../../../defaults' +import { loadEnv } from '../../../../env' +import { getModuleAddress } from '../../../../helpers' +import { logger } from '../../../../logging' +import { CDPOpenParams, CLIArgs, CLIEnvironment } from '../../../../types' +import { execute } from '../../../deploy' +import addresses from '../../../../../addresses.json' + +const getParams = (cliArgs: CLIArgs): CDPOpenParams => { + let { _: parameters, c: networkId } = cliArgs + parameters = parameters.slice(3) + const collateralToken = parameters[0] + if (!moduleDictionary.cdp[collateralToken]) { + logger.error('openPosition: Collateral token does not have a corresponding CDP') + return {} as CDPOpenParams + } + const contractAddress = getModuleAddress(networkId, 'cdp', collateralToken, 'deposit') + + if (collateralToken === 'wsteth') { + parameters = parameters.slice(1) + } + + if (parameters.length !== 3) { + logger.error('openPosition: Not enough parameters were supplied') + return {} as CDPOpenParams + } + + if (isNaN(parameters[1]) || isNaN(parameters[2])) { + logger.error('openPositions: parameters supplied are in the wrong type') + return {} as CDPOpenParams + } + + const collateralAmount = ethers.utils.parseUnits(parameters[1], 18) + const debtAmount = ethers.utils.parseUnits(parameters[2], 18) + + return { + contractAddress, + collateralToken, + depositToken: parameters[0], + collateralAmount, + debtAmount + } +} + +const openPosition = async (cli: CLIEnvironment, cliArgs: CLIArgs): Promise => { + const params: CDPOpenParams = getParams(cliArgs) + if (!params.collateralToken) { + logger.error('openPosition: bad parameters') + return + } + if (params.collateralToken === 'wsteth') { + return await depositWithWrapper(params, cli) + } +} + +const depositWithWrapper = async (params: CDPOpenParams, cli: CLIEnvironment): Promise => { + const depositTokenAddress: string = tokenAddresses[params.depositToken] + if (!depositTokenAddress) { + logger.error('openPosition: deposit token address not found') + return + } + + if (cli.argv.c === 42069) { + const approveCommand: string = `cast send --rpc-url ${ + cli.providerUrl + } ${depositTokenAddress} "approve(address,uint256)" ${ + params.contractAddress + } ${params.collateralAmount.toString()} --from ${cli.wallet.address} --json` + const res = JSON.parse(await execute(approveCommand)) + if (res.status === '0x1') { + logger.info( + `${ + cli.wallet.address + } approved ${params.collateralAmount.toString()} for ${depositTokenAddress}` + ) + } + } + + const openCommand: string = `cast send --rpc-url ${cli.providerUrl} ${ + params.contractAddress + } "open(uint256,uint256,address)" ${params.collateralAmount.toString()} ${params.debtAmount.toString()} ${depositTokenAddress} --from ${ + cli.wallet.address + } --json` + const receipt = JSON.parse(await execute(openCommand)) + if (receipt.status === '0x1') { + logger.info(`Open a new debt position for ${cli.wallet.address} successfully.`) + const cdpAddress: string = addresses[cli.argv.c].modules.CDPPool_wstETH + const positionCommand: string = `cast call --rpc-url ${cli.providerUrl} ${cdpAddress} "cdps(address)((uint256,uint256))" ${cli.wallet.address}` + const positionReceipt = await execute(positionCommand) + logger.info(positionReceipt) + } +} + +export const openCommand = { + command: 'open', + describe: 'opens a position', + handler: async (argv: CLIArgs): Promise => { + return await openPosition(await loadEnv(argv), argv) + } +} diff --git a/cli/commands/modules/cdp/write/removeCollateral.ts b/cli/commands/modules/cdp/write/removeCollateral.ts new file mode 100644 index 0000000..7176c49 --- /dev/null +++ b/cli/commands/modules/cdp/write/removeCollateral.ts @@ -0,0 +1,71 @@ +import { ethers } from 'ethers' +import { moduleDictionary } from '../../../../defaults' +import { loadEnv } from '../../../../env' +import { getModuleAddress } from '../../../../helpers' +import { logger } from '../../../../logging' +import { CDPCollateralParams, CLIArgs, CLIEnvironment } from '../../../../types' +import { execute } from '../../../deploy' +import addresses from '../../../../../addresses.json' + +const getParams = (cliArgs: CLIArgs): CDPCollateralParams => { + let { _: parameters, c: networkId } = cliArgs + parameters = parameters.slice(3) + const collateralToken = parameters[0] + if (!moduleDictionary.cdp[collateralToken]) { + logger.error('removeCollateral: Collateral token does not have a corresponding CDP') + return {} as CDPCollateralParams + } + const contractAddress = getModuleAddress(networkId, 'cdp', collateralToken, 'default') + + if (collateralToken === 'wsteth') { + parameters = parameters.slice(1) + } + + if (parameters.length !== 1) { + logger.error('removeCollateral: Not enough parameters were supplied') + return {} as CDPCollateralParams + } + + if (isNaN(parameters[0])) { + logger.error('removeCollateral: parameters supplied are in the wrong type') + return {} as CDPCollateralParams + } + + const collateralAmount = ethers.utils.parseUnits(parameters[0], 18) + + return { + contractAddress, + collateralToken, + depositToken: parameters[0], + collateralAmount + } +} + +const removeCollateral = async (cli: CLIEnvironment, cliArgs: CLIArgs): Promise => { + const params: CDPCollateralParams = getParams(cliArgs) + if (!params.collateralToken) { + logger.error('removeCollateral: bad parameters') + return + } + const cdpAddress: string = addresses[cli.argv.c].modules.CDPPool_wstETH + const removeCollateralCommand: string = `cast send --rpc-url ${ + cli.providerUrl + } ${cdpAddress} "removeCollateral(uint256)" ${params.collateralAmount.toString()} --from ${ + cli.wallet.address + } --json` + const receipt = JSON.parse(await execute(removeCollateralCommand)) + if (receipt.status === '0x1') { + logger.info(`Remove collateral from position for ${cli.wallet.address} successfully.`) + const positionCommand: string = `cast call --rpc-url ${cli.providerUrl} ${cdpAddress} "cdps(address)((uint256,uint256))" ${cli.wallet.address}` + const positionReceipt = await execute(positionCommand) + logger.info(positionReceipt) + } +} + +export const removeCollateralCommand = { + command: 'remove-collateral', + describe: 'Remove collateral from a position', + handler: async (argv: CLIArgs): Promise => { + return await removeCollateral(await loadEnv(argv), argv) + } +} diff --git a/cli/commands/modules/cdp/write/removeDebt.ts b/cli/commands/modules/cdp/write/removeDebt.ts new file mode 100644 index 0000000..86c5325 --- /dev/null +++ b/cli/commands/modules/cdp/write/removeDebt.ts @@ -0,0 +1,92 @@ +import { ethers } from 'ethers' +import { moduleDictionary } from '../../../../defaults' +import { loadEnv } from '../../../../env' +import { getModuleAddress } from '../../../../helpers' +import { logger } from '../../../../logging' +import { CDPDebtParams, CLIArgs, CLIEnvironment } from '../../../../types' +import { execute } from '../../../deploy' +import addresses from '../../../../../addresses.json' + +const getParams = (cliArgs: CLIArgs): CDPDebtParams => { + let { _: parameters, c: networkId } = cliArgs + parameters = parameters.slice(3) + const collateralToken = parameters[0] + if (!moduleDictionary.cdp[collateralToken]) { + logger.error('removeDebt: Collateral token does not have a corresponding CDP') + return {} as CDPDebtParams + } + const contractAddress = getModuleAddress(networkId, 'cdp', collateralToken, 'default') + console.log(contractAddress) + + if (collateralToken === 'wsteth') { + parameters = parameters.slice(1) + } + + if (parameters.length !== 1) { + logger.error('removeDebt: Not enough parameters were supplied') + return {} as CDPDebtParams + } + + if (isNaN(parameters[0])) { + logger.error('removeDebt: parameters supplied are in the wrong type') + return {} as CDPDebtParams + } + + const debtAmount = ethers.utils.parseUnits(parameters[0], 18) + + return { + contractAddress, + collateralToken, + depositToken: '', + debtAmount + } +} + +const removeDebt = async (cli: CLIEnvironment, cliArgs: CLIArgs): Promise => { + const params: CDPDebtParams = getParams(cliArgs) + if (!params.collateralToken) { + logger.error('removeDebt: bad parameters') + return + } + const { PHO: phoAddress, Kernel: kernelAddress }: Record = + addresses[cli.argv.c].core + const moduleName: string = moduleDictionary.cdp[params.collateralToken].default + const cdpAddress: string = addresses[cli.argv.c].modules[moduleName] + + if (cli.argv.c === 42069) { + const approveCommand: string = `cast send --rpc-url ${ + cli.providerUrl + } ${phoAddress} "approve(address,uint256)" ${kernelAddress} ${params.debtAmount.toString()} --from ${ + cli.wallet.address + } --json` + const res = JSON.parse(await execute(approveCommand)) + if (res.status === '0x1') { + logger.info( + `${cli.wallet.address} approved ${params.debtAmount.toString()} for ${ + params.contractAddress + }` + ) + } + } + + const removeDebtCommand: string = `cast send --rpc-url ${ + cli.providerUrl + } ${cdpAddress} "removeDebt(uint256)" ${params.debtAmount.toString()} --from ${ + cli.wallet.address + } --json` + const receipt = JSON.parse(await execute(removeDebtCommand)) + if (receipt.status === '0x1') { + logger.info(`Removed debt from position for ${cli.wallet.address} successfully.`) + const positionCommand: string = `cast call --rpc-url ${cli.providerUrl} ${cdpAddress} "cdps(address)((uint256,uint256))" ${cli.wallet.address}` + const positionReceipt = await execute(positionCommand) + logger.info(positionReceipt) + } +} + +export const removeDebtCommand = { + command: 'remove-debt', + describe: 'Remove debt from a position', + handler: async (argv: CLIArgs): Promise => { + return await removeDebt(await loadEnv(argv), argv) + } +} diff --git a/cli/commands/modules/index.ts b/cli/commands/modules/index.ts new file mode 100644 index 0000000..2b89f1c --- /dev/null +++ b/cli/commands/modules/index.ts @@ -0,0 +1,13 @@ +import yargs, { Argv } from 'yargs' +import { cdpCommand } from './cdp' + +export const modulesCommand = { + command: 'modules', + describe: 'Photon protocol modules', + builder: (yargs: Argv): yargs.Argv => { + return yargs.command(cdpCommand) + }, + handler: (): void => { + yargs.showHelp() + } +} diff --git a/cli/defaults.ts b/cli/defaults.ts index 7094ff5..c010405 100644 --- a/cli/defaults.ts +++ b/cli/defaults.ts @@ -1,5 +1,5 @@ import { Options } from 'yargs' -import { Overrides } from 'ethers' +import { ethers, Overrides } from 'ethers' import dotenv from 'dotenv' dotenv.config() @@ -53,3 +53,19 @@ export const coreContracts = [ 'ChainlinkPriceFeed', 'CurvePool' ] + +export const moduleDictionary = { + cdp: { + wsteth: { + deposit: 'wstETHCDPWrapper', + default: 'CDPPool_wstETH' + } + } +} + +export const tokenAddresses = { + eth: ethers.constants.AddressZero, + weth: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', + steth: '0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84', + wsteth: '0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0' +} diff --git a/cli/deployParams.json b/cli/deployParams.json index 791abc5..cda6427 100644 --- a/cli/deployParams.json +++ b/cli/deployParams.json @@ -3,7 +3,7 @@ { "name": "protocol", "description": "deploys main protocol contracts: TON, PHO, Kernel, ModuleManager, ChainlinkPriceFeed", - "deploy": true, + "deploy": false, "contractName": "DeployProtocol", "sigParams": [ { @@ -17,7 +17,7 @@ { "name": "CurvePool", "description": "Deploys a new FraxBP-PHO curve pool", - "deploy": true, + "deploy": false, "contractName": "DeployCurvePool", "sigParams": [], "isCore": true, @@ -26,7 +26,7 @@ { "name": "Price Controller", "description": "Deploys price controller module", - "deploy": true, + "deploy": false, "contractName": "DeployPriceController", "sigParams": [ { @@ -52,7 +52,7 @@ { "name": "USDC stablecoin module", "description": "Deploys USDC Stablecoin module", - "deploy": true, + "deploy": false, "contractName": "DeployStablecoinDepositModule", "sigParams": [ { @@ -66,7 +66,7 @@ { "name": "FRAX stablecoin module", "description": "Deploys FRAX Stablecoin module", - "deploy": true, + "deploy": false, "contractName": "DeployStablecoinDepositModule", "sigParams": [ { @@ -80,7 +80,7 @@ { "name": "LUSD stablecoin module", "description": "Deploys LUSD Stablecoin module", - "deploy": true, + "deploy": false, "contractName": "DeployStablecoinDepositModule", "sigParams": [ { @@ -94,7 +94,7 @@ { "name": "Maple deposit module", "description": "Deploys maple deposit module", - "deploy": true, + "deploy": false, "contractName": "DeployMapleDepositModule", "sigParams": [ { @@ -108,7 +108,7 @@ { "name": "USDC Zero Coupon Bond module", "description": "Deploys Zero coupon bonds module with USDC", - "deploy": true, + "deploy": false, "contractName": "DeployZCBModule", "sigParams": [ { @@ -138,6 +138,54 @@ ], "isCore": false, "contractLabel": "ZCBModuleUSDC" + }, + { + "name": "Dummy oracle deployment", + "description": "Deploy dummy oracle", + "deploy": false, + "contractName": "DeployDummyOracle", + "sigParams": [], + "isCore": true, + "contractLabel": "ChainlinkPriceFeed" + }, + { + "name": "wstETH CDP mechanism", + "description": "CDP Mechanism deployment using wstETH as collateral", + "deploy": true, + "contractName": "DeployCDPModule", + "sigParams": [ + { + "type": "address", + "value": "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0" + }, + { + "type": "uint256", + "value": "170000" + }, + { + "type": "uint256", + "value": "150000" + }, + { + "type": "uint256", + "value": 1000 + }, + { + "type": "uint256", + "value": 500 + } + ], + "isCore": false, + "contractLabel": "CDPPool_wstETH" + }, + { + "name": "wstETH CDP wrapper", + "description": "wrapper contract for wstETH based CDP", + "deploy": true, + "contractName": "DeployWstETHCDPWrapper", + "sigParams": [], + "isCore": false, + "contractLabel": "wstETHCDPWrapper" } ] } diff --git a/cli/env.ts b/cli/env.ts index b7e17b8..9d2674d 100644 --- a/cli/env.ts +++ b/cli/env.ts @@ -26,9 +26,9 @@ export const loadEnv = async (argv: CLIArgs): Promise => { const chainId = (await wallet.provider.getNetwork()).chainId const nonce = await wallet.getTransactionCount() const walletAddress = await wallet.getAddress() - const { c: networkId }: { c: number } = argv + const { c: networkId } = argv if (!verifyNetwork(networkId)) { - logger.info(`Network id ${networkId} is invalid`) + logger.info(`Network id ${networkId as string} is invalid`) } const { core: coreContracts, modules: modulesContracts } = diff --git a/cli/helpers.ts b/cli/helpers.ts index a2d575c..229f667 100644 --- a/cli/helpers.ts +++ b/cli/helpers.ts @@ -1,7 +1,7 @@ import { logger } from './logging' import { NetworkContracts } from './types' import addresses from '../addresses.json' -import { rpcUrls } from './defaults' +import { moduleDictionary, rpcUrls } from './defaults' export const verifyModule = (networkId: number, moduleId: string): boolean => { try { @@ -25,7 +25,7 @@ export const verifyNetwork = (networkId: number): boolean => { logger.info('First parameter should be the network name') return false } else if (!getNetworkRPC(networkId)) { - logger.info(`Network with ID ${networkId} does not have a RPC_URK record in the .env file`) + logger.error(`Network with ID ${networkId} does not have a RPC_URK record in the .env file`) return false } return true @@ -42,3 +42,25 @@ export const getNetworkRPC = (networkId: number): string => { export const getNetworkContractAddresses = (networkId: number): NetworkContracts => { return addresses[networkId] } + +export const getModuleAddress = ( + networkId: number, + type: string, + identifier: string, + action: string +): string => { + const contractName = moduleDictionary[type][identifier][action] + if (!contractName || !verifyNetwork(networkId)) { + logger.error('getModuleAddress: No module was found with the given params') + return '' + } + const modules = addresses[networkId].modules + if (!modules) { + logger.error('getModuleAddress: Network does not have deployed contracts') + } + return modules[contractName] +} + +export const toReadablePrice = (value: string): string => { + return value.slice(0, value.length - 18) + '.' + value.slice(-18) +} diff --git a/cli/types.ts b/cli/types.ts index 186eca0..2f79ea5 100644 --- a/cli/types.ts +++ b/cli/types.ts @@ -9,7 +9,7 @@ import { Argv } from 'yargs' export interface SignatureParam { type: string - value: string | number + value: string | number | BigNumber } export interface DeployParams { @@ -78,3 +78,26 @@ export interface ProtocolFunction { contract: string name: string } + +export interface CDPBaseParams { + contractAddress: string + collateralToken: string + depositToken: string +} +export interface CDPOpenParams extends CDPBaseParams { + collateralAmount: BigNumber + debtAmount: BigNumber +} + +export interface CDPCollateralParams extends CDPBaseParams { + collateralAmount: BigNumber +} + +export interface CDPDebtParams extends CDPBaseParams { + debtAmount: BigNumber +} + +export interface CDPLiquidationParams extends CDPBaseParams { + cdpOwner: string + liquidator: string +} diff --git a/deployments/addresses_last.example.json b/deployments/addresses_last.example.json index ba123dc..b5f1d07 100644 --- a/deployments/addresses_last.example.json +++ b/deployments/addresses_last.example.json @@ -1,4 +1,4 @@ { - "phoGovernance": "0x4ce45ECaa645B0fc00250C62D20F14003707B471", - "tonGovernance": "0x4ce45ECaa645B0fc00250C62D20F14003707B471" + "phoGovernance": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "tonGovernance": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266" } diff --git a/package-lock.json b/package-lock.json index c9dc850..d3b86f6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "devDependencies": { "@typechain/ethers-v5": "^10.1.1", "@types/node": "^18.11.9", + "@types/yargs": "^17.0.17", "@typescript-eslint/eslint-plugin": "^5.46.1", "dotenv": "^16.0.3", "eslint": "^8.29.0", @@ -1067,6 +1068,21 @@ "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==", "dev": true }, + "node_modules/@types/yargs": { + "version": "17.0.17", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.17.tgz", + "integrity": "sha512-72bWxFKTK6uwWJAVT+3rF6Jo6RTojiJ27FQo8Rf60AL+VZbzoVPnMFhKsUnbjR8A3BTCYQ7Mv3hnl8T0A+CX9g==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", + "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", + "dev": true + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "5.46.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.46.1.tgz", @@ -6168,6 +6184,21 @@ "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==", "dev": true }, + "@types/yargs": { + "version": "17.0.17", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.17.tgz", + "integrity": "sha512-72bWxFKTK6uwWJAVT+3rF6Jo6RTojiJ27FQo8Rf60AL+VZbzoVPnMFhKsUnbjR8A3BTCYQ7Mv3hnl8T0A+CX9g==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "@types/yargs-parser": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", + "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", + "dev": true + }, "@typescript-eslint/eslint-plugin": { "version": "5.46.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.46.1.tgz", diff --git a/package.json b/package.json index e6507be..b7c936d 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "devDependencies": { "@typechain/ethers-v5": "^10.1.1", "@types/node": "^18.11.9", + "@types/yargs": "^17.0.17", "@typescript-eslint/eslint-plugin": "^5.46.1", "dotenv": "^16.0.3", "eslint": "^8.29.0", diff --git a/scripts/DeployCDPModule.s.sol b/scripts/DeployCDPModule.s.sol new file mode 100644 index 0000000..1eca771 --- /dev/null +++ b/scripts/DeployCDPModule.s.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +pragma solidity ^0.8.13; + +import "forge-std/Script.sol"; +import "@modules/cdpModule/CDPPool.sol"; +import "./Addresses.s.sol"; + +/// Script to deploy protocol (PHO, TON, Kernel, ModuleManager) +contract DeployCDPModule is Script, Addresses { + + CDPPool public pool; + + function run(address depositToken, uint256 minCR, uint256 liquidationCR, uint256 minDebt, uint256 protocolFee) external { + vm.startBroadcast(); + + address moduleManagerAddress = getAddress(".ModuleManager"); + address TONTimelock = getAddress(".tonGovernance"); + address chainlinkPriceOracle = getAddress(".ChainlinkPriceFeed"); + + pool = new CDPPool( + moduleManagerAddress, + chainlinkPriceOracle, + depositToken, + TONTimelock, + minCR, + liquidationCR, + minDebt * 10 ** 18, + protocolFee + ); + + vm.stopBroadcast(); + } +} diff --git a/scripts/DeployCDPModuleWETH.s.sol b/scripts/DeployCDPModuleWETH.s.sol deleted file mode 100644 index 4677692..0000000 --- a/scripts/DeployCDPModuleWETH.s.sol +++ /dev/null @@ -1,33 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later - -pragma solidity ^0.8.13; - -import "forge-std/Script.sol"; -import "@modules/cdpModule/CDPPool.sol"; -import "./Addresses.s.sol"; - -/// Script to deploy protocol (PHO, TON, Kernel, ModuleManager) -contract DeployCDPModuleWETH is Script, Addresses { - - CDPPool public wethPool; - address public WETH_ADDRESS = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; - - function run() external { - vm.startBroadcast(); - - address moduleManagerAddress = getAddress(".ModuleManager"); - address chainlinkPriceFeedAddress = getAddress(".ChainlinkPriceFeed"); - - wethPool = new CDPPool( - moduleManagerAddress, - chainlinkPriceFeedAddress, - WETH_ADDRESS, - 170000, - 150000, - 1000 * 10 ** 18, - 500 - ); - - vm.stopBroadcast(); - } -} diff --git a/scripts/DeployDummyOracle.s.sol b/scripts/DeployDummyOracle.s.sol new file mode 100644 index 0000000..e8828b9 --- /dev/null +++ b/scripts/DeployDummyOracle.s.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +pragma solidity ^0.8.13; + +import "forge-std/Script.sol"; +import "@oracle/DummyOracle.sol"; + +contract DeployDummyOracle is Script { + + DummyOracle public oracle; + + function run() external { + vm.startBroadcast(); + + oracle = new DummyOracle(); + + vm.stopBroadcast(); + } +} diff --git a/scripts/DeployWstETHCDPWrapper.s.sol b/scripts/DeployWstETHCDPWrapper.s.sol new file mode 100644 index 0000000..0f10f4f --- /dev/null +++ b/scripts/DeployWstETHCDPWrapper.s.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +pragma solidity ^0.8.13; + +import "forge-std/Script.sol"; +import "@modules/cdpModule/wstETHCDPWrapper.sol"; +import "./Addresses.s.sol"; + +/// Script to deploy protocol (PHO, TON, Kernel, ModuleManager) +contract DeployWstETHCDPWrapper is Script, Addresses { + + wstETHCDPWrapper public wrapper; + + function run() external { + vm.startBroadcast(); + + address cdpAddress = getAddress(".CDPPool_wstETH"); + + wrapper = new wstETHCDPWrapper(cdpAddress); + + vm.stopBroadcast(); + } +} diff --git a/src/modules/cdpModule/wstETHCDPWrapper.sol b/src/modules/cdpModule/wstETHCDPWrapper.sol index a808e9e..7ebc262 100644 --- a/src/modules/cdpModule/wstETHCDPWrapper.sol +++ b/src/modules/cdpModule/wstETHCDPWrapper.sol @@ -26,6 +26,8 @@ contract wstETHCDPWrapper { constructor(address _pool) { pool = ICDPPool(_pool); + STETH.approve(address(WSTETH), type(uint256).max); + WSTETH.approve(_pool, type(uint256).max); } receive() external payable {} diff --git a/src/oracle/DummyOracle.sol b/src/oracle/DummyOracle.sol index 8bd6047..82a75b5 100644 --- a/src/oracle/DummyOracle.sol +++ b/src/oracle/DummyOracle.sol @@ -12,6 +12,7 @@ contract DummyOracle is IPriceOracle { uint256 public pho_usd_price; uint256 public usdc_usd_price; + address public constant ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; address public constant WETH_ADDRESS = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; address public constant STETH_ADDRESS = 0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84; address public constant WSTETH_ADDRESS = 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0; @@ -27,6 +28,7 @@ contract DummyOracle is IPriceOracle { pho_usd_price = 10 ** 6; usdc_usd_price = 10 ** 6; + priceFeeds[ETH_ADDRESS] = weth_usd_price; priceFeeds[WETH_ADDRESS] = weth_usd_price; priceFeeds[STETH_ADDRESS] = weth_usd_price; priceFeeds[WSTETH_ADDRESS] = weth_usd_price * 11 / 10; @@ -85,6 +87,7 @@ contract DummyOracle is IPriceOracle { } function setWethUSDPrice(uint256 _price) public { + priceFeeds[ETH_ADDRESS] = _price; priceFeeds[WETH_ADDRESS] = _price; priceFeeds[STETH_ADDRESS] = _price; priceFeeds[WSTETH_ADDRESS] = _price * 11 / 10; @@ -95,4 +98,8 @@ contract DummyOracle is IPriceOracle { function getPrice(address baseToken) external view returns (uint256) { return priceFeeds[baseToken]; } + + function addPriceFeed(address baseToken, uint256 price) external { + priceFeeds[baseToken] = price; + } }