Skip to content

Commit

Permalink
Merge pull request #402 from valory-xyz/feat/staking_program_marketplace
Browse files Browse the repository at this point in the history
Mech marketplace integration and staking contract
  • Loading branch information
truemiller authored Oct 29, 2024
2 parents 4056077 + d8cf13c commit bcf4ea1
Show file tree
Hide file tree
Showing 37 changed files with 1,013 additions and 500 deletions.
442 changes: 442 additions & 0 deletions frontend/abis/mechMarketplace.ts

Large diffs are not rendered by default.

44 changes: 44 additions & 0 deletions frontend/abis/requesterActivityChecker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
export const REQUESTER_ACTIVITY_CHECKER_ABI = [
{
inputs: [
{ internalType: 'address', name: '_mechMarketplace', type: 'address' },
{ internalType: 'uint256', name: '_livenessRatio', type: 'uint256' },
],
stateMutability: 'nonpayable',
type: 'constructor',
},
{ inputs: [], name: 'ZeroAddress', type: 'error' },
{ inputs: [], name: 'ZeroValue', type: 'error' },
{
inputs: [{ internalType: 'address', name: 'multisig', type: 'address' }],
name: 'getMultisigNonces',
outputs: [{ internalType: 'uint256[]', name: 'nonces', type: 'uint256[]' }],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{ internalType: 'uint256[]', name: 'curNonces', type: 'uint256[]' },
{ internalType: 'uint256[]', name: 'lastNonces', type: 'uint256[]' },
{ internalType: 'uint256', name: 'ts', type: 'uint256' },
],
name: 'isRatioPass',
outputs: [{ internalType: 'bool', name: 'ratioPass', type: 'bool' }],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'livenessRatio',
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'mechMarketplace',
outputs: [{ internalType: 'address', name: '', type: 'address' }],
stateMutability: 'view',
type: 'function',
},
];
1 change: 1 addition & 0 deletions frontend/client/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export type ConfigurationTemplate = {
agent_id: number;
threshold: number;
use_staking: boolean;
use_mech_marketplace: boolean;
cost_of_bond: number;
monthly_gas_estimate: number;
fund_requirements: FundRequirementsTemplate;
Expand Down
11 changes: 9 additions & 2 deletions frontend/components/MainPage/header/AgentButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useCallback, useMemo } from 'react';

import { Chain, DeploymentStatus } from '@/client';
import { COLOR } from '@/constants/colors';
import { StakingProgramId } from '@/enums/StakingProgram';
import { useBalance } from '@/hooks/useBalance';
import { useElectronApi } from '@/hooks/useElectronApi';
import { useReward } from '@/hooks/useReward';
Expand Down Expand Up @@ -132,7 +133,7 @@ const AgentNotRunningButton = () => {
const { showNotification } = useElectronApi();
const {
setIsPaused: setIsBalancePollingPaused,
safeBalance,
masterSafeBalance: safeBalance,
isLowBalance,
totalOlasStakedBalance,
totalEthBalance,
Expand Down Expand Up @@ -179,6 +180,10 @@ const AgentNotRunningButton = () => {
// Mock "DEPLOYING" status (service polling will update this once resumed)
setServiceStatus(DeploymentStatus.DEPLOYING);

// Get the active staking program id; default id if there's no agent yet
const stakingProgramId: StakingProgramId =
activeStakingProgramId ?? defaultStakingProgramId;

// Create master safe if it doesn't exist
try {
if (!masterSafeAddress) {
Expand All @@ -197,9 +202,11 @@ const AgentNotRunningButton = () => {
// Then create / deploy the service
try {
await ServicesService.createService({
stakingProgramId: activeStakingProgramId ?? defaultStakingProgramId, // overwrite with StakingProgram.Alpha to test migration
stakingProgramId,
serviceTemplate,
deploy: true,
useMechMarketplace:
stakingProgramId === StakingProgramId.BetaMechMarketplace,
});
} catch (error) {
console.error(error);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Popover, Typography } from 'antd';
import { gql, request } from 'graphql-request';
import { z } from 'zod';

import { SUBGRAPH_URL } from '@/constants/urls';
import { GNOSIS_REWARDS_HISTORY_SUBGRAPH_URL } from '@/constants/urls';
import { POPOVER_WIDTH_MEDIUM } from '@/constants/width';
import { useStakingProgram } from '@/hooks/useStakingProgram';
import { formatToTime } from '@/utils/time';
Expand Down Expand Up @@ -39,7 +39,10 @@ const useEpochEndTime = () => {
const { data, isLoading } = useQuery({
queryKey: ['latestEpochTime'],
queryFn: async () => {
const response = (await request(SUBGRAPH_URL, latestEpochTimeQuery)) as {
const response = (await request(
GNOSIS_REWARDS_HISTORY_SUBGRAPH_URL,
latestEpochTimeQuery,
)) as {
checkpoints: EpochTimeResponse[];
};
return EpochTimeResponseSchema.parse(response.checkpoints[0]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ const AlertInsufficientMigrationFunds = ({
}: CantMigrateAlertProps) => {
const { serviceTemplate } = useServiceTemplates();
const { isServiceStaked } = useStakingContractInfo();
const { safeBalance, totalOlasStakedBalance } = useBalance();
const { masterSafeBalance: safeBalance, totalOlasStakedBalance } =
useBalance();

const totalOlasRequiredForStaking = getMinimumStakedAmountRequired(
serviceTemplate,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,22 +23,37 @@ type MigrateButtonProps = {
export const MigrateButton = ({ stakingProgramId }: MigrateButtonProps) => {
const { goto } = usePageState();
const { serviceTemplate } = useServiceTemplates();
const { setIsServicePollingPaused, setServiceStatus, updateServiceStatus } =
useServices();
const {
setIsServicePollingPaused,
setServiceStatus,
updateServiceStatus,
hasInitialLoaded: isServicesLoaded,
service,
} = useServices();
const { setIsPaused: setIsBalancePollingPaused } = useBalance();
const { updateActiveStakingProgramId: updateStakingProgram } =
useStakingProgram();
const { activeStakingContractInfo } = useStakingContractInfo();
const { setMigrationModalOpen } = useModals();

const { migrateValidation } = useMigrate(stakingProgramId);
const { migrateValidation, firstDeployValidation } =
useMigrate(stakingProgramId);

// if false, user is migrating, not running for first time
const isFirstDeploy = useMemo(() => {
if (!isServicesLoaded) return false;
if (service) return false;

return true;
}, [isServicesLoaded, service]);

const validation = isFirstDeploy ? firstDeployValidation : migrateValidation;

const popoverContent = useMemo(() => {
if (migrateValidation.canMigrate) return null;
if (validation.canMigrate) return null;

if (
migrateValidation.reason ===
CantMigrateReason.NotStakedForMinimumDuration &&
validation.reason === CantMigrateReason.NotStakedForMinimumDuration &&
!isNil(activeStakingContractInfo)
) {
return (
Expand All @@ -48,15 +63,15 @@ export const MigrateButton = ({ stakingProgramId }: MigrateButtonProps) => {
);
}

return migrateValidation.reason;
}, [activeStakingContractInfo, migrateValidation]);
return validation.reason;
}, [activeStakingContractInfo, validation]);

return (
<Popover content={popoverContent}>
<Button
type="primary"
size="large"
disabled={!migrateValidation.canMigrate}
disabled={!validation.canMigrate}
onClick={async () => {
setIsServicePollingPaused(true);
setIsBalancePollingPaused(true);
Expand All @@ -69,6 +84,8 @@ export const MigrateButton = ({ stakingProgramId }: MigrateButtonProps) => {
stakingProgramId,
serviceTemplate,
deploy: true,
useMechMarketplace:
stakingProgramId === StakingProgramId.BetaMechMarketplace,
});

await updateStakingProgram();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ export enum CantMigrateReason {
NoAvailableStakingSlots = 'The program has no more avaiable slots',
NotStakedForMinimumDuration = 'Pearl has not been staked for the required minimum duration',
PearlCurrentlyRunning = 'Unable to switch while Pearl is running',
LoadingServices = 'Loading services...',
CannotFindStakingContractInfo = 'Cannot obtain staking contract information',
}

type MigrateValidation =
Expand All @@ -34,18 +36,26 @@ type MigrateValidation =
export const useMigrate = (stakingProgramId: StakingProgramId) => {
const { serviceStatus } = useServices();
const { serviceTemplate } = useServiceTemplates();
const { isBalanceLoaded, safeBalance, totalOlasStakedBalance } = useBalance();
const {
isBalanceLoaded,
masterSafeBalance: safeBalance,
totalOlasStakedBalance,
} = useBalance();
const { activeStakingProgramId, activeStakingProgramMeta } =
useStakingProgram();

const {
activeStakingContractInfo,
isServiceStaked,
isServiceStakedForMinimumDuration,
isStakingContractInfoLoaded,
isRewardsAvailable,
hasEnoughServiceSlots,
stakingContractInfoRecord,
} = useStakingContractInfo();

const stakingContractInfo = stakingContractInfoRecord?.[stakingProgramId];

const { hasInitialLoaded: isServicesLoaded } = useServices();

const minimumOlasRequiredToMigrate = useMemo(
() => getMinimumStakedAmountRequired(serviceTemplate, stakingProgramId),
[serviceTemplate, stakingProgramId],
Expand All @@ -67,19 +77,37 @@ export const useMigrate = (stakingProgramId: StakingProgramId) => {
totalOlasStakedBalance,
]);

const hasEnoughOlasForFirstRun = useMemo(() => {
if (!isBalanceLoaded) return false;
if (isNil(safeBalance?.OLAS)) return false;
if (isNil(minimumOlasRequiredToMigrate)) return false;

return safeBalance.OLAS >= minimumOlasRequiredToMigrate;
}, [isBalanceLoaded, minimumOlasRequiredToMigrate, safeBalance]);

const migrateValidation = useMemo<MigrateValidation>(() => {
// loading requirements
if (!isServicesLoaded) {
return { canMigrate: false, reason: CantMigrateReason.LoadingServices };
}

if (!isBalanceLoaded) {
return { canMigrate: false, reason: CantMigrateReason.LoadingBalance };
}

if (!isStakingContractInfoLoaded) {
if (isServicesLoaded && !isStakingContractInfoLoaded) {
return {
canMigrate: false,
reason: CantMigrateReason.LoadingStakingContractInfo,
};
}

if (!stakingContractInfo) {
return {
canMigrate: false,
reason: CantMigrateReason.CannotFindStakingContractInfo,
};
}

// general requirements
if (activeStakingProgramId === stakingProgramId) {
return {
Expand All @@ -88,14 +116,17 @@ export const useMigrate = (stakingProgramId: StakingProgramId) => {
};
}

if (!isRewardsAvailable) {
if ((stakingContractInfo.availableRewards ?? 0) <= 0) {
return {
canMigrate: false,
reason: CantMigrateReason.NoAvailableRewards,
};
}

if (!hasEnoughServiceSlots) {
if (
(stakingContractInfo.serviceIds ?? []).length >=
(stakingContractInfo.maxNumServices ?? 0)
) {
return {
canMigrate: false,
reason: CantMigrateReason.NoAvailableStakingSlots,
Expand Down Expand Up @@ -145,12 +176,12 @@ export const useMigrate = (stakingProgramId: StakingProgramId) => {

return { canMigrate: true };
}, [
isServicesLoaded,
isBalanceLoaded,
isStakingContractInfoLoaded,
stakingContractInfo,
activeStakingProgramId,
stakingProgramId,
isRewardsAvailable,
hasEnoughServiceSlots,
hasEnoughOlasToMigrate,
isServiceStaked,
activeStakingProgramMeta?.canMigrateTo,
Expand All @@ -159,7 +190,49 @@ export const useMigrate = (stakingProgramId: StakingProgramId) => {
serviceStatus,
]);

const firstDeployValidation = useMemo<MigrateValidation>(() => {
if (!isServicesLoaded) {
return { canMigrate: false, reason: CantMigrateReason.LoadingServices };
}

if (!isBalanceLoaded) {
return { canMigrate: false, reason: CantMigrateReason.LoadingBalance };
}

if (!hasEnoughOlasForFirstRun) {
return {
canMigrate: false,
reason: CantMigrateReason.InsufficientOlasToMigrate,
};
}

const stakingContractInfo = stakingContractInfoRecord?.[stakingProgramId];

if (stakingContractInfo?.availableRewards === 0) {
return {
canMigrate: false,
reason: CantMigrateReason.NoAvailableRewards,
};
}

if (!stakingContractInfo?.maxNumServices) {
return {
canMigrate: false,
reason: CantMigrateReason.NoAvailableStakingSlots,
};
}

return { canMigrate: true };
}, [
isServicesLoaded,
isBalanceLoaded,
hasEnoughOlasForFirstRun,
stakingContractInfoRecord,
stakingProgramId,
]);

return {
migrateValidation,
firstDeployValidation,
};
};
Loading

0 comments on commit bcf4ea1

Please sign in to comment.