Skip to content

Commit

Permalink
Merge pull request #271 from lidofinance/feat/csm-motion
Browse files Browse the repository at this point in the history
feat: add `CSMSettleElStealingPenalty` motion
  • Loading branch information
infoster42 authored Sep 23, 2024
2 parents ba9f311 + 42ef80d commit 34526fe
Show file tree
Hide file tree
Showing 15 changed files with 2,399 additions and 0 deletions.
2,107 changes: 2,107 additions & 0 deletions abi/CSMRegistry.abi.json

Large diffs are not rendered by default.

51 changes: 51 additions & 0 deletions abi/CSMSettleElStealingPenalty.abi.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
[
{
"inputs": [
{
"internalType": "address",
"name": "_trustedCaller",
"type": "address"
},
{ "internalType": "address", "name": "_csm", "type": "address" }
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [
{ "internalType": "address", "name": "_creator", "type": "address" },
{ "internalType": "bytes", "name": "_evmScriptCallData", "type": "bytes" }
],
"name": "createEVMScript",
"outputs": [{ "internalType": "bytes", "name": "", "type": "bytes" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "csm",
"outputs": [
{ "internalType": "contract ICSModule", "name": "", "type": "address" }
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{ "internalType": "bytes", "name": "_evmScriptCallData", "type": "bytes" }
],
"name": "decodeEVMScriptCallData",
"outputs": [
{ "internalType": "uint256[]", "name": "", "type": "uint256[]" }
],
"stateMutability": "pure",
"type": "function"
},
{
"inputs": [],
"name": "trustedCaller",
"outputs": [{ "internalType": "address", "name": "", "type": "address" }],
"stateMutability": "view",
"type": "function"
}
]
4 changes: 4 additions & 0 deletions modules/blockChain/contractAddresses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,3 +199,7 @@ export const StonksStablesAllowedRecipientsRegistry: ChainAddressMap = {
[CHAINS.Mainnet]: '0x3f0534CCcFb952470775C516DC2eff8396B8A368',
[CHAINS.Holesky]: '0xDd553C1F88EDCFc2033141Cb908eFf9189988A90',
}

export const CSMRegistry: ChainAddressMap = {
[CHAINS.Holesky]: '0x4562c3e63c2e586cd1651b958c22f88135acad4f',
}
10 changes: 10 additions & 0 deletions modules/blockChain/contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -450,3 +450,13 @@ export const ContractStonksStablesTopUp = createContractHelpers({
factory: TypeChain.TopUpWithLimitsStablesAbi__factory,
address: EvmAddressesByType[MotionType.StonksStablesTopUp],
})

export const ContractCSMSettleElStealingPenalty = createContractHelpers({
factory: TypeChain.CSMSettleElStealingPenaltyAbi__factory,
address: EvmAddressesByType[MotionType.CSMSettleElStealingPenalty],
})

export const ContractCSMRegistry = createContractHelpers({
factory: TypeChain.CSMRegistryAbi__factory,
address: CONTRACT_ADDRESSES.CSMRegistry,
})
2 changes: 2 additions & 0 deletions modules/motions/evmAddresses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,8 @@ export const EvmAddressesByChain: EvmAddresses = {
[MotionType.StonksStethTopUp]: '0x1240775f1857fB8317bD9ba63f4A8A6A78D9af06',
[MotionType.StonksStablesTopUp]:
'0x65A9913467A9793Bb23726d72C99A470bb9294Ad',
[MotionType.CSMSettleElStealingPenalty]:
'0x07696EA8A5b53C3E35d9cce10cc62c6c79C4691D',

// next motion factories are @deprecated
// we are keeping them here to display history data
Expand Down
21 changes: 21 additions & 0 deletions modules/motions/hooks/useCSMNodeOperatorsCount.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { useLidoSWR } from '@lido-sdk/react'
import { ContractCSMRegistry } from 'modules/blockChain/contracts'
import { useWeb3 } from 'modules/blockChain/hooks/useWeb3'

export const useCSMNodeOperatorsCount = () => {
const { chainId } = useWeb3()
const registry = ContractCSMRegistry.useRpc()

return useLidoSWR(
[`swr:useCSMNodeOperatorsCount`, chainId],
async () => {
const count = (await registry.getNodeOperatorsCount()).toNumber()

return count
},
{
revalidateOnFocus: false,
revalidateOnReconnect: false,
},
)
}
2 changes: 2 additions & 0 deletions modules/motions/hooks/useContractEvmScript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ export const EVM_CONTRACTS = {
[MotionType.LegoStablesTopUp]: CONTRACTS.ContractLegoStablesTopUp,
[MotionType.StonksStethTopUp]: CONTRACTS.ContractStonksStethTopUp,
[MotionType.StonksStablesTopUp]: CONTRACTS.ContractStonksStablesTopUp,
[MotionType.CSMSettleElStealingPenalty]:
CONTRACTS.ContractCSMSettleElStealingPenalty,
} as const

export function useContractEvmScript<T extends MotionType | EvmUnrecognized>(
Expand Down
1 change: 1 addition & 0 deletions modules/motions/hooks/useEVMScriptDecoder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export function useEVMScriptDecoder() {
abis.RegistryWithLimitsAbi__factory.abi,
[KEYS.StonksStablesAllowedRecipientsRegistry]:
abis.RegistryWithLimitsAbi__factory.abi,
[KEYS.CSMRegistry]: abis.CSMRegistryAbi__factory.abi,
}),
)
}, `evm-script-decoder-${chainId}`)
Expand Down
2 changes: 2 additions & 0 deletions modules/motions/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ export const MotionTypeForms = {
LegoStablesTopUp: 'LegoStablesTopUp',
StonksStethTopUp: 'StonksStethTopUp',
StonksStablesTopUp: 'StonksStablesTopUp',

CSMSettleElStealingPenalty: 'CSMSettleElStealingPenalty',
} as const
// intentionally
// eslint-disable-next-line @typescript-eslint/no-redeclare
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { CSMSettleElStealingPenaltyAbi } from 'generated'
import { pluralize } from 'modules/shared/utils/pluralize'
import { NestProps } from './types'

// CSMSettleElStealingPenalty
export function DescCSMSettleElStealingPenalty({
callData,
}: NestProps<CSMSettleElStealingPenaltyAbi['decodeEVMScriptCallData']>) {
return (
<>
Settle (confirm) EL Rewards Stealing penalty for the following CSM{' '}
{pluralize(callData.length, 'operator')}:
{callData.map((item, index) => {
const nodeOperatorId = item.toNumber()

return <div key={index}>NO #{nodeOperatorId}</div>
})}
</>
)
}
2 changes: 2 additions & 0 deletions modules/motions/ui/MotionDescription/MotionDescription.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import { DescSDVTNodeOperatorNamesSet } from './DescSDVTNodeOperatorNamesSet'
import { DescSDVTNodeOperatorsAdd } from './DescSDVTNodeOperatorsAdd'
import { DescSDVTNodeOperatorManagersChange } from './DescSDVTNodeOperatorManagersChange'
import { DescNodeOperatorIncreaseLimit } from './DescNodeOperatorLimitIncrease'
import { DescCSMSettleElStealingPenalty } from './DescCSMSettleElStealingPenalty'

type DescWithLimitsProps = NestProps<
TopUpWithLimitsAbi['decodeEVMScriptCallData']
Expand Down Expand Up @@ -298,6 +299,7 @@ const MOTION_DESCRIPTIONS = {
registryType={MotionType.StonksStethTopUp}
/>
),
[MotionType.CSMSettleElStealingPenalty]: DescCSMSettleElStealingPenalty,
} as const

type Props = {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import { utils } from 'ethers'

import { Fragment } from 'react'
import { useFieldArray, useFormContext } from 'react-hook-form'
import { Plus, ButtonIcon } from '@lidofinance/lido-ui'
import { useWeb3 } from 'modules/blockChain/hooks/useWeb3'

import { PageLoader } from 'modules/shared/ui/Common/PageLoader'
import {
Fieldset,
MessageBox,
RemoveItemButton,
FieldsWrapper,
FieldsHeader,
FieldsHeaderDesc,
} from '../CreateMotionFormStyle'

import { ContractCSMSettleElStealingPenalty } from 'modules/blockChain/contracts'
import { MotionType } from 'modules/motions/types'
import { createMotionFormPart } from './createMotionFormPart'
import { estimateGasFallback } from 'modules/motions/utils'
import { useCSMNodeOperatorsCount } from 'modules/motions/hooks/useCSMNodeOperatorsCount'
import { InputNumberControl } from 'modules/shared/ui/Controls/InputNumber'
import { validateUintValue } from 'modules/motions/utils/validateUintValue'

type NodeOperator = {
id: string
}

export const formParts = createMotionFormPart({
motionType: MotionType.CSMSettleElStealingPenalty,
populateTx: async ({ evmScriptFactory, formData, contract }) => {
const sortedNodeOperators = formData.nodeOperators
.map(({ id }) => Number(id))
.sort((a, b) => a - b)

const encodedCallData = new utils.AbiCoder().encode(
['uint256[]'],
[sortedNodeOperators],
)
const gasLimit = await estimateGasFallback(
contract.estimateGas.createMotion(evmScriptFactory, encodedCallData),
)
const tx = await contract.populateTransaction.createMotion(
evmScriptFactory,
encodedCallData,
{ gasLimit },
)
return tx
},
getDefaultFormData: () => ({
nodeOperators: [
{
id: '',
},
] as NodeOperator[],
}),
Component: ({ fieldNames, submitAction }) => {
const { walletAddress } = useWeb3()

const {
data: nodeOperatorsCount,
initialLoading: isNodeOperatorsCountLoading,
} = useCSMNodeOperatorsCount()

const trustedCaller = ContractCSMSettleElStealingPenalty.useSwrWeb3(
'trustedCaller',
[],
)

const fieldsArr = useFieldArray({ name: fieldNames.nodeOperators })
const { watch } = useFormContext()
const selectedNodeOperators: NodeOperator[] = watch(
fieldNames.nodeOperators,
)

const handleAddSettle = () =>
fieldsArr.append({
id: '',
} as NodeOperator)

if (trustedCaller.initialLoading || isNodeOperatorsCountLoading) {
return <PageLoader />
}

if (trustedCaller.data !== walletAddress) {
return <MessageBox>You should be connected as trusted caller</MessageBox>
}

if (!nodeOperatorsCount) {
return <MessageBox>There are no node operators</MessageBox>
}

return (
<>
{fieldsArr.fields.map((item, fieldIndex) => {
return (
<Fragment key={item.id}>
<FieldsWrapper>
<FieldsHeader>
{fieldsArr.fields.length > 1 && (
<FieldsHeaderDesc>
Settle #{fieldIndex + 1}
</FieldsHeaderDesc>
)}
{fieldsArr.fields.length > 1 && (
<RemoveItemButton
onClick={() => fieldsArr.remove(fieldIndex)}
>
Remove settle {fieldIndex + 1}
</RemoveItemButton>
)}
</FieldsHeader>

<Fieldset>
<InputNumberControl
name={`${fieldNames.nodeOperators}.${fieldIndex}.id`}
label="Node operator ID"
rules={{
required: 'Field is required',
validate: value => {
const uintError = validateUintValue(value)
if (uintError) {
return uintError
}
const valueNum = Number(value)

if (valueNum >= nodeOperatorsCount) {
return 'Invalid node operator ID'
}

const isAlreadyInInput = selectedNodeOperators.some(
({ id }, index) =>
id === value && index !== fieldIndex,
)

if (isAlreadyInInput) {
return 'This ID is already in the list'
}

return true
},
}}
/>
</Fieldset>
</FieldsWrapper>
</Fragment>
)
})}

{selectedNodeOperators.length < nodeOperatorsCount && (
<Fieldset>
<ButtonIcon
type="button"
variant="ghost"
size="sm"
onClick={handleAddSettle}
icon={<Plus />}
color="secondary"
>
One more settle
</ButtonIcon>
</Fieldset>
)}

{submitAction}
</>
)
},
})
3 changes: 3 additions & 0 deletions modules/motions/ui/MotionFormStartNew/Parts/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import * as StartNewSDVTNodeOperatorRewardAddressesSet from './StartNewSDVTNodeO
import * as StartNewSDVTNodeOperatorNamesSet from './StartNewSDVTNodeOperatorNamesSet'
import * as StartNewSDVTNodeOperatorManagersChange from './StartNewSDVTNodeOperatorManagersChange'
import * as StartNewNodeOperatorLimitIncrease from './StartNewNodeOperatorLimitIncrease'
import * as StartNewCSMSettleElStealingPenalty from './StartNewCSMSettleElStealingPenalty'

export const formParts = {
[MotionTypeForms.NodeOperatorIncreaseLimit]:
Expand Down Expand Up @@ -119,6 +120,8 @@ export const formParts = {
StartNewTopUpWithLimitsAndCustomToken.formParts({
registryType: MotionTypeForms.StonksStablesTopUp,
}),
[MotionTypeForms.CSMSettleElStealingPenalty]:
StartNewCSMSettleElStealingPenalty.formParts,
} as const

export type FormData = {
Expand Down
2 changes: 2 additions & 0 deletions modules/motions/utils/getMotionTypeDisplayName.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ export const MotionTypeDisplayNames: Record<
[MotionType.PmlStethTopUp]: 'Top up PML stETH',
[MotionType.AtcStethTopUp]: 'Top up ATC stETH',
[MotionType.LegoStablesTopUp]: 'Top up LEGO stablecoins',
[MotionType.CSMSettleElStealingPenalty]:
'Settle EL Rewards Stealing penalty for CSM operators',

[EvmUnrecognized]: 'Unrecognized evm factory',

Expand Down
2 changes: 2 additions & 0 deletions modules/shared/utils/pluralize.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const pluralize = (count: number, word: string) =>
count === 1 ? word : `${word}s`

0 comments on commit 34526fe

Please sign in to comment.