Skip to content
Draft
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
149 changes: 149 additions & 0 deletions abi/DashboardFactory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
export const DashboardFactoryAbi = [
{
inputs: [
{
internalType: 'address',
name: '_lidoLocator',
type: 'address',
},
],
stateMutability: 'nonpayable',
type: 'constructor',
},
{
inputs: [],
name: 'CloneArgumentsTooLong',
type: 'error',
},
{
inputs: [],
name: 'FailedDeployment',
type: 'error',
},
{
inputs: [
{
internalType: 'uint256',
name: 'balance',
type: 'uint256',
},
{
internalType: 'uint256',
name: 'needed',
type: 'uint256',
},
],
name: 'InsufficientBalance',
type: 'error',
},
{
inputs: [
{
internalType: 'string',
name: 'argName',
type: 'string',
},
],
name: 'ZeroArgument',
type: 'error',
},
{
anonymous: false,
inputs: [
{
indexed: true,
internalType: 'address',
name: 'dashboard',
type: 'address',
},
{
indexed: true,
internalType: 'address',
name: 'vault',
type: 'address',
},
{
indexed: true,
internalType: 'address',
name: 'admin',
type: 'address',
},
],
name: 'DashboardCreated',
type: 'event',
},
{
inputs: [],
name: 'LIDO_LOCATOR',
outputs: [
{
internalType: 'contract ILidoLocator',
name: '',
type: 'address',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{
internalType: 'contract IStakingVault',
name: '_vault',
type: 'address',
},
{
internalType: 'address',
name: '_defaultAdmin',
type: 'address',
},
{
internalType: 'address',
name: '_nodeOperator',
type: 'address',
},
{
internalType: 'address',
name: '_nodeOperatorManager',
type: 'address',
},
{
internalType: 'uint256',
name: '_nodeOperatorFeeBP',
type: 'uint256',
},
{
internalType: 'uint256',
name: '_confirmExpiry',
type: 'uint256',
},
{
components: [
{
internalType: 'address',
name: 'account',
type: 'address',
},
{
internalType: 'bytes32',
name: 'role',
type: 'bytes32',
},
],
internalType: 'struct Permissions.RoleAssignment[]',
name: '_roleAssignments',
type: 'tuple[]',
},
],
name: 'createDashboard',
outputs: [
{
internalType: 'contract Dashboard',
name: 'dashboard',
type: 'address',
},
],
stateMutability: 'nonpayable',
type: 'function',
},
] as const;
1 change: 1 addition & 0 deletions abi/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export * from './StakingVault.js';
export * from './VaultHub.js';
export * from './VaultFactory.js';
export * from './Dashboard.js';
export * from './DashboardFactory.js';
export * from './StEth.js';
export * from './LidoLocator.js';
export * from './VaultViewer.js';
Expand Down
41 changes: 41 additions & 0 deletions contracts/dashboard-factory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import {
getContract,
Address,
createPublicClient,
http,
GetContractReturnType,
WalletClient,
} from 'viem';
import { hoodi } from 'viem/chains';
import { DashboardFactoryAbi } from 'abi';
import { getChain, getElUrl } from 'configs';

const DashboardFactoryAddresses: Record<number, Address> = {
[hoodi.id]: '0xbfe0650638e8a6657Fd04B6156E552F3A6211e33',
};

export const getDashboardFactoryContract = async (): Promise<
GetContractReturnType<typeof DashboardFactoryAbi, WalletClient>
> => {
const chain = await getChain();
const address = DashboardFactoryAddresses[chain.id];

if (!address) {
throw new Error(
`DashboardFactory contract not found for chain ${chain.id}`,
);
}

return getContract({
address: address,
abi: DashboardFactoryAbi,
client: createPublicClient({
chain,
transport: http(getElUrl()),
}),
});
};

export type DashboardFactoryContract = Awaited<
ReturnType<typeof getDashboardFactoryContract>
>;
1 change: 1 addition & 0 deletions contracts/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export * from './vault-factory.js';
export * from './vault.js';
export * from './dashboard.js';
export * from './dashboard-impl.js';
export * from './dashboard-factory.js';
export * from './steth.js';
export * from './locator.js';
export * from './vault-viewer.js';
Expand Down
2 changes: 2 additions & 0 deletions programs/contracts/dashboard-factory/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './main.js';
export * from './write.js';
5 changes: 5 additions & 0 deletions programs/contracts/dashboard-factory/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { contracts } from '../main.js';

export const dashboardFactory = contracts
.command('dashboard-factory')
.description('dashboard factory contract');
138 changes: 138 additions & 0 deletions programs/contracts/dashboard-factory/write.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import { Address, parseEventLogs } from 'viem';
import { DashboardFactoryAbi } from 'abi';
import { Option } from 'commander';
import { getDashboardFactoryContract } from 'contracts';
import {
getConfirmExpiry,
getNodeOperatorFeeRate,
prepareCreateVaultPayload,
} from 'features';
import { RoleAssignment } from 'types';
import {
callWriteMethodWithReceipt,
confirmOperation,
logInfo,
getCommandsJson,
stringToAddress,
stringToNumber,
jsonToRoleAssignment,
logTable,
} from 'utils';

import { dashboardFactory } from './main.js';

const dashboardFactoryWrite = dashboardFactory
.command('write')
.alias('w')
.description('dashboard factory write commands');

dashboardFactoryWrite.addOption(new Option('-cmd2json'));
dashboardFactoryWrite.on('option:-cmd2json', function () {
logInfo(getCommandsJson(dashboardFactoryWrite));
process.exit();
});

dashboardFactoryWrite
.command('create-dashboard')
.alias('c-d')
.description('create Dashboard contract by StakingVault')
.argument('<stakingVault>', 'staking vault address', stringToAddress)
.argument('<defaultAdmin>', 'default admin address', stringToAddress)
.argument('<nodeOperator>', 'node operator address')
.argument('<nodeOperatorManager>', 'node operator manager address')
.argument('<confirmExpiry>', 'confirm expiry', stringToNumber)
.argument(
'<nodeOperatorFeeRate>',
'Node operator fee rate, for e.g. 100 == 1%',
stringToNumber,
)
.option(
'-r, --roles <roles>',
'other roles to assign to the vault',
jsonToRoleAssignment,
)
.action(
async (
stakingVault: Address,
defaultAdmin: Address,
nodeOperator: Address,
nodeOperatorManager: Address,
confirmExpiry: number,
nodeOperatorFeeRate: number,
options: { roles: RoleAssignment[] },
) => {
const confirmExpiryValue = await getConfirmExpiry({ confirmExpiry });
const nodeOperatorFeeRateValue =
await getNodeOperatorFeeRate(nodeOperatorFeeRate);

const createVaultData = prepareCreateVaultPayload({
defaultAdmin,
nodeOperator,
nodeOperatorManager,
confirmExpiry: confirmExpiryValue,
nodeOperatorFeeRate: nodeOperatorFeeRateValue,
quantity: '1', // prepareCreateVaultPayload wait the quantity as string
roles: options.roles,
});
if (!createVaultData) return;

const { payload, otherRoles } = createVaultData;

const confirm = await confirmOperation(
`Are you sure you want to create dashboard for the staking vault ${stakingVault} with default admin is ${defaultAdmin}?`,
);
if (!confirm) return;

const contract = await getDashboardFactoryContract();

try {
const result = await callWriteMethodWithReceipt({
contract,
methodName: 'createDashboard',
payload: [
stakingVault,
payload.defaultAdmin,
payload.nodeOperator,
payload.nodeOperatorManager,
payload.nodeOperatorFeeRate,
payload.confirmExpiry,
otherRoles,
],
});

if (!result) return;

const { receipt, tx } = result;

// Gnosis safe case
if (!receipt) {
logInfo('Transaction has been sent');
return;
}

const events = parseEventLogs({
abi: DashboardFactoryAbi,
logs: receipt.logs,
});

const dashboardEvent = events.find(
(event) => event.eventName === 'DashboardCreated',
);
const dashboard = dashboardEvent?.args.dashboard;
const owner = dashboardEvent?.args.admin;

logTable({
data: [
['Dashboard Address', dashboard],
['Owner Address', owner],
['Transaction Hash', tx],
['Block Number', receipt.blockNumber],
],
});
} catch (err) {
if (err instanceof Error) {
logInfo('Error occurred while creating dashboard', err.message);
}
}
},
);
1 change: 1 addition & 0 deletions programs/contracts/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from './main.js';
export * from './dashboard/index.js';
export * from './dashboard-factory/index.js';
export * from './hub/index.js';
export * from './operator-grid/index.js';
export * from './pdg/index.js';
Expand Down