-
Notifications
You must be signed in to change notification settings - Fork 178
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
refactor: move llm bots to separate package #4629
Changes from 3 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import { getHardhatConfig } from "@uma/common"; | ||
import path from "path"; | ||
|
||
// Hardhat plugins used in monitor-v2 package tests. | ||
import "@nomiclabs/hardhat-ethers"; | ||
import "@nomiclabs/hardhat-waffle"; | ||
import "hardhat-deploy"; | ||
|
||
const coreWkdir = path.dirname(require.resolve("@uma/core/package.json")); | ||
const packageWkdir = path.dirname(require.resolve("./package.json")); | ||
|
||
const configOverride = { | ||
paths: { | ||
root: coreWkdir, | ||
sources: `${coreWkdir}/contracts`, | ||
artifacts: `${coreWkdir}/artifacts`, | ||
cache: `${coreWkdir}/cache`, | ||
tests: `${packageWkdir}/test`, | ||
}, | ||
}; | ||
|
||
export default getHardhatConfig(configOverride, coreWkdir); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
{ | ||
"name": "@uma/llm-bot", | ||
"version": "1.0.0", | ||
"description": "LLM bots for UMA", | ||
"author": "UMA Team", | ||
"license": "AGPL-3.0-only", | ||
"scripts": { | ||
"build": "tsc --build", | ||
"test": "hardhat test" | ||
}, | ||
"dependencies": { | ||
"@ethersproject/abstract-provider": "^5.4.0", | ||
"@uma/common": "^2.34.0", | ||
"@uma/contracts-node": "^0.4.17", | ||
"@uma/financial-templates-lib": "^2.33.0", | ||
"async-retry": "^1.3.3", | ||
"ethers": "^5.4.2" | ||
}, | ||
"devDependencies": { | ||
"@nomicfoundation/hardhat-network-helpers": "^1.0.8", | ||
"@nomiclabs/hardhat-waffle": "^2.0.5", | ||
"chai": "^4.3.7", | ||
"ethereum-waffle": "^4.0.10", | ||
"sinon": "^15.0.1" | ||
}, | ||
"publishConfig": { | ||
"registry": "https://registry.npmjs.com/", | ||
"access": "public" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
interface BlockConfig { | ||
oneHour: number; | ||
maxBlockLookBack: number; | ||
} | ||
|
||
/** | ||
* Default configuration for different blockchain networks. | ||
* Each network is identified by its chain ID. | ||
*/ | ||
export const blockDefaults: Record<string, BlockConfig> = { | ||
"1": { | ||
// Mainnet configuration | ||
oneHour: 300, // Approximate number of blocks mined in one hour (12 seconds per block) | ||
maxBlockLookBack: 20000, // Maximum number of blocks to look back for events | ||
}, | ||
"137": { | ||
// Polygon (Matic) configuration | ||
oneHour: 1800, // Approximate number of blocks mined in one hour (2 seconds per block) | ||
maxBlockLookBack: 3499, // Maximum number of blocks to look back for events | ||
}, | ||
"10": { | ||
// Optimism configuration | ||
oneHour: 1800, // Approximate number of blocks mined in one hour (2 seconds per block) | ||
maxBlockLookBack: 10000, // Maximum number of blocks to look back for events | ||
}, | ||
"42161": { | ||
// Arbitrum configuration | ||
oneHour: 240, // Approximate number of blocks mined in one hour (15 seconds per block) | ||
maxBlockLookBack: 10000, // Maximum number of blocks to look back for events | ||
}, | ||
"43114": { | ||
// Avalanche configuration | ||
oneHour: 1800, // Approximate number of blocks mined in one hour (2 seconds per block) | ||
maxBlockLookBack: 2000, // Maximum number of blocks to look back for events | ||
}, | ||
other: { | ||
// Default configuration for other networks | ||
oneHour: 240, // Approximate number of blocks mined in one hour (15 seconds per block) | ||
maxBlockLookBack: 1000, // Maximum number of blocks to look back for events | ||
}, | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import { ContractName, ERC20Ethers, getAbi, getAddress } from "@uma/contracts-node"; | ||
import { Contract } from "ethers"; | ||
import { Provider } from "@ethersproject/abstract-provider"; | ||
import { utils } from "ethers"; | ||
|
||
export const getContractInstanceWithProvider = async <T extends Contract>( | ||
contractName: ContractName, | ||
provider: Provider, | ||
address?: string | ||
): Promise<T> => { | ||
const networkId = (await provider.getNetwork()).chainId; | ||
const contractAddress = address || (await getAddress(contractName, networkId)); | ||
const contractAbi = getAbi(contractName); | ||
return new Contract(contractAddress, contractAbi, provider) as T; | ||
}; | ||
|
||
export const tryHexToUtf8String = (ancillaryData: string): string => { | ||
try { | ||
return utils.toUtf8String(ancillaryData); | ||
} catch (err) { | ||
return ancillaryData; | ||
} | ||
}; | ||
|
||
export const getCurrencyDecimals = async (provider: Provider, currencyAddress: string): Promise<number> => { | ||
const currencyContract = await getContractInstanceWithProvider<ERC20Ethers>("ERC20", provider, currencyAddress); | ||
try { | ||
return await currencyContract.decimals(); | ||
} catch (err) { | ||
return 18; | ||
} | ||
}; | ||
|
||
export const getCurrencySymbol = async (provider: Provider, currencyAddress: string): Promise<string> => { | ||
const currencyContract = await getContractInstanceWithProvider<ERC20Ethers>("ERC20", provider, currencyAddress); | ||
try { | ||
return await currencyContract.symbol(); | ||
} catch (err) { | ||
// Try to get the symbol as bytes32 (e.g. MKR uses this). | ||
try { | ||
const bytes32SymbolIface = new utils.Interface(["function symbol() view returns (bytes32 symbol)"]); | ||
const bytes32Symbol = await provider.call({ | ||
to: currencyAddress, | ||
data: bytes32SymbolIface.encodeFunctionData("symbol"), | ||
}); | ||
return utils.parseBytes32String(bytes32SymbolIface.decodeFunctionResult("symbol", bytes32Symbol).symbol); | ||
} catch (err) { | ||
return ""; | ||
} | ||
} | ||
}; | ||
|
||
// Gets the topic of an event from its name. In case of overloaded events, the first one found is returned. | ||
export const getEventTopic = (contractName: ContractName, eventName: string): string => { | ||
const contractAbi = getAbi(contractName); | ||
const iface = new utils.Interface(contractAbi); | ||
const eventKey = Object.keys(iface.events).find((key) => iface.events[key].name === eventName); | ||
if (!eventKey) throw new Error(`Event ${eventName} not found in contract ${contractName}`); | ||
return utils.keccak256(utils.toUtf8Bytes(eventKey)); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import { TenderlySimulationResult } from "@uma/common"; | ||
|
||
const optimisticOracleV2UIBaseUrl = "https://oracle.uma.xyz"; | ||
const testnetOptimisticOracleV2UIBaseUrl = "https://testnet.oracle.uma.xyz"; | ||
|
||
// monitor-v2 package is only using Optimistic Oracle V3, so currently there is no need to generalize this. | ||
export const generateOOv3UILink = (transactionHash: string, eventIndex: number, chainId?: number): string => { | ||
// Currently testnet UI supports only goerli, so assume any other chain is production. | ||
const baseUrl = chainId === 5 ? testnetOptimisticOracleV2UIBaseUrl : optimisticOracleV2UIBaseUrl; | ||
return `<${baseUrl}/?transactionHash=${transactionHash}&eventIndex=${eventIndex}|View in UI>`; | ||
}; | ||
|
||
export const createSnapshotProposalLink = (baseUrl: string, space: string, proposalId: string): string => { | ||
return `<${baseUrl}/#/${space}/proposal/${proposalId}|Snapshot UI>`; | ||
}; | ||
|
||
export const createTenderlySimulationLink = (simulationResult?: TenderlySimulationResult): string => { | ||
if (simulationResult === undefined) { | ||
return "No Tenderly simulation available"; | ||
} else if (simulationResult.status) { | ||
return `<${simulationResult.resultUrl.url}|Tenderly simulation successful${ | ||
!simulationResult.resultUrl.public ? " (private)" : "" | ||
}>`; | ||
} else { | ||
return `<${simulationResult.resultUrl.url}|Tenderly simulation reverted${ | ||
!simulationResult.resultUrl.public ? " (private)" : "" | ||
}>`; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Was a little surprised to see this code here? Is this used (or placed to be used) in the LLC bit codebase? If so, no problem, just wanted to call it out! |
||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import { formatBytes32String, parseUnits } from "./utils"; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are some/all of these test utilities files copied? If so, could you comment that in the PR so we know which to review as fresh code? |
||
|
||
// Constants for DVM2.0. | ||
export const baseSlashAmount = parseUnits("0.001", "ether"); | ||
export const governanceSlashAmount = parseUnits("0", "ether"); | ||
export const emissionRate = parseUnits("0.18", "ether"); | ||
export const unstakeCooldown = 60 * 60 * 24 * 7; // 7 days | ||
export const phaseLength = 60 * 60 * 24; // 1 day | ||
export const gat = parseUnits("5000000", "ether"); | ||
export const spat = parseUnits("0.5", "ether"); | ||
export const maxRolls = 4; | ||
export const maxRequestsPerRound = 1000; | ||
export const minimumWaitTime = 60 * 60 * 24 * 10; // 10 days | ||
export const governorStartingId = 0; | ||
export const governanceProposalBond = parseUnits("5000", "ether"); | ||
export const emergencyQuorum = parseUnits("5000000", "ether"); | ||
export const totalSupply = parseUnits("100000000", "ether"); | ||
|
||
// Constants for Optimistic Oracle V3. | ||
export const defaultLiveness = 7200; | ||
export const defaultCurrency = { name: "Bond", symbol: "BOND", decimals: 18, finalFee: parseUnits("100") }; | ||
export const defaultOptimisticOracleV3Identifier = formatBytes32String("ASSERT_TRUTH"); | ||
export const defaultOptimisticOracleV2Identifier = formatBytes32String("YES_OR_NO_QUERY"); | ||
export const zeroRawValue = { rawValue: "0" }; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
import { addGlobalHardhatTestingAddress, ZERO_ADDRESS } from "@uma/common"; | ||
import { | ||
EmergencyProposerEthers, | ||
FixedSlashSlashingLibraryEthers, | ||
GovernorV2Ethers, | ||
ProposerV2Ethers, | ||
VotingV2Ethers, | ||
} from "@uma/contracts-node"; | ||
import { umaEcosystemFixture } from "./UmaEcosystem.Fixture"; | ||
import { | ||
baseSlashAmount, | ||
emergencyQuorum, | ||
emissionRate, | ||
gat, | ||
governanceProposalBond, | ||
governanceSlashAmount, | ||
governorStartingId, | ||
maxRequestsPerRound, | ||
maxRolls, | ||
minimumWaitTime, | ||
phaseLength, | ||
spat, | ||
totalSupply, | ||
unstakeCooldown, | ||
} from "../constants"; | ||
import { formatBytes32String, getContractFactory, hre, Signer } from "../utils"; | ||
|
||
export interface DVM2Contracts { | ||
votingV2: VotingV2Ethers; | ||
governorV2: GovernorV2Ethers; | ||
proposerV2: ProposerV2Ethers; | ||
emergencyProposer: EmergencyProposerEthers; | ||
} | ||
|
||
export const dvm2Fixture = hre.deployments.createFixture( | ||
async ({ ethers }): Promise<DVM2Contracts> => { | ||
return await deployDVM2(ethers); | ||
} | ||
); | ||
|
||
export const deployDVM2 = hre.deployments.createFixture( | ||
async ({ ethers }): Promise<DVM2Contracts> => { | ||
// Signer from ethers and hardhat-ethers are not version compatible. | ||
const [deployer] = (await ethers.getSigners()) as Signer[]; | ||
const deployerAddress = await deployer.getAddress(); | ||
|
||
// This fixture is dependent on the UMA ecosystem fixture. Run it first and grab the output. This is used in the | ||
// deployments that follows. | ||
const parentFixture = await umaEcosystemFixture(); | ||
|
||
// Deploy slashing library. | ||
const slashingLibrary = (await (await getContractFactory("FixedSlashSlashingLibrary", deployer)).deploy( | ||
baseSlashAmount, | ||
governanceSlashAmount | ||
)) as FixedSlashSlashingLibraryEthers; | ||
|
||
// Deploying VotingV2 contract requires minting voting tokens for GAT validation. | ||
await parentFixture.votingToken.addMinter(deployerAddress); | ||
await parentFixture.votingToken.mint(deployerAddress, totalSupply); | ||
const votingV2 = (await (await getContractFactory("VotingV2", deployer)).deploy( | ||
emissionRate, | ||
unstakeCooldown, | ||
phaseLength, | ||
maxRolls, | ||
maxRequestsPerRound, | ||
gat, | ||
spat, | ||
parentFixture.votingToken.address, | ||
parentFixture.finder.address, | ||
slashingLibrary.address, | ||
ZERO_ADDRESS | ||
)) as VotingV2Ethers; | ||
|
||
// Deploy GovernorV2 contract. | ||
const governorV2 = (await (await getContractFactory("GovernorV2", deployer)).deploy( | ||
parentFixture.finder.address, | ||
governorStartingId | ||
)) as GovernorV2Ethers; | ||
|
||
// Deploy ProposerV2 contract. | ||
const proposerV2 = (await (await getContractFactory("ProposerV2", deployer)).deploy( | ||
parentFixture.votingToken.address, | ||
governanceProposalBond, | ||
governorV2.address, | ||
parentFixture.finder.address | ||
)) as ProposerV2Ethers; | ||
|
||
// Deploy EmergencyProposer contract. | ||
const emergencyProposer = (await (await getContractFactory("EmergencyProposer", deployer)).deploy( | ||
parentFixture.votingToken.address, | ||
emergencyQuorum, | ||
governorV2.address, | ||
await deployer.getAddress(), | ||
minimumWaitTime | ||
)) as EmergencyProposerEthers; | ||
|
||
// Configure GovernorV2 contract. | ||
await governorV2.resetMember(1, proposerV2.address); | ||
await governorV2.resetMember(2, emergencyProposer.address); | ||
|
||
// Transfer VotingV2 ownership and register it as Oracle with the Finder. | ||
await votingV2.transferOwnership(governorV2.address); | ||
await parentFixture.finder.changeImplementationAddress(formatBytes32String("Oracle"), votingV2.address); | ||
|
||
// Add contracts to global hardhatTestingAddresses. | ||
addGlobalHardhatTestingAddress("VotingV2", votingV2.address); | ||
addGlobalHardhatTestingAddress("GovernorV2", governorV2.address); | ||
addGlobalHardhatTestingAddress("ProposerV2", proposerV2.address); | ||
addGlobalHardhatTestingAddress("EmergencyProposer", emergencyProposer.address); | ||
|
||
return { votingV2, governorV2, proposerV2, emergencyProposer }; | ||
} | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: outdated comment