From 8837c8512970b4108e2d519127dfc0b0f1c8752f Mon Sep 17 00:00:00 2001 From: robertocarlous Date: Wed, 24 Jun 2026 23:06:04 +0100 Subject: [PATCH 1/9] chore(hardhat): add upgradeable contract tooling Add @openzeppelin/hardhat-upgrades and contracts-upgradeable, register the plugin, and introduce shared deploy helpers for Transparent Proxy deployment. Make sepolia_op_devnet network config conditional so compile works without local .env credentials. --- hardhat/.gitignore | 2 + hardhat/deploy/constants.ts | 10 + hardhat/deploy/helpers.ts | 64 ++ hardhat/deploy/types.ts | 7 + hardhat/hardhat.config.ts | 20 +- hardhat/package-lock.json | 1286 ++++++++++++++++++++++++++++++++--- hardhat/package.json | 5 +- 7 files changed, 1300 insertions(+), 94 deletions(-) create mode 100644 hardhat/deploy/constants.ts create mode 100644 hardhat/deploy/helpers.ts create mode 100644 hardhat/deploy/types.ts diff --git a/hardhat/.gitignore b/hardhat/.gitignore index 96cdab9..02602df 100644 --- a/hardhat/.gitignore +++ b/hardhat/.gitignore @@ -16,3 +16,5 @@ node_modules # Hardhat Ignition default folder for deployments against a local node ignition/deployments/chain-31337 +/deployments + diff --git a/hardhat/deploy/constants.ts b/hardhat/deploy/constants.ts new file mode 100644 index 0000000..56e7833 --- /dev/null +++ b/hardhat/deploy/constants.ts @@ -0,0 +1,10 @@ +export const PROXY_KIND = "transparent" as const; + +export const PLATFORM_CONTRACTS = [ + "DinToken", + "DinCoordinator", + "DinValidatorStake", + "DINModelRegistry", +] as const; + +export type PlatformContractName = (typeof PLATFORM_CONTRACTS)[number]; diff --git a/hardhat/deploy/helpers.ts b/hardhat/deploy/helpers.ts new file mode 100644 index 0000000..32fd4cc --- /dev/null +++ b/hardhat/deploy/helpers.ts @@ -0,0 +1,64 @@ +import { ContractFactory } from "ethers"; +import { upgrades } from "hardhat"; +import * as fs from "fs"; +import * as path from "path"; + +import { PROXY_KIND } from "./constants"; +import type { PlatformAddresses } from "./types"; + +const DEPLOYMENTS_DIR = path.join(__dirname, "..", "deployments"); + +export async function deployTransparentProxy( + factory: T, + initArgs: unknown[] = [], + initializer = "initialize" +) { + const proxy = await upgrades.deployProxy(factory, initArgs, { + kind: PROXY_KIND, + initializer, + }); + await proxy.waitForDeployment(); + return proxy; +} + +export async function upgradeTransparentProxy( + proxyAddress: string, + factory: T +) { + const upgraded = await upgrades.upgradeProxy(proxyAddress, factory, { + kind: PROXY_KIND, + }); + await upgraded.waitForDeployment(); + return upgraded; +} + +export function deploymentPath(network: string): string { + return path.join(DEPLOYMENTS_DIR, `${network}.json`); +} + +export function savePlatformAddresses( + network: string, + addresses: PlatformAddresses +): void { + fs.mkdirSync(DEPLOYMENTS_DIR, { recursive: true }); + fs.writeFileSync( + deploymentPath(network), + JSON.stringify(addresses, null, 2) + "\n" + ); +} + +export function loadPlatformAddresses(network: string): PlatformAddresses { + const file = deploymentPath(network); + if (!fs.existsSync(file)) { + throw new Error( + `No deployment file at ${file}. Run the platform deploy script first.` + ); + } + return JSON.parse(fs.readFileSync(file, "utf8")) as PlatformAddresses; +} + +export async function getProxyAdminAddress( + proxyAddress: string +): Promise { + return upgrades.erc1967.getAdminAddress(proxyAddress); +} diff --git a/hardhat/deploy/types.ts b/hardhat/deploy/types.ts new file mode 100644 index 0000000..7cf229b --- /dev/null +++ b/hardhat/deploy/types.ts @@ -0,0 +1,7 @@ +export interface PlatformAddresses { + dinToken: string; + dinCoordinator: string; + dinValidatorStake: string; + dinModelRegistry: string; + proxyAdmin?: string; +} diff --git a/hardhat/hardhat.config.ts b/hardhat/hardhat.config.ts index 1516ded..3108253 100644 --- a/hardhat/hardhat.config.ts +++ b/hardhat/hardhat.config.ts @@ -1,5 +1,6 @@ import { HardhatUserConfig } from "hardhat/config"; import "@nomicfoundation/hardhat-toolbox"; +import "@openzeppelin/hardhat-upgrades"; import * as dotenv from "dotenv"; import * as fs from "fs"; import "hardhat-contract-sizer"; @@ -55,11 +56,20 @@ const config: HardhatUserConfig = { chainId: 1337, allowUnlimitedContractSize: true, }, - sepolia_op_devnet: { - url: sepolia_op_devnet_rpc_url, - accounts: [process.env.ETH_PRIVATE_KEY_0!, process.env.ETH_PRIVATE_KEY_1!], - chainId: 11155420, - }, + ...(sepolia_op_devnet_rpc_url && + process.env.ETH_PRIVATE_KEY_0 && + process.env.ETH_PRIVATE_KEY_1 + ? { + sepolia_op_devnet: { + url: sepolia_op_devnet_rpc_url, + accounts: [ + process.env.ETH_PRIVATE_KEY_0, + process.env.ETH_PRIVATE_KEY_1, + ], + chainId: 11155420, + }, + } + : {}), localhost: { url: "http://127.0.0.1:8545", diff --git a/hardhat/package-lock.json b/hardhat/package-lock.json index 53eb191..1d85771 100644 --- a/hardhat/package-lock.json +++ b/hardhat/package-lock.json @@ -10,6 +10,7 @@ "license": "ISC", "dependencies": { "@openzeppelin/contracts": "^5.3.0", + "@openzeppelin/contracts-upgradeable": "^5.3.0", "chalk": "^4.1.2", "cli-table3": "^0.6.5", "dotenv": "^17.2.3", @@ -18,6 +19,7 @@ "devDependencies": { "@nomicfoundation/hardhat-toolbox": "^5.0.0", "@nomicfoundation/hardhat-verify": "^2.1.3", + "@openzeppelin/hardhat-upgrades": "^3.9.1", "hardhat": "^2.23.0", "hardhat-contract-sizer": "^2.10.1" } @@ -30,6 +32,550 @@ "license": "MIT", "peer": true }, + "node_modules/@aws-crypto/crc32": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", + "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/crc32/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/@aws-crypto/sha256-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/@aws-crypto/sha256-js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/sha256-js/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/@aws-crypto/supports-web-crypto": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/supports-web-crypto/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/@aws-sdk/client-lambda": { + "version": "3.1075.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-lambda/-/client-lambda-3.1075.0.tgz", + "integrity": "sha512-S+sy0L7WEouGGys0kJZJ+br0sQGxgWC+0nR/4ySsym/CSg/k+VHXBdD697WJLli+X5a76tFBshx5bjgVKgLg4Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.974.23", + "@aws-sdk/credential-provider-node": "^3.972.58", + "@aws-sdk/types": "^3.973.13", + "@smithy/core": "^3.24.6", + "@smithy/fetch-http-handler": "^5.4.6", + "@smithy/node-http-handler": "^4.7.6", + "@smithy/types": "^4.14.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/@aws-sdk/core": { + "version": "3.974.23", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.974.23.tgz", + "integrity": "sha512-MiWR/uWjxjFXGzrE0Ghc5lWxUxzHsUWFhV+OX7M4cR9SrmrnZs6TXavnCWnzzdwJeFri34xQo81rvGNzK3c4BQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.13", + "@aws-sdk/xml-builder": "^3.972.31", + "@aws/lambda-invoke-store": "^0.2.2", + "@smithy/core": "^3.24.6", + "@smithy/signature-v4": "^5.4.6", + "@smithy/types": "^4.14.3", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/core/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.972.49", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.49.tgz", + "integrity": "sha512-liB3yQNHCM9k/gu/w36XHMKPluT7HTlnGUhRbBGSISDQkcr/Sy1zsZabiuvQj8WG5yW573u9RehrBvvnIQ9OEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.23", + "@aws-sdk/types": "^3.973.13", + "@smithy/core": "^3.24.6", + "@smithy/types": "^4.14.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.972.51", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.51.tgz", + "integrity": "sha512-XET0H2oofciJ5lMRWNIvRjAP7Q3wv2XT+JtJJEdhPWUMwe3TvQ9qcxonpu7vXmNngncvFpi4E2It+Tamas/naA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.23", + "@aws-sdk/types": "^3.973.13", + "@smithy/core": "^3.24.6", + "@smithy/fetch-http-handler": "^5.4.6", + "@smithy/node-http-handler": "^4.7.6", + "@smithy/types": "^4.14.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.972.56", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.56.tgz", + "integrity": "sha512-IAmc61hbgQiHht9U3x0tnRwz0lzdwOwD/i9voRgdJrKamF+JtmrBOsW9GwB7mfFonNWOWL4qARWYrF8veEMe3w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.23", + "@aws-sdk/credential-provider-env": "^3.972.49", + "@aws-sdk/credential-provider-http": "^3.972.51", + "@aws-sdk/credential-provider-login": "^3.972.55", + "@aws-sdk/credential-provider-process": "^3.972.49", + "@aws-sdk/credential-provider-sso": "^3.972.55", + "@aws-sdk/credential-provider-web-identity": "^3.972.55", + "@aws-sdk/nested-clients": "^3.997.23", + "@aws-sdk/types": "^3.973.13", + "@smithy/core": "^3.24.6", + "@smithy/credential-provider-imds": "^4.3.7", + "@smithy/types": "^4.14.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/@aws-sdk/credential-provider-login": { + "version": "3.972.55", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.55.tgz", + "integrity": "sha512-hBBkANo3cDn+h2qxxzER4a+J8JCO9o9Z/YYmU7iky6AcaarX5RRdRcHNC6SLdwY0vAXQygn6soUbDqPn3GghaA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.23", + "@aws-sdk/nested-clients": "^3.997.23", + "@aws-sdk/types": "^3.973.13", + "@smithy/core": "^3.24.6", + "@smithy/types": "^4.14.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.972.58", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.58.tgz", + "integrity": "sha512-OyCLVmSI7pZO8hxwNVX6pXhTVlJqRBTp+ijdEfJSUj0RyjHnF602OfAarOzGq6wkGodeFkYBt8MmJ6A6ycRgWw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "^3.972.49", + "@aws-sdk/credential-provider-http": "^3.972.51", + "@aws-sdk/credential-provider-ini": "^3.972.56", + "@aws-sdk/credential-provider-process": "^3.972.49", + "@aws-sdk/credential-provider-sso": "^3.972.55", + "@aws-sdk/credential-provider-web-identity": "^3.972.55", + "@aws-sdk/types": "^3.973.13", + "@smithy/core": "^3.24.6", + "@smithy/credential-provider-imds": "^4.3.7", + "@smithy/types": "^4.14.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.972.49", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.49.tgz", + "integrity": "sha512-C8h36lBuC/RnBSsjlO+dn6xZm3KbAl5vpJaVPAfQnMmz2/OISmKOc8XZcqMQgO2ADwBYNRMM6Kf3vz9G/TulMQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.23", + "@aws-sdk/types": "^3.973.13", + "@smithy/core": "^3.24.6", + "@smithy/types": "^4.14.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.972.55", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.55.tgz", + "integrity": "sha512-1FkOz74Ea5QGS9jtIoXp55T/IkSS3spv+nLTT07fRY/+T5xmEOqaYBVIaEmX4zTNvbV6g2lrtlaVKWEoNyJt3w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.23", + "@aws-sdk/nested-clients": "^3.997.23", + "@aws-sdk/token-providers": "3.1074.0", + "@aws-sdk/types": "^3.973.13", + "@smithy/core": "^3.24.6", + "@smithy/types": "^4.14.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.972.55", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.55.tgz", + "integrity": "sha512-g2BoECD1q01kTPByi56+VLVvdWDzMkKIcr77qixpqH0okw2t0U5CoPv+6S8v/D1Y2Wa6QKKtn6XAtDzP+Kfpvg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.23", + "@aws-sdk/nested-clients": "^3.997.23", + "@aws-sdk/types": "^3.973.13", + "@smithy/core": "^3.24.6", + "@smithy/types": "^4.14.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/@aws-sdk/nested-clients": { + "version": "3.997.23", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.23.tgz", + "integrity": "sha512-gO93ZPsI2bxeFZD42f1/qjDw6FAZkNZcKRO94LIiT03fzOmcJ9e/tunxjVjA1Rl69ClmVJzz8H3G9CdKef10PA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.974.23", + "@aws-sdk/signature-v4-multi-region": "^3.996.35", + "@aws-sdk/types": "^3.973.13", + "@smithy/core": "^3.24.6", + "@smithy/fetch-http-handler": "^5.4.6", + "@smithy/node-http-handler": "^4.7.6", + "@smithy/types": "^4.14.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/@aws-sdk/signature-v4-multi-region": { + "version": "3.996.35", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.35.tgz", + "integrity": "sha512-6L/VWs+Wch2stHemCGTmUNqKLMzURxQDK5boNG3Jn3kAOp71meDUuS5sbObpEvFxHDq0uWeSLFDNSYsjNt+Dlg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.13", + "@smithy/signature-v4": "^5.4.6", + "@smithy/types": "^4.14.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/signature-v4-multi-region/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/@aws-sdk/token-providers": { + "version": "3.1074.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1074.0.tgz", + "integrity": "sha512-pv80IzgGW4RnXWtft692chZOM9i6PhebVsLCcnaM4dBEPZva2fE6FXAHs76G7Rc7s3yGyX/68G0nZMrUy+Vmpg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.23", + "@aws-sdk/nested-clients": "^3.997.23", + "@aws-sdk/types": "^3.973.13", + "@smithy/core": "^3.24.6", + "@smithy/types": "^4.14.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/token-providers/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/@aws-sdk/types": { + "version": "3.973.13", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.13.tgz", + "integrity": "sha512-pEHZqRkAlHfnfAU9tK+WpKv/gBNjGJrHMgA3A0iYRGyswBS2t0pfez+lWlwktb3Bqa0ovh7w/QJTFwp3fDxLNg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/types/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/@aws-sdk/util-locate-window": { + "version": "3.965.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.965.8.tgz", + "integrity": "sha512-uUbMs1cBZPafD0ohUj6EwNf0fPZ534NvBxHox4hjX+0Rxq5paSYUem7+hi833pYrzrcnBATKIYpR02MDXT5M9g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/util-locate-window/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/@aws-sdk/util-utf8-browser": { + "version": "3.259.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8-browser/-/util-utf8-browser-3.259.0.tgz", + "integrity": "sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.3.1" + } + }, + "node_modules/@aws-sdk/util-utf8-browser/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/@aws-sdk/xml-builder": { + "version": "3.972.31", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.31.tgz", + "integrity": "sha512-SzE4Pgyl+hDF+BuyuzxUSpwnuUu9lJuO1YGgteG89/4Qv0+2IQiVQqdbPV32IozLvXWQChPQcdkk/sKvb1QHiQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/xml-builder/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/@aws/lambda-invoke-store": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.4.tgz", + "integrity": "sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@bytecodealliance/preview2-shim": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@bytecodealliance/preview2-shim/-/preview2-shim-0.17.0.tgz", + "integrity": "sha512-JorcEwe4ud0x5BS/Ar2aQWOQoFzjq/7jcnxYXCvSMh0oRm0dQXzOA+hqLDBnOMks1LLBA7dmiLLsEBl09Yd6iQ==", + "dev": true, + "license": "(Apache-2.0 WITH LLVM-exception)" + }, "node_modules/@colors/colors": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", @@ -1430,6 +1976,16 @@ "dev": true, "peer": true }, + "node_modules/@nomicfoundation/slang": { + "version": "0.18.3", + "resolved": "https://registry.npmjs.org/@nomicfoundation/slang/-/slang-0.18.3.tgz", + "integrity": "sha512-YqAWgckqbHM0/CZxi9Nlf4hjk9wUNLC9ngWCWBiqMxPIZmzsVKYuChdlrfeBPQyvQQBoOhbx+7C1005kLVQDZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bytecodealliance/preview2-shim": "0.17.0" + } + }, "node_modules/@nomicfoundation/solidity-analyzer": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer/-/solidity-analyzer-0.1.2.tgz", @@ -1527,11 +2083,174 @@ } }, "node_modules/@openzeppelin/contracts": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-5.3.0.tgz", - "integrity": "sha512-zj/KGoW7zxWUE8qOI++rUM18v+VeLTTzKs/DJFkSzHpQFPD/jKKF0TrMxBfGLl3kpdELCNccvB3zmofSzm4nlA==", + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-5.6.1.tgz", + "integrity": "sha512-Ly6SlsVJ3mj+b18W3R8gNufB7dTICT105fJhodGAGgyC2oqnBAhqSiNDJ8V8DLY05cCz81GLI0CU5vNYA1EC/w==", "license": "MIT" }, + "node_modules/@openzeppelin/contracts-upgradeable": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-5.6.1.tgz", + "integrity": "sha512-n4a/vfRs114lXyUdYg7pyY8LvFKWvCDF5lEcRRAVxap8g6ZEdLqm+9tmt2zTtRHcNMxTYp9y5t6KBof4tHp7Og==", + "license": "MIT", + "peerDependencies": { + "@openzeppelin/contracts": "5.6.1" + } + }, + "node_modules/@openzeppelin/defender-sdk-base-client": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@openzeppelin/defender-sdk-base-client/-/defender-sdk-base-client-2.7.1.tgz", + "integrity": "sha512-7gFCteA+V3396A3McgqzmirwmbPXuHJYN896O3AbsHX9XcxInN74C5Zv3tFHld0GmIX/VlaIvILNMhOpdISZjA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@aws-sdk/client-lambda": "^3.563.0", + "amazon-cognito-identity-js": "^6.3.6", + "async-retry": "^1.3.3", + "axios": "^1.7.4" + } + }, + "node_modules/@openzeppelin/defender-sdk-deploy-client": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@openzeppelin/defender-sdk-deploy-client/-/defender-sdk-deploy-client-2.7.1.tgz", + "integrity": "sha512-vFkDupn8ATW83KjZlY5U7UdsvSo9YZwOMQoVaHJO3S+Z6h0wa6cTzuQV9C0AKYq524quQkFsQ4AQq5CgsgdEkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@openzeppelin/defender-sdk-base-client": "^2.7.1", + "axios": "^1.7.4", + "lodash": "^4.17.21" + } + }, + "node_modules/@openzeppelin/defender-sdk-network-client": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@openzeppelin/defender-sdk-network-client/-/defender-sdk-network-client-2.7.1.tgz", + "integrity": "sha512-AWJKT9YKv9wH3/1AJZCztF3VIsg1sX+v8fjtyFLROqtVAzmhB8WKBRVt9GHAZ+PmsixAKDMOEbH6R1cipTIVHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@openzeppelin/defender-sdk-base-client": "^2.7.1", + "axios": "^1.7.4", + "lodash": "^4.17.21" + } + }, + "node_modules/@openzeppelin/hardhat-upgrades": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/@openzeppelin/hardhat-upgrades/-/hardhat-upgrades-3.9.1.tgz", + "integrity": "sha512-pSDjlOnIpP+PqaJVe144dK6VVKZw2v6YQusyt0OOLiCsl+WUzfo4D0kylax7zjrOxqy41EK2ipQeIF4T+cCn2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@openzeppelin/defender-sdk-base-client": "^2.1.0", + "@openzeppelin/defender-sdk-deploy-client": "^2.1.0", + "@openzeppelin/defender-sdk-network-client": "^2.1.0", + "@openzeppelin/upgrades-core": "^1.41.0", + "chalk": "^4.1.0", + "debug": "^4.1.1", + "ethereumjs-util": "^7.1.5", + "proper-lockfile": "^4.1.1", + "undici": "^6.11.1" + }, + "bin": { + "migrate-oz-cli-project": "dist/scripts/migrate-oz-cli-project.js" + }, + "peerDependencies": { + "@nomicfoundation/hardhat-ethers": "^3.0.6", + "@nomicfoundation/hardhat-verify": "^2.0.14", + "ethers": "^6.6.0", + "hardhat": "^2.24.1" + }, + "peerDependenciesMeta": { + "@nomicfoundation/hardhat-verify": { + "optional": true + } + } + }, + "node_modules/@openzeppelin/hardhat-upgrades/node_modules/undici": { + "version": "6.27.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.27.0.tgz", + "integrity": "sha512-YmfV3YnEDzXRC5lZ2jWtWWHKGUm1zIt8AhesR1tens+HTNv+YZlN/dp6G727LOvMJ8xjP9Be7Y2Sdr96LDm+pg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, + "node_modules/@openzeppelin/upgrades-core": { + "version": "1.46.0", + "resolved": "https://registry.npmjs.org/@openzeppelin/upgrades-core/-/upgrades-core-1.46.0.tgz", + "integrity": "sha512-UFSeO/4r8eeXj0C/HAwV+J4b72sE1HX0aALQFs5S2RBOsfXvKweyjQf35vrK32LQiyHdP6IPShAsEBVvpSEgGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nomicfoundation/slang": "^0.18.3", + "bignumber.js": "^9.1.2", + "cbor": "^10.0.0", + "chalk": "^4.1.0", + "compare-versions": "^6.0.0", + "debug": "^4.1.1", + "ethereumjs-util": "^7.0.3", + "minimatch": "^10.2.5", + "minimist": "^1.2.7", + "proper-lockfile": "^4.1.1", + "solidity-ast": "^0.4.60" + }, + "bin": { + "openzeppelin-upgrades-core": "dist/cli/cli.js" + } + }, + "node_modules/@openzeppelin/upgrades-core/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@openzeppelin/upgrades-core/node_modules/brace-expansion": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@openzeppelin/upgrades-core/node_modules/cbor": { + "version": "10.0.12", + "resolved": "https://registry.npmjs.org/cbor/-/cbor-10.0.12.tgz", + "integrity": "sha512-exQDevYd7ZQLP4moMQcZkKCVZsXLAtUSflObr3xTh4xzFIv/xBCdvCd6L259kQOUP2kcTC0jvC6PpZIf/WmRXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "nofilter": "^3.0.2" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@openzeppelin/upgrades-core/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@scure/base": { "version": "1.1.9", "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.9.tgz", @@ -1645,47 +2364,239 @@ "node": ">=6" } }, - "node_modules/@sentry/tracing": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-5.30.0.tgz", - "integrity": "sha512-dUFowCr0AIMwiLD7Fs314Mdzcug+gBVo/+NCMyDw8tFxJkwWAKl7Qa2OZxLQ0ZHjakcj1hNKfCQJ9rhyfOl4Aw==", + "node_modules/@sentry/tracing": { + "version": "5.30.0", + "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-5.30.0.tgz", + "integrity": "sha512-dUFowCr0AIMwiLD7Fs314Mdzcug+gBVo/+NCMyDw8tFxJkwWAKl7Qa2OZxLQ0ZHjakcj1hNKfCQJ9rhyfOl4Aw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sentry/hub": "5.30.0", + "@sentry/minimal": "5.30.0", + "@sentry/types": "5.30.0", + "@sentry/utils": "5.30.0", + "tslib": "^1.9.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@sentry/types": { + "version": "5.30.0", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-5.30.0.tgz", + "integrity": "sha512-R8xOqlSTZ+htqrfteCWU5Nk0CDN5ApUTvrlvBuiH1DyP6czDZ4ktbZB0hAgBlVcK0U+qpD3ag3Tqqpa5Q67rPw==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=6" + } + }, + "node_modules/@sentry/utils": { + "version": "5.30.0", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-5.30.0.tgz", + "integrity": "sha512-zaYmoH0NWWtvnJjC9/CBseXMtKHm/tm40sz3YfJRxeQjyzRqNQPgivpd9R/oDJCYj999mzdW382p/qi2ypjLww==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sentry/types": "5.30.0", + "tslib": "^1.9.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@smithy/core": { + "version": "3.26.0", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.26.0.tgz", + "integrity": "sha512-mLUktFAn+Pa2agl1J7VgtYNFWCX8/b4GMJSK1hCu4YCvtBfM6F8Os3EP4ry+DFFlXOf3wyvlgXhuUdFoy52D3g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/crc32": "5.2.0", + "@smithy/types": "^4.15.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/core/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/@smithy/credential-provider-imds": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.4.2.tgz", + "integrity": "sha512-18UMDMyrAbDcpmL1gLUA7ww0fRTcdCrSjSJOi2Sbld+tVjwD/pW+OAwjlScFLR7vvBnhZrIPQ7kVuTf1mnJLug==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.26.0", + "@smithy/types": "^4.15.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/@smithy/fetch-http-handler": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.5.2.tgz", + "integrity": "sha512-Ei/UK/QMhq0rKaMqGPlOAkE2yS9DZeYmZdk1RAKc3vp3zxgleZHZyBLlZv8yLsxljX4svCRuMTD6u3LLIcU4Bg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.26.0", + "@smithy/types": "^4.15.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/fetch-http-handler/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/is-array-buffer/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/@smithy/node-http-handler": { + "version": "4.8.2", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.8.2.tgz", + "integrity": "sha512-wfl1uwrAqMH9/pi4kqBo5LBcFwrJLxuDLqL7p7qNcJIFcyZDUc6pzhYk4CYv+DP7fIUpQCZumwNnkhPKS52osQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.26.0", + "@smithy/types": "^4.15.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-http-handler/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/@smithy/signature-v4": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.5.2.tgz", + "integrity": "sha512-7xHpmPY4rt0IOmeAA8EfjgEH8isT+587TCdy9H6a7d4OMi5CQ0oEHhWllunvPu4j4Cq0vTFwdxXN/kABWPjdyA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.26.0", + "@smithy/types": "^4.15.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/signature-v4/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "dev": true, - "license": "MIT", + "license": "0BSD" + }, + "node_modules/@smithy/types": { + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.15.0.tgz", + "integrity": "sha512-Z5TAOxygoFvybJV3igo5SloFflSokHx2hu1eFA+DxDTcn+FtKxUSui+rbTRG1pAafMA888Z3MVvCWUuvCrTXjg==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "@sentry/hub": "5.30.0", - "@sentry/minimal": "5.30.0", - "@sentry/types": "5.30.0", - "@sentry/utils": "5.30.0", - "tslib": "^1.9.3" + "tslib": "^2.6.2" }, "engines": { - "node": ">=6" + "node": ">=18.0.0" } }, - "node_modules/@sentry/types": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-5.30.0.tgz", - "integrity": "sha512-R8xOqlSTZ+htqrfteCWU5Nk0CDN5ApUTvrlvBuiH1DyP6czDZ4ktbZB0hAgBlVcK0U+qpD3ag3Tqqpa5Q67rPw==", + "node_modules/@smithy/types/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "dev": true, - "license": "BSD-3-Clause", + "license": "0BSD" + }, + "node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=6" + "node": ">=14.0.0" } }, - "node_modules/@sentry/utils": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-5.30.0.tgz", - "integrity": "sha512-zaYmoH0NWWtvnJjC9/CBseXMtKHm/tm40sz3YfJRxeQjyzRqNQPgivpd9R/oDJCYj999mzdW382p/qi2ypjLww==", + "node_modules/@smithy/util-buffer-from/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "dev": true, - "license": "BSD-3-Clause", + "license": "0BSD" + }, + "node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "@sentry/types": "5.30.0", - "tslib": "^1.9.3" + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6" + "node": ">=14.0.0" } }, + "node_modules/@smithy/util-utf8/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, "node_modules/@solidity-parser/parser": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.14.5.tgz", @@ -1811,7 +2722,6 @@ "integrity": "sha512-Xh8vSwUeMKeYYrj3cX4lGQgFSF/N03r+tv4AiLl1SucqV+uTQpxRcnM8AkXKHwYP9ZPXOYXRr2KPXpVlIvqh9w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/node": "*" } @@ -1891,7 +2801,6 @@ "integrity": "sha512-u0HuPQwe/dHrItgHHpmw3N2fYCR6x4ivMNbPHRkBVP4CvN+kiRrKHWk3i8tXiO/joPwXLMYvF9TTF0eqgHIuOw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -1902,7 +2811,6 @@ "integrity": "sha512-uRwJqmiXmh9++aSu1VNEn3iIxWOhd8AHXNSdlaLfdAAdSTY9jYVeGWnzejM3dvrkbqE3/hyQkQQ29IFATEGlew==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/node": "*" } @@ -1929,7 +2837,6 @@ "integrity": "sha512-hHxJU6PAEUn0TP4S/ZOzuTUvJWuZ6eIKeNKb5RBpODvSl6hp1Wrw4s7ATY50rklRCScUDpHzVA/DQdSjJ3UoYQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/node": "*" } @@ -2032,6 +2939,44 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/amazon-cognito-identity-js": { + "version": "6.3.18", + "resolved": "https://registry.npmjs.org/amazon-cognito-identity-js/-/amazon-cognito-identity-js-6.3.18.tgz", + "integrity": "sha512-tsFGh5sMWvcD0LzDqkfJLZeNAglswtFB2clep7E1xJZpo/djQgrDHvXTzgPmzZuA3oNF2UXynSuENEDNTS56Pg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-js": "1.2.2", + "buffer": "4.9.2", + "fast-base64-decode": "^1.0.0", + "isomorphic-unfetch": "^3.0.0", + "js-cookie": "^3.0.7" + } + }, + "node_modules/amazon-cognito-identity-js/node_modules/@aws-crypto/sha256-js": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-1.2.2.tgz", + "integrity": "sha512-Nr1QJIbW/afYYGzYvrF70LtaHrIRtd4TNAglX8BvlfxJLZ45SAmueIKYl5tWoNBPzp65ymXGFK0Bb1vZUpuc9g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^1.2.2", + "@aws-sdk/types": "^3.1.0", + "tslib": "^1.11.1" + } + }, + "node_modules/amazon-cognito-identity-js/node_modules/@aws-crypto/util": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-1.2.2.tgz", + "integrity": "sha512-H8PjG5WJ4wz0UXAFXeJjWCW1vkvIJ3qUUD+rGRwJ2/hj+xT58Qle2MTql/2MGzkU+1JLAFuR6aJpLAjHwhmwwg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.1.0", + "@aws-sdk/util-utf8-browser": "^3.0.0", + "tslib": "^1.11.1" + } + }, "node_modules/amdefine": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", @@ -2224,13 +3169,22 @@ "license": "MIT", "peer": true }, + "node_modules/async-retry": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz", + "integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "retry": "0.13.1" + } + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/at-least-node": { "version": "1.0.0", @@ -2249,7 +3203,6 @@ "integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", @@ -2269,11 +3222,31 @@ "integrity": "sha512-xz7wQ8xDhdyP7tQxwdteLYeFfS68tSMNCZ/Y37WJ4bhGfKPpqEIlmIyueQHqOyoPhE6xNUqjzRr8ra0eF9VRvA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "safe-buffer": "^5.0.1" } }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/bech32": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz", @@ -2282,6 +3255,16 @@ "license": "MIT", "peer": true }, + "node_modules/bignumber.js": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", + "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -2300,8 +3283,7 @@ "resolved": "https://registry.npmjs.org/blakejs/-/blakejs-1.2.1.tgz", "integrity": "sha512-QXUSXI3QVc/gJME0dBpXrag1kbzOqCjCX8/b54ntNyW6sjtoqxqRk3LTmXzaJoh71zMsDCjM+47jS7XiwN/+fQ==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/bn.js": { "version": "5.2.1", @@ -2310,6 +3292,13 @@ "dev": true, "license": "MIT" }, + "node_modules/bowser": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz", + "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==", + "dev": true, + "license": "MIT" + }, "node_modules/boxen": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz", @@ -2389,7 +3378,6 @@ "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "buffer-xor": "^1.0.3", "cipher-base": "^1.0.0", @@ -2405,7 +3393,6 @@ "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "base-x": "^3.0.2" } @@ -2416,13 +3403,24 @@ "integrity": "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "bs58": "^4.0.0", "create-hash": "^1.1.0", "safe-buffer": "^5.1.2" } }, + "node_modules/buffer": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", + "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -2435,8 +3433,7 @@ "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/bytes": { "version": "3.1.2", @@ -2454,7 +3451,6 @@ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" @@ -2619,7 +3615,6 @@ "integrity": "sha512-3Ek9H3X6pj5TgenXYtNWdaBon1tgYCaebd+XPg0keyjEbEfkD4KkmAxkQ/i1vYvxdcT5nscLBfq9VJRmCBcFSw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "inherits": "^2.0.4", "safe-buffer": "^5.2.1" @@ -2713,7 +3708,6 @@ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "delayed-stream": "~1.0.0" }, @@ -2879,6 +3873,13 @@ "node": ">= 12" } }, + "node_modules/compare-versions": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.1.1.tgz", + "integrity": "sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==", + "dev": true, + "license": "MIT" + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -2964,7 +3965,6 @@ "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "cipher-base": "^1.0.1", "inherits": "^2.0.1", @@ -2979,7 +3979,6 @@ "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "cipher-base": "^1.0.3", "create-hash": "^1.1.0", @@ -3085,7 +4084,6 @@ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=0.4.0" } @@ -3155,7 +4153,6 @@ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", @@ -3224,7 +4221,6 @@ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 0.4" } @@ -3235,7 +4231,6 @@ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 0.4" } @@ -3246,7 +4241,6 @@ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "es-errors": "^1.3.0" }, @@ -3260,7 +4254,6 @@ "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", @@ -3562,7 +4555,6 @@ "integrity": "sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg==", "dev": true, "license": "MPL-2.0", - "peer": true, "dependencies": { "@types/bn.js": "^5.1.0", "bn.js": "^5.1.2", @@ -3580,7 +4572,6 @@ "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/pbkdf2": "^3.0.0", "@types/secp256k1": "^4.0.1", @@ -3737,12 +4728,18 @@ "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "md5.js": "^1.3.4", "safe-buffer": "^5.1.1" } }, + "node_modules/fast-base64-decode": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fast-base64-decode/-/fast-base64-decode-1.0.0.tgz", + "integrity": "sha512-qwaScUgUGBYeDNRnbc/KyllVU88Jk1pRHPStuF/lO7B0/RTRLj7U0lkdTAutlBblY08rwZDff6tNU9cjv6j//Q==", + "dev": true, + "license": "MIT" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -3900,7 +4897,6 @@ "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -3969,7 +4965,6 @@ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "dev": true, "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -4001,7 +4996,6 @@ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", @@ -4038,7 +5032,6 @@ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" @@ -4287,7 +5280,6 @@ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 0.4" }, @@ -4434,7 +5426,6 @@ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 0.4" }, @@ -4448,7 +5439,6 @@ "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "has-symbols": "^1.0.3" }, @@ -4465,7 +5455,6 @@ "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "inherits": "^2.0.4", "readable-stream": "^3.6.0", @@ -4492,7 +5481,6 @@ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "function-bind": "^1.1.2" }, @@ -4610,6 +5598,27 @@ "node": ">=0.10.0" } }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -4793,8 +5802,7 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/isexe": { "version": "2.0.0", @@ -4804,6 +5812,24 @@ "license": "ISC", "peer": true }, + "node_modules/isomorphic-unfetch": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/isomorphic-unfetch/-/isomorphic-unfetch-3.1.0.tgz", + "integrity": "sha512-geDJjpoZ8N0kWexiwkX8F9NkTsXhetLPVbZFQ+JTW239QNOwvB0gniuR1Wc6f0AMTn7/mFGyXvHTifrCp/GH8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "node-fetch": "^2.6.1", + "unfetch": "^4.2.0" + } + }, + "node_modules/js-cookie": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.8.tgz", + "integrity": "sha512-yeJd4aNAdYZQjaon2bpD/Gb0B/omw7HQOsynXXcOiWVCacbBcPlgn8S/d1X6blFSaHao7ozqtW7NZW19xpCtIw==", + "dev": true, + "license": "MIT" + }, "node_modules/js-sha3": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", @@ -5048,7 +6074,6 @@ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 0.4" } @@ -5059,7 +6084,6 @@ "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "hash-base": "^3.0.0", "inherits": "^2.0.1", @@ -5177,7 +6201,6 @@ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 0.6" } @@ -5188,7 +6211,6 @@ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "mime-db": "1.52.0" }, @@ -5229,7 +6251,6 @@ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "dev": true, "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -5421,6 +6442,27 @@ "lodash": "^4.17.21" } }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/node-gyp-build": { "version": "4.8.4", "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", @@ -5681,7 +6723,6 @@ "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "create-hash": "^1.1.2", "create-hmac": "^1.1.4", @@ -5785,13 +6826,34 @@ "node": ">= 6" } }, + "node_modules/proper-lockfile": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", + "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "retry": "^0.12.0", + "signal-exit": "^3.0.2" + } + }, + "node_modules/proper-lockfile/node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/qs": { "version": "6.14.0", @@ -6023,6 +7085,16 @@ "node": ">=4" } }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/reusify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", @@ -6041,7 +7113,6 @@ "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "hash-base": "^3.0.0", "inherits": "^2.0.1" @@ -6053,7 +7124,6 @@ "integrity": "sha512-d5gdPmgQ0Z+AklL2NVXr/IoSjNZFfTVvQWzL/AM2AOcSzYP2xjlb0AC8YyCLc41MSNf6P6QVtjgPdmVtzb+4lQ==", "dev": true, "license": "MPL-2.0", - "peer": true, "dependencies": { "bn.js": "^5.2.0" }, @@ -6265,8 +7335,7 @@ "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-3.0.1.tgz", "integrity": "sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/secp256k1": { "version": "4.0.4", @@ -6275,7 +7344,6 @@ "dev": true, "hasInstallScript": true, "license": "MIT", - "peer": true, "dependencies": { "elliptic": "^6.5.7", "node-addon-api": "^5.0.0", @@ -6290,8 +7358,7 @@ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/semver": { "version": "6.3.1", @@ -6318,8 +7385,7 @@ "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/setprototypeof": { "version": "1.2.0", @@ -6334,7 +7400,6 @@ "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", "dev": true, "license": "(MIT AND BSD-3-Clause)", - "peer": true, "dependencies": { "inherits": "^2.0.1", "safe-buffer": "^5.0.1" @@ -6506,6 +7571,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, "node_modules/sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", @@ -6575,6 +7647,13 @@ "semver": "bin/semver" } }, + "node_modules/solidity-ast": { + "version": "0.4.62", + "resolved": "https://registry.npmjs.org/solidity-ast/-/solidity-ast-0.4.62.tgz", + "integrity": "sha512-jSC7msQCkJXIzM8LlDjRZ5cif5w40g6THlXHFk3zchbL5dm3YLoBETvqPGo5KndYkftjhcs5kz1fnTu4d34lVQ==", + "dev": true, + "license": "MIT" + }, "node_modules/solidity-coverage": { "version": "0.8.15", "resolved": "https://registry.npmjs.org/solidity-coverage/-/solidity-coverage-0.8.15.tgz", @@ -7087,6 +8166,13 @@ "node": ">=0.6" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true, + "license": "MIT" + }, "node_modules/ts-command-line-args": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/ts-command-line-args/-/ts-command-line-args-2.5.1.tgz", @@ -7379,8 +8465,14 @@ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" + }, + "node_modules/unfetch": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/unfetch/-/unfetch-4.2.0.tgz", + "integrity": "sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA==", + "dev": true, + "license": "MIT" }, "node_modules/universalify": { "version": "0.1.2", @@ -7559,6 +8651,24 @@ "@scure/bip39": "1.3.0" } }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", diff --git a/hardhat/package.json b/hardhat/package.json index 08c34ca..a421ddc 100644 --- a/hardhat/package.json +++ b/hardhat/package.json @@ -3,7 +3,8 @@ "version": "1.0.0", "main": "index.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "compile": "hardhat compile", + "test": "hardhat test" }, "keywords": [], "author": "", @@ -12,11 +13,13 @@ "devDependencies": { "@nomicfoundation/hardhat-toolbox": "^5.0.0", "@nomicfoundation/hardhat-verify": "^2.1.3", + "@openzeppelin/hardhat-upgrades": "^3.9.1", "hardhat": "^2.23.0", "hardhat-contract-sizer": "^2.10.1" }, "dependencies": { "@openzeppelin/contracts": "^5.3.0", + "@openzeppelin/contracts-upgradeable": "^5.3.0", "chalk": "^4.1.2", "cli-table3": "^0.6.5", "dotenv": "^17.2.3", From 4a5ba71b314c73a34d6ac7401998eadfbd05ebdf Mon Sep 17 00:00:00 2001 From: robertocarlous Date: Wed, 24 Jun 2026 23:11:18 +0100 Subject: [PATCH 2/9] feat(contracts): convert DinToken to upgradeable pattern Replace constructor and immutable OWNER with initializer setup, ERC20Upgradeable, and a one-time setCoordinator wiring step for proxy deployment order. --- hardhat/contracts/DinToken.sol | 48 ++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/hardhat/contracts/DinToken.sol b/hardhat/contracts/DinToken.sol index d7f411b..9cf115a 100644 --- a/hardhat/contracts/DinToken.sol +++ b/hardhat/contracts/DinToken.sol @@ -1,40 +1,42 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.28; -import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import "@openzeppelin/contracts/access/Ownable.sol"; +import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; -/// @title DIN Token -/// @notice ERC20-compliant token for the DIN Protocol ecosystem. -/// @dev Minting is restricted to the owner (DinCoordinator), which can be updated through transfer of ownership. - -contract DinToken is ERC20 { +contract DinToken is Initializable, ERC20Upgradeable, OwnableUpgradeable { error InvalidAddress(); error Unauthorized(); + error CoordinatorAlreadySet(); + + address public coordinator; - // Event for off-chain indexing event TokensMinted(address indexed to, uint256 amount); + event CoordinatorSet(address indexed coordinator); - /// @notice Immutable owner - DinCoordinator, set once at deployment - address public immutable OWNER; + constructor() { + _disableInitializers(); + } + + function initialize() public initializer { + __ERC20_init("DIN Token", "DIN"); + __Ownable_init(msg.sender); + } - /// @notice Constructor initializes the token with name and symbol. - constructor(address owner_) ERC20("DIN Token", "DIN") { - OWNER = owner_; - // Optionally mint initial supply to the deployer if needed - // _mint(owner_, initialSupply * 10 ** decimals()); + function setCoordinator(address coordinator_) external onlyOwner { + if (coordinator != address(0)) revert CoordinatorAlreadySet(); + if (coordinator_ == address(0)) revert InvalidAddress(); + coordinator = coordinator_; + emit CoordinatorSet(coordinator_); } - /// @notice Modifier: restricts to immutable owner (gas-efficient, no SLOAD) - modifier onlyOwner() { - if (msg.sender != OWNER) revert Unauthorized(); + modifier onlyCoordinator() { + if (msg.sender != coordinator) revert Unauthorized(); _; } - /// @notice Mint new tokens — callable only by the contract owner. - /// @param to The address to receive minted tokens. - /// @param amount The number of tokens to mint (18 decimals). - function mint(address to, uint256 amount) external onlyOwner { + function mint(address to, uint256 amount) external onlyCoordinator { if (to == address(0)) revert InvalidAddress(); _mint(to, amount); emit TokensMinted(to, amount); From 4f334748eee0453a6d6d734c203de4d358f117ce Mon Sep 17 00:00:00 2001 From: robertocarlous Date: Wed, 24 Jun 2026 23:15:01 +0100 Subject: [PATCH 3/9] feat(contracts): convert DinCoordinator to upgradeable pattern Accept an externally deployed DinToken proxy in initialize instead of deploying inline, and replace immutable dinToken with storage. --- hardhat/contracts/DinCoordinator.sol | 34 ++++++++++++++++++---------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/hardhat/contracts/DinCoordinator.sol b/hardhat/contracts/DinCoordinator.sol index 5c7ed47..61fc275 100644 --- a/hardhat/contracts/DinCoordinator.sol +++ b/hardhat/contracts/DinCoordinator.sol @@ -1,8 +1,9 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.28; -import "./DinToken.sol"; // Import the DINToken contract interface -import "@openzeppelin/contracts/access/Ownable.sol"; +import "./DinToken.sol"; +import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import "@openzeppelin/contracts/utils/ReentrancyGuardTransient.sol"; interface IDinValidatorStake { @@ -11,11 +12,16 @@ interface IDinValidatorStake { function removeSlasherContract(address slasherContract) external; } -contract DinCoordinator is Ownable, ReentrancyGuardTransient { - DinToken public immutable dinToken; +contract DinCoordinator is + Initializable, + OwnableUpgradeable, + ReentrancyGuardTransient +{ + DinToken public dinToken; IDinValidatorStake public dinValidatorStakeContract; - uint256 public dinPerEth = 1_000_000 * 1e18; // 1M DIN tokens (with 18 decimals) + uint256 public dinPerEth; + event EthDepositAndDINminted( address indexed user, uint256 ethAmount, @@ -31,16 +37,21 @@ contract DinCoordinator is Ownable, ReentrancyGuardTransient { error ZeroValue(); error TransferFailed(); - constructor() Ownable(msg.sender) { - // Deploy DINToken - dinToken = new DinToken(address(this)); + constructor() { + _disableInitializers(); + } + + function initialize(address dinToken_) external initializer { + if (dinToken_ == address(0)) revert InvalidAddress(); + __Ownable_init(msg.sender); + dinToken = DinToken(dinToken_); + dinPerEth = 1_000_000 * 1e18; } - /// @notice User deposits ETH → receives DIN tokens function depositAndMint() external payable nonReentrant { if (msg.value == 0) revert ZeroValue(); - uint256 mintAmount = (msg.value * dinPerEth) / 1e18; // ✅ Safe decimal math + uint256 mintAmount = (msg.value * dinPerEth) / 1e18; dinToken.mint(msg.sender, mintAmount); emit EthDepositAndDINminted(msg.sender, msg.value, mintAmount); @@ -48,7 +59,7 @@ contract DinCoordinator is Ownable, ReentrancyGuardTransient { function withdraw() external onlyOwner nonReentrant { uint256 balance = address(this).balance; - if (balance == 0) return; // ✅ No-op if empty + if (balance == 0) return; (bool success, ) = payable(owner()).call{value: balance}(""); if (!success) revert TransferFailed(); } @@ -77,7 +88,6 @@ contract DinCoordinator is Ownable, ReentrancyGuardTransient { emit ValidatorStakeContractUpdated(validatorStakeContract); } - // ✅ Optional: Update exchange rate (with safeguards) function updateDinPerEth(uint256 newRate) external onlyOwner { if (newRate == 0) revert ZeroValue(); dinPerEth = newRate; From cfe9b498d309257088b92da534febf7726f54892 Mon Sep 17 00:00:00 2001 From: robertocarlous Date: Wed, 24 Jun 2026 23:17:27 +0100 Subject: [PATCH 4/9] feat(contracts): convert DinValidatorStake to upgradeable pattern Replace constructor with initialize, move DIN_TOKEN and DIN_COORDINATOR from immutables into storage, and adopt OwnableUpgradeable. --- hardhat/contracts/DinValidatorStake.sol | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/hardhat/contracts/DinValidatorStake.sol b/hardhat/contracts/DinValidatorStake.sol index fe9436f..021da6a 100644 --- a/hardhat/contracts/DinValidatorStake.sol +++ b/hardhat/contracts/DinValidatorStake.sol @@ -1,12 +1,17 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity ^0.8.28; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import "@openzeppelin/contracts/utils/ReentrancyGuardTransient.sol"; -contract DinValidatorStake is Ownable, ReentrancyGuardTransient { +contract DinValidatorStake is + Initializable, + OwnableUpgradeable, + ReentrancyGuardTransient +{ error NotDINCoordinator(); error ValidatorIsBlacklisted(); error ValidatorNotBlacklisted(); @@ -22,8 +27,8 @@ contract DinValidatorStake is Ownable, ReentrancyGuardTransient { error NoPendingWithdrawal(); error WithdrawalNotReady(); - IERC20 public immutable DIN_TOKEN; - address public immutable DIN_COORDINATOR; + IERC20 public DIN_TOKEN; + address public DIN_COORDINATOR; using SafeERC20 for IERC20; @@ -67,10 +72,18 @@ contract DinValidatorStake is Ownable, ReentrancyGuardTransient { mapping(address => ValidatorInfo) public validators; - constructor(address dinToken, address dinCoordinator) Ownable(msg.sender) { + constructor() { + _disableInitializers(); + } + + function initialize( + address dinToken, + address dinCoordinator + ) external initializer { if (dinToken == address(0) || dinCoordinator == address(0)) { revert InvalidAddress(); } + __Ownable_init(msg.sender); DIN_TOKEN = IERC20(dinToken); DIN_COORDINATOR = dinCoordinator; } From 07358f87c001dd1e4a9466cc51eb40819acecb75 Mon Sep 17 00:00:00 2001 From: robertocarlous Date: Wed, 24 Jun 2026 23:21:23 +0100 Subject: [PATCH 5/9] feat(contracts): convert DINModelRegistry to upgradeable pattern Replace constructor and daoAdmin with OwnableUpgradeable, move fee defaults into initialize, and drop setDAOAdmin in favor of transferOwnership. --- hardhat/contracts/DINModelRegistry.sol | 176 +++++-------------------- 1 file changed, 36 insertions(+), 140 deletions(-) diff --git a/hardhat/contracts/DINModelRegistry.sol b/hardhat/contracts/DINModelRegistry.sol index 3fcc489..a879913 100644 --- a/hardhat/contracts/DINModelRegistry.sol +++ b/hardhat/contracts/DINModelRegistry.sol @@ -1,10 +1,8 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity ^0.8.28; -/// @title DIN Model Registry (v2 - Request/Approval Based) -/// @author InfiniteZero Foundation -/// @notice Secure registry with approval layer for manifests -/// @dev Minimal, auditable, DAO-controlled primitive +import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; interface IDinValidatorStake { function isSlasherContract( @@ -16,11 +14,7 @@ interface IOwnable { function owner() external view returns (address); } -contract DINModelRegistry { - /*////////////////////////////////////////////////////////////// - ERRORS - //////////////////////////////////////////////////////////////*/ - error NotDINDAOAdmin(); +contract DINModelRegistry is Initializable, OwnableUpgradeable { error NotModelOwner(); error InvalidModelId(); error InvalidRequestId(); @@ -33,22 +27,17 @@ contract DINModelRegistry { error TaskCoordinatorAlreadyRegistered(); error TaskAuditorAlreadyRegistered(); error ZeroAddress(); - // Approval-time revalidation errors error CoordinatorNoLongerSlasher(); error AuditorNoLongerSlasher(); error CoordinatorOwnershipChanged(); error AuditorOwnershipChanged(); - /*////////////////////////////////////////////////////////////// - EVENTS - //////////////////////////////////////////////////////////////*/ event ModelRegistrationRequested( uint256 indexed requestId, address indexed requester ); event ModelApproved(uint256 indexed requestId, uint256 indexed modelId); event ModelRejected(uint256 indexed requestId); - event ManifestUpdateRequested( uint256 indexed requestId, uint256 indexed modelId @@ -59,31 +48,20 @@ contract DINModelRegistry { bytes32 newCID ); event ManifestUpdateRejected(uint256 indexed requestId); - - // Kill-switch events event ModelDisabled(uint256 indexed modelId); event ModelEnabled(uint256 indexed modelId); - - // Individual fee events (granular tracking) event OpenSourceFeeUpdated(uint256 newFee); event ProprietaryFeeUpdated(uint256 newFee); event OpenSourceUpdateFeeUpdated(uint256 newFee); event ProprietaryUpdateFeeUpdated(uint256 newFee); - - // Combined fee event (atomic governance proposals) event FeesUpdated( uint256 openSourceFee, uint256 proprietaryFee, uint256 openSourceUpdateFee, uint256 proprietaryUpdateFee ); - event FeesWithdrawn(address indexed to, uint256 amount); - event DAOAdminUpdated(address indexed oldAdmin, address indexed newAdmin); - /*////////////////////////////////////////////////////////////// - STRUCTS - //////////////////////////////////////////////////////////////*/ struct Model { address owner; bool isOpenSource; @@ -114,36 +92,33 @@ contract DINModelRegistry { bool approved; } - /*////////////////////////////////////////////////////////////// - STATE VARIABLES - //////////////////////////////////////////////////////////////*/ - address public daoAdmin; IDinValidatorStake public dinValidatorStake; - // Registration fees - uint256 public openSourceFee = 0.000001 ether; - uint256 public proprietaryFee = 0.00001 ether; - - // Manifest update fees - uint256 public openSourceUpdateFee = 0.0000001 ether; - uint256 public proprietaryUpdateFee = 0.000001 ether; + uint256 public openSourceFee; + uint256 public proprietaryFee; + uint256 public openSourceUpdateFee; + uint256 public proprietaryUpdateFee; Model[] private models; ModelRequest[] public modelRequests; ManifestUpdateRequest[] public manifestRequests; - mapping(address => uint256) private _modelIdByTaskCoordinator; // Stores modelId + 1 - mapping(address => uint256) private _modelIdByTaskAuditor; // Stores modelId + 1 - - // Kill-switch: disabled models cannot have manifests updated or participate + mapping(address => uint256) private _modelIdByTaskCoordinator; + mapping(address => uint256) private _modelIdByTaskAuditor; mapping(uint256 => bool) public modelDisabled; - /*////////////////////////////////////////////////////////////// - MODIFIERS - //////////////////////////////////////////////////////////////*/ - modifier onlyDAOAdmin() { - if (msg.sender != daoAdmin) revert NotDINDAOAdmin(); - _; + constructor() { + _disableInitializers(); + } + + function initialize(address dinValidatorStake_) external initializer { + if (dinValidatorStake_ == address(0)) revert ZeroAddress(); + __Ownable_init(msg.sender); + dinValidatorStake = IDinValidatorStake(dinValidatorStake_); + openSourceFee = 0.000001 ether; + proprietaryFee = 0.00001 ether; + openSourceUpdateFee = 0.0000001 ether; + proprietaryUpdateFee = 0.000001 ether; } modifier onlyModelOwner(uint256 modelId) { @@ -157,24 +132,6 @@ contract DINModelRegistry { _; } - /*////////////////////////////////////////////////////////////// - CONSTRUCTOR - //////////////////////////////////////////////////////////////*/ - constructor(address _dinValidatorStake) { - daoAdmin = msg.sender; // DIN DAO representative - dinValidatorStake = IDinValidatorStake(_dinValidatorStake); - } - - /*////////////////////////////////////////////////////////////// - MODEL REGISTRATION REQUEST - //////////////////////////////////////////////////////////////*/ - - /// @notice Submit a model registration request for DAO review - /// @param manifestCID IPFS CID (bytes32) containing the model manifest - /// @param taskCoordinator Address of the registered task coordinator slasher contract - /// @param taskAuditor Address of the registered task auditor slasher contract - /// @param isOpenSource Whether the model is open-source or proprietary - /// @return requestId ID of the pending request function requestModelRegistration( bytes32 manifestCID, address taskCoordinator, @@ -220,24 +177,17 @@ contract DINModelRegistry { emit ModelRegistrationRequested(requestId, msg.sender); } - /*////////////////////////////////////////////////////////////// - APPROVE / REJECT MODEL - //////////////////////////////////////////////////////////////*/ - - /// @notice DAO approves a pending model registration - function approveModel(uint256 requestId) external onlyDAOAdmin { + function approveModel(uint256 requestId) external onlyOwner { if (requestId >= modelRequests.length) revert InvalidRequestId(); ModelRequest storage req = modelRequests[requestId]; if (req.processed) revert AlreadyProcessed(); - // Guard against reusing the same coordinator or auditor across models if (_modelIdByTaskCoordinator[req.taskCoordinator] != 0) revert TaskCoordinatorAlreadyRegistered(); if (_modelIdByTaskAuditor[req.taskAuditor] != 0) revert TaskAuditorAlreadyRegistered(); - // Revalidate: slasher status or ownership may have changed since the request was submitted if (!dinValidatorStake.isSlasherContract(req.taskCoordinator)) revert CoordinatorNoLongerSlasher(); if (!dinValidatorStake.isSlasherContract(req.taskAuditor)) @@ -260,7 +210,6 @@ contract DINModelRegistry { }) ); - // Store modelId + 1 to distinguish from the zero-value default _modelIdByTaskCoordinator[req.taskCoordinator] = modelId + 1; _modelIdByTaskAuditor[req.taskAuditor] = modelId + 1; @@ -270,8 +219,7 @@ contract DINModelRegistry { emit ModelApproved(requestId, modelId); } - /// @notice DAO rejects a pending model registration (fee is retained) - function rejectModel(uint256 requestId) external onlyDAOAdmin { + function rejectModel(uint256 requestId) external onlyOwner { if (requestId >= modelRequests.length) revert InvalidRequestId(); ModelRequest storage req = modelRequests[requestId]; @@ -283,14 +231,6 @@ contract DINModelRegistry { emit ModelRejected(requestId); } - /*////////////////////////////////////////////////////////////// - MANIFEST UPDATE REQUEST - //////////////////////////////////////////////////////////////*/ - - /// @notice Submit a manifest update request for DAO review (model owner only) - /// @param modelId ID of the model to update - /// @param newManifestCID New IPFS CID (bytes32) for the model manifest - /// @return requestId ID of the pending manifest update request function requestManifestUpdate( uint256 modelId, bytes32 newManifestCID @@ -325,18 +265,12 @@ contract DINModelRegistry { emit ManifestUpdateRequested(requestId, modelId); } - /*////////////////////////////////////////////////////////////// - APPROVE / REJECT MANIFEST UPDATE - //////////////////////////////////////////////////////////////*/ - - /// @notice DAO approves a pending manifest update - function approveManifestUpdate(uint256 requestId) external onlyDAOAdmin { + function approveManifestUpdate(uint256 requestId) external onlyOwner { if (requestId >= manifestRequests.length) revert InvalidRequestId(); ManifestUpdateRequest storage req = manifestRequests[requestId]; if (req.processed) revert AlreadyProcessed(); - // Prevent approving updates for a model that has since been disabled if (modelDisabled[req.modelId]) revert ModelIsDisabled(req.modelId); models[req.modelId].manifestCID = req.newManifestCID; @@ -347,8 +281,7 @@ contract DINModelRegistry { emit ManifestUpdated(requestId, req.modelId, req.newManifestCID); } - /// @notice DAO rejects a pending manifest update (fee is retained) - function rejectManifestUpdate(uint256 requestId) external onlyDAOAdmin { + function rejectManifestUpdate(uint256 requestId) external onlyOwner { if (requestId >= manifestRequests.length) revert InvalidRequestId(); ManifestUpdateRequest storage req = manifestRequests[requestId]; @@ -360,10 +293,6 @@ contract DINModelRegistry { emit ManifestUpdateRejected(requestId); } - /*////////////////////////////////////////////////////////////// - VIEW FUNCTIONS - //////////////////////////////////////////////////////////////*/ - function getModel( uint256 modelId ) @@ -402,81 +331,60 @@ contract DINModelRegistry { return manifestRequests.length; } - /// @notice Look up the model ID registered for a given task coordinator function getModelIdByTaskCoordinator( address taskCoordinator ) external view returns (bool exists, uint256 modelId) { uint256 val = _modelIdByTaskCoordinator[taskCoordinator]; if (val == 0) return (false, 0); - return (true, val - 1); // subtract 1 to convert back to 0-indexed modelId + return (true, val - 1); } - /// @notice Look up the model ID registered for a given task auditor function getModelIdByTaskAuditor( address taskAuditor ) external view returns (bool exists, uint256 modelId) { uint256 val = _modelIdByTaskAuditor[taskAuditor]; if (val == 0) return (false, 0); - return (true, val - 1); // subtract 1 to convert back to 0-indexed modelId + return (true, val - 1); } - /*////////////////////////////////////////////////////////////// - DAO ADMIN FUNCTIONS - //////////////////////////////////////////////////////////////*/ - - // ── Kill switch ──────────────────────────────────────────────────────── - - /// @notice Immediately disable a model — blocks manifest updates and - /// any downstream contract that checks modelDisabled(modelId) - function disableModel(uint256 modelId) external onlyDAOAdmin { + function disableModel(uint256 modelId) external onlyOwner { if (modelId >= models.length) revert InvalidModelId(); modelDisabled[modelId] = true; emit ModelDisabled(modelId); } - /// @notice Re-enable a previously disabled model - function enableModel(uint256 modelId) external onlyDAOAdmin { + function enableModel(uint256 modelId) external onlyOwner { if (modelId >= models.length) revert InvalidModelId(); modelDisabled[modelId] = false; emit ModelEnabled(modelId); } - // ── Individual fee setters ───────────────────────────────────────────── - - /// @notice Update the open-source model registration fee - function setOpenSourceFee(uint256 newFee) external onlyDAOAdmin { + function setOpenSourceFee(uint256 newFee) external onlyOwner { openSourceFee = newFee; emit OpenSourceFeeUpdated(newFee); } - /// @notice Update the proprietary model registration fee - function setProprietaryFee(uint256 newFee) external onlyDAOAdmin { + function setProprietaryFee(uint256 newFee) external onlyOwner { proprietaryFee = newFee; emit ProprietaryFeeUpdated(newFee); } - /// @notice Update the open-source manifest update fee - function setOpenSourceUpdateFee(uint256 newFee) external onlyDAOAdmin { + function setOpenSourceUpdateFee(uint256 newFee) external onlyOwner { openSourceUpdateFee = newFee; emit OpenSourceUpdateFeeUpdated(newFee); } - /// @notice Update the proprietary manifest update fee - function setProprietaryUpdateFee(uint256 newFee) external onlyDAOAdmin { + function setProprietaryUpdateFee(uint256 newFee) external onlyOwner { proprietaryUpdateFee = newFee; emit ProprietaryUpdateFeeUpdated(newFee); } - // ── Combined setter (atomic governance) ──────────────────────────────── - - /// @notice Atomically update all four protocol fees in a single transaction - /// @dev Prefer this for governance proposals to avoid inconsistent fee states function setFees( uint256 _openSourceFee, uint256 _proprietaryFee, uint256 _openSourceUpdateFee, uint256 _proprietaryUpdateFee - ) external onlyDAOAdmin { + ) external onlyOwner { openSourceFee = _openSourceFee; proprietaryFee = _proprietaryFee; openSourceUpdateFee = _openSourceUpdateFee; @@ -490,21 +398,9 @@ contract DINModelRegistry { ); } - /// @notice Withdraw accumulated fees to a designated address - function withdrawFees(address payable to) external onlyDAOAdmin { + function withdrawFees(address payable to) external onlyOwner { uint256 balance = address(this).balance; to.transfer(balance); emit FeesWithdrawn(to, balance); } - - // ── DAO admin transfer ───────────────────────────────────────────────── - - /// @notice Transfer DAO admin role (multisig / timelock migration path) - /// @dev Emits DAOAdminUpdated so indexers can track governance handover - function setDAOAdmin(address newAdmin) external onlyDAOAdmin { - if (newAdmin == address(0)) revert ZeroAddress(); - address old = daoAdmin; - daoAdmin = newAdmin; - emit DAOAdminUpdated(old, newAdmin); - } } From 66765a304961d7113e0d1defba1c13ba93a4bf8a Mon Sep 17 00:00:00 2001 From: robertocarlous Date: Wed, 24 Jun 2026 23:27:25 +0100 Subject: [PATCH 6/9] feat(hardhat): add platform proxy deploy and upgrade scripts Add deploy-platform and upgrade-platform scripts with npm shortcuts, wire the four contracts in dependency order, and annotate constructors for hardhat-upgrades validation. --- .gitignore | 2 - hardhat/contracts/DINModelRegistry.sol | 1 + hardhat/contracts/DinCoordinator.sol | 1 + hardhat/contracts/DinToken.sol | 1 + hardhat/contracts/DinValidatorStake.sol | 1 + hardhat/hardhat.config.ts | 11 ++--- hardhat/package.json | 4 +- hardhat/scripts/deploy-platform.ts | 66 +++++++++++++++++++++++++ hardhat/scripts/upgrade-platform.ts | 63 +++++++++++++++++++++++ 9 files changed, 139 insertions(+), 11 deletions(-) create mode 100644 hardhat/scripts/deploy-platform.ts create mode 100644 hardhat/scripts/upgrade-platform.ts diff --git a/.gitignore b/.gitignore index b17789f..f3e4392 100644 --- a/.gitignore +++ b/.gitignore @@ -21,8 +21,6 @@ tasks/local/0x4657105FC932625CD289107aAE7B2174a822b709/services/__pycache__/* /tasks/* /scripts/* /hardhat/ignition/* -/hardhat/scripts/* -/hardhat/test/* /hardhat/test-output.txt /dincli/config/accounts.json /dincli/config/din_info.json diff --git a/hardhat/contracts/DINModelRegistry.sol b/hardhat/contracts/DINModelRegistry.sol index a879913..5940c26 100644 --- a/hardhat/contracts/DINModelRegistry.sol +++ b/hardhat/contracts/DINModelRegistry.sol @@ -107,6 +107,7 @@ contract DINModelRegistry is Initializable, OwnableUpgradeable { mapping(address => uint256) private _modelIdByTaskAuditor; mapping(uint256 => bool) public modelDisabled; + /// @custom:oz-upgrades-unsafe-allow constructor constructor() { _disableInitializers(); } diff --git a/hardhat/contracts/DinCoordinator.sol b/hardhat/contracts/DinCoordinator.sol index 61fc275..c20fdc0 100644 --- a/hardhat/contracts/DinCoordinator.sol +++ b/hardhat/contracts/DinCoordinator.sol @@ -37,6 +37,7 @@ contract DinCoordinator is error ZeroValue(); error TransferFailed(); + /// @custom:oz-upgrades-unsafe-allow constructor constructor() { _disableInitializers(); } diff --git a/hardhat/contracts/DinToken.sol b/hardhat/contracts/DinToken.sol index 9cf115a..c1b4204 100644 --- a/hardhat/contracts/DinToken.sol +++ b/hardhat/contracts/DinToken.sol @@ -15,6 +15,7 @@ contract DinToken is Initializable, ERC20Upgradeable, OwnableUpgradeable { event TokensMinted(address indexed to, uint256 amount); event CoordinatorSet(address indexed coordinator); + /// @custom:oz-upgrades-unsafe-allow constructor constructor() { _disableInitializers(); } diff --git a/hardhat/contracts/DinValidatorStake.sol b/hardhat/contracts/DinValidatorStake.sol index 021da6a..8d6632e 100644 --- a/hardhat/contracts/DinValidatorStake.sol +++ b/hardhat/contracts/DinValidatorStake.sol @@ -72,6 +72,7 @@ contract DinValidatorStake is mapping(address => ValidatorInfo) public validators; + /// @custom:oz-upgrades-unsafe-allow constructor constructor() { _disableInitializers(); } diff --git a/hardhat/hardhat.config.ts b/hardhat/hardhat.config.ts index 3108253..fb7628c 100644 --- a/hardhat/hardhat.config.ts +++ b/hardhat/hardhat.config.ts @@ -8,10 +8,8 @@ import * as path from "path"; import { task } from "hardhat/config"; import { HardhatRuntimeEnvironment } from "hardhat/types"; -// Load base .env dotenv.config({ path: "../.env" }); -// Load network-specific .env const network = process.env.NETWORK || "local"; const sepolia_op_devnet_rpc_url = process.env.SEPOLIA_OP_DEVNET_RPC_URL; const envFile = `../.env.${network}`; @@ -33,24 +31,21 @@ const config: HardhatUserConfig = { runs: 200 }, viaIR: true, - // ✅ CRITICAL: Set EVM version to Cancun for TSTORE/TLOAD support evmVersion: "cancun" } }, networks: { hardhat: { - hardfork: "cancun", // ✅ Required for local testing + hardfork: "cancun", ...(process.env.ALCHEMY_API_KEY ? { forking: { url: `https://eth-mainnet.g.alchemy.com/v2/${process.env.ALCHEMY_API_KEY}`, - // Replace with your Alchemy API key - //blockNumber: 18700000, // Optional: specific block to fork from (or remove to fork latest) }, } : {}), accounts: { - count: 70, // Generate 70 accounts + count: 70, accountsBalance: "10000000000000000000000" }, chainId: 1337, @@ -94,7 +89,7 @@ const config: HardhatUserConfig = { enabled: false }, mocha: { - timeout: 100000000 // Set a very high timeout for tests + timeout: 100000000 } }; diff --git a/hardhat/package.json b/hardhat/package.json index a421ddc..f8d9bc0 100644 --- a/hardhat/package.json +++ b/hardhat/package.json @@ -4,7 +4,9 @@ "main": "index.js", "scripts": { "compile": "hardhat compile", - "test": "hardhat test" + "test": "hardhat test", + "deploy:platform": "hardhat run scripts/deploy-platform.ts", + "upgrade:platform": "hardhat run scripts/upgrade-platform.ts" }, "keywords": [], "author": "", diff --git a/hardhat/scripts/deploy-platform.ts b/hardhat/scripts/deploy-platform.ts new file mode 100644 index 0000000..bca1915 --- /dev/null +++ b/hardhat/scripts/deploy-platform.ts @@ -0,0 +1,66 @@ +import hre, { ethers } from "hardhat"; + +import { + deployTransparentProxy, + getProxyAdminAddress, + savePlatformAddresses, +} from "../deploy/helpers"; + +async function main() { + const [deployer] = await ethers.getSigners(); + + console.log("Network:", hre.network.name); + console.log("Deployer:", deployer.address); + + const DinToken = await ethers.getContractFactory("DinToken"); + const dinToken = await deployTransparentProxy(DinToken, []); + const dinTokenAddress = await dinToken.getAddress(); + console.log("DinToken:", dinTokenAddress); + + const DinCoordinator = await ethers.getContractFactory("DinCoordinator"); + const dinCoordinator = await deployTransparentProxy(DinCoordinator, [ + dinTokenAddress, + ]); + const dinCoordinatorAddress = await dinCoordinator.getAddress(); + console.log("DinCoordinator:", dinCoordinatorAddress); + + await (await dinToken.setCoordinator(dinCoordinatorAddress)).wait(); + console.log("DinToken coordinator wired"); + + const DinValidatorStake = await ethers.getContractFactory("DinValidatorStake"); + const dinValidatorStake = await deployTransparentProxy(DinValidatorStake, [ + dinTokenAddress, + dinCoordinatorAddress, + ]); + const dinValidatorStakeAddress = await dinValidatorStake.getAddress(); + console.log("DinValidatorStake:", dinValidatorStakeAddress); + + await ( + await dinCoordinator.updateValidatorStakeContract(dinValidatorStakeAddress) + ).wait(); + console.log("DinCoordinator stake contract wired"); + + const DINModelRegistry = await ethers.getContractFactory("DINModelRegistry"); + const dinModelRegistry = await deployTransparentProxy(DINModelRegistry, [ + dinValidatorStakeAddress, + ]); + const dinModelRegistryAddress = await dinModelRegistry.getAddress(); + console.log("DINModelRegistry:", dinModelRegistryAddress); + + const proxyAdmin = await getProxyAdminAddress(dinTokenAddress); + + savePlatformAddresses(hre.network.name, { + dinToken: dinTokenAddress, + dinCoordinator: dinCoordinatorAddress, + dinValidatorStake: dinValidatorStakeAddress, + dinModelRegistry: dinModelRegistryAddress, + proxyAdmin, + }); + + console.log(`Saved deployments/${hre.network.name}.json`); +} + +main().catch((error) => { + console.error(error); + process.exitCode = 1; +}); diff --git a/hardhat/scripts/upgrade-platform.ts b/hardhat/scripts/upgrade-platform.ts new file mode 100644 index 0000000..afa6057 --- /dev/null +++ b/hardhat/scripts/upgrade-platform.ts @@ -0,0 +1,63 @@ +import hre, { ethers, upgrades } from "hardhat"; + +import { PLATFORM_CONTRACTS } from "../deploy/constants"; +import { + loadPlatformAddresses, + upgradeTransparentProxy, +} from "../deploy/helpers"; +import type { PlatformAddresses } from "../deploy/types"; + +const PROXY_KEYS: Record< + (typeof PLATFORM_CONTRACTS)[number], + keyof PlatformAddresses +> = { + DinToken: "dinToken", + DinCoordinator: "dinCoordinator", + DinValidatorStake: "dinValidatorStake", + DINModelRegistry: "dinModelRegistry", +}; + +async function main() { + const contractName = process.env.CONTRACT; + if (!contractName) { + throw new Error( + `Set CONTRACT to one of: ${PLATFORM_CONTRACTS.join(", ")}` + ); + } + + if ( + !PLATFORM_CONTRACTS.includes( + contractName as (typeof PLATFORM_CONTRACTS)[number] + ) + ) { + throw new Error( + `Unknown CONTRACT "${contractName}". Expected one of: ${PLATFORM_CONTRACTS.join(", ")}` + ); + } + + const addresses = loadPlatformAddresses(hre.network.name); + const proxyKey = + PROXY_KEYS[contractName as (typeof PLATFORM_CONTRACTS)[number]]; + const proxyAddress = addresses[proxyKey]; + if (!proxyAddress) { + throw new Error(`No proxy address for ${contractName} in deployment file`); + } + + console.log("Network:", hre.network.name); + console.log("Upgrading:", contractName); + console.log("Proxy:", proxyAddress); + + const factory = await ethers.getContractFactory(contractName); + await upgradeTransparentProxy(proxyAddress, factory); + + const implementationAddress = + await upgrades.erc1967.getImplementationAddress(proxyAddress); + + console.log("Proxy:", proxyAddress); + console.log("Implementation:", implementationAddress); +} + +main().catch((error) => { + console.error(error); + process.exitCode = 1; +}); From 710e4077074dd568202b0cf4db7b28496aa597b2 Mon Sep 17 00:00:00 2001 From: robertocarlous Date: Thu, 25 Jun 2026 15:19:20 +0100 Subject: [PATCH 7/9] test(hardhat): add upgrade-safety tests for platform contracts Cover state persistence, access control, storage layout validation, and cross-contract wiring across transparent proxy upgrades for all four platform contracts. --- hardhat/contracts/mocks/MockSlasher.sol | 8 ++ .../contracts/upgrade/DINModelRegistryV2.sol | 12 ++ .../contracts/upgrade/DinCoordinatorV2.sol | 12 ++ hardhat/contracts/upgrade/DinTokenV2.sol | 12 ++ .../contracts/upgrade/DinValidatorStakeV2.sol | 12 ++ hardhat/test/DINModelRegistry.upgrade.test.ts | 116 ++++++++++++++++++ hardhat/test/DinCoordinator.upgrade.test.ts | 70 +++++++++++ hardhat/test/DinToken.upgrade.test.ts | 54 ++++++++ .../test/DinValidatorStake.upgrade.test.ts | 86 +++++++++++++ hardhat/test/helpers/platform.ts | 64 ++++++++++ 10 files changed, 446 insertions(+) create mode 100644 hardhat/contracts/mocks/MockSlasher.sol create mode 100644 hardhat/contracts/upgrade/DINModelRegistryV2.sol create mode 100644 hardhat/contracts/upgrade/DinCoordinatorV2.sol create mode 100644 hardhat/contracts/upgrade/DinTokenV2.sol create mode 100644 hardhat/contracts/upgrade/DinValidatorStakeV2.sol create mode 100644 hardhat/test/DINModelRegistry.upgrade.test.ts create mode 100644 hardhat/test/DinCoordinator.upgrade.test.ts create mode 100644 hardhat/test/DinToken.upgrade.test.ts create mode 100644 hardhat/test/DinValidatorStake.upgrade.test.ts create mode 100644 hardhat/test/helpers/platform.ts diff --git a/hardhat/contracts/mocks/MockSlasher.sol b/hardhat/contracts/mocks/MockSlasher.sol new file mode 100644 index 0000000..536ff25 --- /dev/null +++ b/hardhat/contracts/mocks/MockSlasher.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.28; + +import "@openzeppelin/contracts/access/Ownable.sol"; + +contract MockSlasher is Ownable { + constructor(address initialOwner) Ownable(initialOwner) {} +} diff --git a/hardhat/contracts/upgrade/DINModelRegistryV2.sol b/hardhat/contracts/upgrade/DINModelRegistryV2.sol new file mode 100644 index 0000000..360026a --- /dev/null +++ b/hardhat/contracts/upgrade/DINModelRegistryV2.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.28; + +import "../DINModelRegistry.sol"; + +/// @custom:oz-upgrades-from DINModelRegistry +/// @custom:oz-upgrades-unsafe-allow missing-initializer +contract DINModelRegistryV2 is DINModelRegistry { + function version() external pure returns (uint256) { + return 2; + } +} diff --git a/hardhat/contracts/upgrade/DinCoordinatorV2.sol b/hardhat/contracts/upgrade/DinCoordinatorV2.sol new file mode 100644 index 0000000..922ca2f --- /dev/null +++ b/hardhat/contracts/upgrade/DinCoordinatorV2.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.28; + +import "../DinCoordinator.sol"; + +/// @custom:oz-upgrades-from DinCoordinator +/// @custom:oz-upgrades-unsafe-allow missing-initializer +contract DinCoordinatorV2 is DinCoordinator { + function version() external pure returns (uint256) { + return 2; + } +} diff --git a/hardhat/contracts/upgrade/DinTokenV2.sol b/hardhat/contracts/upgrade/DinTokenV2.sol new file mode 100644 index 0000000..06392fc --- /dev/null +++ b/hardhat/contracts/upgrade/DinTokenV2.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.28; + +import "../DinToken.sol"; + +/// @custom:oz-upgrades-from DinToken +/// @custom:oz-upgrades-unsafe-allow missing-initializer +contract DinTokenV2 is DinToken { + function version() external pure returns (uint256) { + return 2; + } +} diff --git a/hardhat/contracts/upgrade/DinValidatorStakeV2.sol b/hardhat/contracts/upgrade/DinValidatorStakeV2.sol new file mode 100644 index 0000000..8618b5f --- /dev/null +++ b/hardhat/contracts/upgrade/DinValidatorStakeV2.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.28; + +import "../DinValidatorStake.sol"; + +/// @custom:oz-upgrades-from DinValidatorStake +/// @custom:oz-upgrades-unsafe-allow missing-initializer +contract DinValidatorStakeV2 is DinValidatorStake { + function version() external pure returns (uint256) { + return 2; + } +} diff --git a/hardhat/test/DINModelRegistry.upgrade.test.ts b/hardhat/test/DINModelRegistry.upgrade.test.ts new file mode 100644 index 0000000..105239d --- /dev/null +++ b/hardhat/test/DINModelRegistry.upgrade.test.ts @@ -0,0 +1,116 @@ +import { expect } from "chai"; +import { ethers, upgrades } from "hardhat"; + +import { PROXY_KIND } from "../deploy/constants"; +import { upgradeTransparentProxy } from "../deploy/helpers"; +import { deployPlatform } from "./helpers/platform"; + +describe("DINModelRegistry upgrade", function () { + it("preserves fee settings and model state across upgrade", async function () { + const { deployer, user, dinCoordinator, dinModelRegistry } = + await deployPlatform(); + const proxyAddress = await dinModelRegistry.getAddress(); + + const newProprietaryFee = 42n; + await dinModelRegistry.connect(deployer).setProprietaryFee(newProprietaryFee); + + const MockSlasher = await ethers.getContractFactory("MockSlasher"); + const coordinatorSlasher = await MockSlasher.deploy(user.address); + const auditorSlasher = await MockSlasher.deploy(user.address); + await coordinatorSlasher.waitForDeployment(); + await auditorSlasher.waitForDeployment(); + + const coordinatorSlasherAddress = await coordinatorSlasher.getAddress(); + const auditorSlasherAddress = await auditorSlasher.getAddress(); + + await dinCoordinator.addSlasherContract(coordinatorSlasherAddress); + await dinCoordinator.addSlasherContract(auditorSlasherAddress); + + const manifestCID = ethers.id("manifest-v1"); + const fee = await dinModelRegistry.proprietaryFee(); + await dinModelRegistry.connect(user).requestModelRegistration( + manifestCID, + coordinatorSlasherAddress, + auditorSlasherAddress, + false, + { value: fee } + ); + await dinModelRegistry.connect(deployer).approveModel(0); + + const DINModelRegistryV2 = await ethers.getContractFactory( + "DINModelRegistryV2" + ); + await upgrades.validateUpgrade(proxyAddress, DINModelRegistryV2, { + kind: PROXY_KIND, + }); + const upgraded = await upgradeTransparentProxy( + proxyAddress, + DINModelRegistryV2 + ); + + expect(await upgraded.proprietaryFee()).to.equal(newProprietaryFee); + expect(await upgraded.totalModels()).to.equal(1); + expect(await upgraded.version()).to.equal(2); + + const model = await upgraded.getModel(0); + expect(model.owner).to.equal(user.address); + expect(model.manifestCID).to.equal(manifestCID); + expect(model.taskCoordinator).to.equal(coordinatorSlasherAddress); + expect(model.taskAuditor).to.equal(auditorSlasherAddress); + }); + + it("keeps owner-only governance restricted after upgrade", async function () { + const { other, dinModelRegistry } = await deployPlatform(); + const proxyAddress = await dinModelRegistry.getAddress(); + + const DINModelRegistryV2 = await ethers.getContractFactory( + "DINModelRegistryV2" + ); + await upgradeTransparentProxy(proxyAddress, DINModelRegistryV2); + + await expect( + dinModelRegistry.connect(other).setProprietaryFee(1) + ).to.be.revertedWithCustomError( + dinModelRegistry, + "OwnableUnauthorizedAccount" + ); + }); + + it("keeps pending requests readable after upgrade", async function () { + const { deployer, user, dinCoordinator, dinModelRegistry } = + await deployPlatform(); + const proxyAddress = await dinModelRegistry.getAddress(); + + const MockSlasher = await ethers.getContractFactory("MockSlasher"); + const coordinatorSlasher = await MockSlasher.deploy(user.address); + const auditorSlasher = await MockSlasher.deploy(user.address); + await coordinatorSlasher.waitForDeployment(); + await auditorSlasher.waitForDeployment(); + + await dinCoordinator.addSlasherContract(await coordinatorSlasher.getAddress()); + await dinCoordinator.addSlasherContract(await auditorSlasher.getAddress()); + + const fee = await dinModelRegistry.openSourceFee(); + await dinModelRegistry.connect(user).requestModelRegistration( + ethers.id("manifest-pending"), + await coordinatorSlasher.getAddress(), + await auditorSlasher.getAddress(), + true, + { value: fee } + ); + + const DINModelRegistryV2 = await ethers.getContractFactory( + "DINModelRegistryV2" + ); + await upgradeTransparentProxy(proxyAddress, DINModelRegistryV2); + + expect(await dinModelRegistry.totalModelRequests()).to.equal(1); + + const request = await dinModelRegistry.modelRequests(0); + expect(request.requester).to.equal(user.address); + expect(request.processed).to.equal(false); + + await dinModelRegistry.connect(deployer).approveModel(0); + expect(await dinModelRegistry.totalModels()).to.equal(1); + }); +}); diff --git a/hardhat/test/DinCoordinator.upgrade.test.ts b/hardhat/test/DinCoordinator.upgrade.test.ts new file mode 100644 index 0000000..dff16c8 --- /dev/null +++ b/hardhat/test/DinCoordinator.upgrade.test.ts @@ -0,0 +1,70 @@ +import { expect } from "chai"; +import { ethers, upgrades } from "hardhat"; + +import { PROXY_KIND } from "../deploy/constants"; +import { upgradeTransparentProxy } from "../deploy/helpers"; +import { deployPlatform, mintDinViaDeposit } from "./helpers/platform"; + +describe("DinCoordinator upgrade", function () { + it("preserves exchange rate and token wiring across upgrade", async function () { + const { user, dinToken, dinCoordinator } = await deployPlatform(); + const proxyAddress = await dinCoordinator.getAddress(); + const tokenAddress = await dinToken.getAddress(); + const newRate = 2_000_000n * 10n ** 18n; + + await dinCoordinator.updateDinPerEth(newRate); + await mintDinViaDeposit(dinCoordinator, user, 10_000_000_000_000_000n); + const balanceBefore = await dinToken.balanceOf(user.address); + + const DinCoordinatorV2 = await ethers.getContractFactory("DinCoordinatorV2"); + await upgrades.validateUpgrade(proxyAddress, DinCoordinatorV2, { + kind: PROXY_KIND, + }); + const upgraded = await upgradeTransparentProxy( + proxyAddress, + DinCoordinatorV2 + ); + + expect(await upgraded.dinToken()).to.equal(tokenAddress); + expect(await upgraded.dinPerEth()).to.equal(newRate); + expect(await upgraded.version()).to.equal(2); + expect(await dinToken.balanceOf(user.address)).to.equal(balanceBefore); + }); + + it("keeps owner-only functions restricted after upgrade", async function () { + const { other, dinCoordinator } = await deployPlatform(); + const proxyAddress = await dinCoordinator.getAddress(); + + const DinCoordinatorV2 = await ethers.getContractFactory("DinCoordinatorV2"); + await upgradeTransparentProxy(proxyAddress, DinCoordinatorV2); + + await expect( + dinCoordinator.connect(other).updateDinPerEth(1) + ).to.be.revertedWithCustomError( + dinCoordinator, + "OwnableUnauthorizedAccount" + ); + }); + + it("keeps slasher management wired to stake contract after upgrade", async function () { + const { deployer, dinCoordinator, dinValidatorStake } = + await deployPlatform(); + const proxyAddress = await dinCoordinator.getAddress(); + + const MockSlasher = await ethers.getContractFactory("MockSlasher"); + const slasher = await MockSlasher.deploy(deployer.address); + await slasher.waitForDeployment(); + + await dinCoordinator.addSlasherContract(await slasher.getAddress()); + expect( + await dinValidatorStake.isSlasherContract(await slasher.getAddress()) + ).to.equal(true); + + const DinCoordinatorV2 = await ethers.getContractFactory("DinCoordinatorV2"); + await upgradeTransparentProxy(proxyAddress, DinCoordinatorV2); + + expect( + await dinValidatorStake.isSlasherContract(await slasher.getAddress()) + ).to.equal(true); + }); +}); diff --git a/hardhat/test/DinToken.upgrade.test.ts b/hardhat/test/DinToken.upgrade.test.ts new file mode 100644 index 0000000..6c62d43 --- /dev/null +++ b/hardhat/test/DinToken.upgrade.test.ts @@ -0,0 +1,54 @@ +import { expect } from "chai"; +import { ethers, upgrades } from "hardhat"; + +import { PROXY_KIND } from "../deploy/constants"; +import { upgradeTransparentProxy } from "../deploy/helpers"; +import { deployPlatform, mintDinViaDeposit } from "./helpers/platform"; + +describe("DinToken upgrade", function () { + it("preserves balances and coordinator wiring across upgrade", async function () { + const { deployer, user, dinToken, dinCoordinator } = await deployPlatform(); + const proxyAddress = await dinToken.getAddress(); + const coordinatorAddress = await dinCoordinator.getAddress(); + + await mintDinViaDeposit(dinCoordinator, user, 10_000_000_000_000_000n); + const balanceBefore = await dinToken.balanceOf(user.address); + + const DinTokenV2 = await ethers.getContractFactory("DinTokenV2"); + await upgrades.validateUpgrade(proxyAddress, DinTokenV2, { + kind: PROXY_KIND, + }); + const upgraded = await upgradeTransparentProxy(proxyAddress, DinTokenV2); + + expect(await upgraded.coordinator()).to.equal(coordinatorAddress); + expect(await upgraded.balanceOf(user.address)).to.equal(balanceBefore); + expect(await upgraded.version()).to.equal(2); + + await mintDinViaDeposit(dinCoordinator, user, 1_000_000_000_000_000n); + expect(await upgraded.balanceOf(user.address)).to.be.gt(balanceBefore); + }); + + it("keeps mint restricted to coordinator after upgrade", async function () { + const { user, dinToken } = await deployPlatform(); + const proxyAddress = await dinToken.getAddress(); + + const DinTokenV2 = await ethers.getContractFactory("DinTokenV2"); + await upgradeTransparentProxy(proxyAddress, DinTokenV2); + + await expect( + dinToken.connect(user).mint(user.address, 1) + ).to.be.revertedWithCustomError(dinToken, "Unauthorized"); + }); + + it("keeps coordinator setup restricted to owner after upgrade", async function () { + const { other, dinToken, dinCoordinator } = await deployPlatform(); + const proxyAddress = await dinToken.getAddress(); + + const DinTokenV2 = await ethers.getContractFactory("DinTokenV2"); + await upgradeTransparentProxy(proxyAddress, DinTokenV2); + + await expect( + dinToken.connect(other).setCoordinator(await dinCoordinator.getAddress()) + ).to.be.revertedWithCustomError(dinToken, "OwnableUnauthorizedAccount"); + }); +}); diff --git a/hardhat/test/DinValidatorStake.upgrade.test.ts b/hardhat/test/DinValidatorStake.upgrade.test.ts new file mode 100644 index 0000000..1bd79d3 --- /dev/null +++ b/hardhat/test/DinValidatorStake.upgrade.test.ts @@ -0,0 +1,86 @@ +import { expect } from "chai"; +import { ethers, upgrades } from "hardhat"; + +import { PROXY_KIND } from "../deploy/constants"; +import { upgradeTransparentProxy } from "../deploy/helpers"; +import { deployPlatform, mintDinViaDeposit } from "./helpers/platform"; + +const MIN_STAKE = 10n * 10n ** 18n; +const STAKE_DEPOSIT_ETH = 10_000_000_000_000_000n; + +describe("DinValidatorStake upgrade", function () { + it("preserves stake balances across upgrade", async function () { + const { user, dinToken, dinCoordinator, dinValidatorStake } = + await deployPlatform(); + const proxyAddress = await dinValidatorStake.getAddress(); + + await mintDinViaDeposit(dinCoordinator, user, STAKE_DEPOSIT_ETH); + await dinToken + .connect(user) + .approve(proxyAddress, MIN_STAKE); + await dinValidatorStake.connect(user).stake(MIN_STAKE); + + const stakeBefore = await dinValidatorStake.getStake(user.address); + + const DinValidatorStakeV2 = await ethers.getContractFactory( + "DinValidatorStakeV2" + ); + await upgrades.validateUpgrade(proxyAddress, DinValidatorStakeV2, { + kind: PROXY_KIND, + }); + const upgraded = await upgradeTransparentProxy( + proxyAddress, + DinValidatorStakeV2 + ); + + expect(await upgraded.getStake(user.address)).to.equal(stakeBefore); + expect(await upgraded.isValidatorActive(user.address)).to.equal(true); + expect(await upgraded.version()).to.equal(2); + }); + + it("keeps slasher registration restricted to coordinator after upgrade", async function () { + const { deployer, other, dinValidatorStake } = await deployPlatform(); + const proxyAddress = await dinValidatorStake.getAddress(); + + const MockSlasher = await ethers.getContractFactory("MockSlasher"); + const slasher = await MockSlasher.deploy(deployer.address); + await slasher.waitForDeployment(); + const slasherAddress = await slasher.getAddress(); + + const DinValidatorStakeV2 = await ethers.getContractFactory( + "DinValidatorStakeV2" + ); + await upgradeTransparentProxy(proxyAddress, DinValidatorStakeV2); + + await expect( + dinValidatorStake.connect(other).addSlasherContract(slasherAddress) + ).to.be.revertedWithCustomError(dinValidatorStake, "NotDINCoordinator"); + }); + + it("keeps blacklist control with owner after upgrade", async function () { + const { deployer, other, user, dinToken, dinCoordinator, dinValidatorStake } = + await deployPlatform(); + const proxyAddress = await dinValidatorStake.getAddress(); + + await mintDinViaDeposit(dinCoordinator, user, STAKE_DEPOSIT_ETH); + await dinToken.connect(user).approve(proxyAddress, MIN_STAKE); + await dinValidatorStake.connect(user).stake(MIN_STAKE); + + const DinValidatorStakeV2 = await ethers.getContractFactory( + "DinValidatorStakeV2" + ); + await upgradeTransparentProxy(proxyAddress, DinValidatorStakeV2); + + await dinValidatorStake.connect(deployer).blacklistValidator(user.address); + expect(await dinValidatorStake.isValidatorActive(user.address)).to.equal( + false + ); + + await expect( + dinValidatorStake.connect(other).unblacklistValidator(user.address) + ).to.be.revertedWithCustomError( + dinValidatorStake, + "OwnableUnauthorizedAccount" + ); + }); +}); diff --git a/hardhat/test/helpers/platform.ts b/hardhat/test/helpers/platform.ts new file mode 100644 index 0000000..6bec152 --- /dev/null +++ b/hardhat/test/helpers/platform.ts @@ -0,0 +1,64 @@ +import { Contract } from "ethers"; +import { ethers } from "hardhat"; + +import { deployTransparentProxy } from "../../deploy/helpers"; + +export interface PlatformFixture { + deployer: Awaited>[0]; + user: Awaited>[0]; + other: Awaited>[0]; + dinToken: Contract; + dinCoordinator: Contract; + dinValidatorStake: Contract; + dinModelRegistry: Contract; +} + +export async function deployPlatform(): Promise { + const [deployer, user, other] = await ethers.getSigners(); + + const DinToken = await ethers.getContractFactory("DinToken"); + const dinToken = await deployTransparentProxy(DinToken, []); + const dinTokenAddress = await dinToken.getAddress(); + + const DinCoordinator = await ethers.getContractFactory("DinCoordinator"); + const dinCoordinator = await deployTransparentProxy(DinCoordinator, [ + dinTokenAddress, + ]); + const dinCoordinatorAddress = await dinCoordinator.getAddress(); + + await (await dinToken.setCoordinator(dinCoordinatorAddress)).wait(); + + const DinValidatorStake = await ethers.getContractFactory("DinValidatorStake"); + const dinValidatorStake = await deployTransparentProxy(DinValidatorStake, [ + dinTokenAddress, + dinCoordinatorAddress, + ]); + const dinValidatorStakeAddress = await dinValidatorStake.getAddress(); + + await ( + await dinCoordinator.updateValidatorStakeContract(dinValidatorStakeAddress) + ).wait(); + + const DINModelRegistry = await ethers.getContractFactory("DINModelRegistry"); + const dinModelRegistry = await deployTransparentProxy(DINModelRegistry, [ + dinValidatorStakeAddress, + ]); + + return { + deployer, + user, + other, + dinToken, + dinCoordinator, + dinValidatorStake, + dinModelRegistry, + }; +} + +export async function mintDinViaDeposit( + dinCoordinator: Contract, + user: Awaited>[0], + ethAmount: bigint +) { + await dinCoordinator.connect(user).depositAndMint({ value: ethAmount }); +} From 4c5829968d54848afc3899e25642512279730f0a Mon Sep 17 00:00:00 2001 From: robertocarlous Date: Fri, 26 Jun 2026 14:17:46 +0100 Subject: [PATCH 8/9] fix(contracts): address upgrade review concerns - Replace transfer() with call{value:} in DINModelRegistry.withdrawFees() and add TransferFailed error to match DinCoordinator's pattern - Change DinToken.initialize() visibility from public to external for consistency with the other three platform contracts - Add explanatory comments to all four V2 contracts justifying the @custom:oz-upgrades-unsafe-allow missing-initializer annotation - Track implementation addresses in deployments JSON after each upgrade: add implementations map to PlatformAddresses type and write it in upgrade-platform.ts so the deployment record stays accurate post-upgrade --- hardhat/contracts/DINModelRegistry.sol | 4 +++- hardhat/contracts/DinToken.sol | 2 +- hardhat/contracts/upgrade/DINModelRegistryV2.sol | 3 +++ hardhat/contracts/upgrade/DinCoordinatorV2.sol | 3 +++ hardhat/contracts/upgrade/DinTokenV2.sol | 3 +++ hardhat/contracts/upgrade/DinValidatorStakeV2.sol | 3 +++ hardhat/deploy/types.ts | 6 ++++++ hardhat/scripts/upgrade-platform.ts | 15 ++++++++++----- 8 files changed, 32 insertions(+), 7 deletions(-) diff --git a/hardhat/contracts/DINModelRegistry.sol b/hardhat/contracts/DINModelRegistry.sol index 5940c26..6e88b37 100644 --- a/hardhat/contracts/DINModelRegistry.sol +++ b/hardhat/contracts/DINModelRegistry.sol @@ -31,6 +31,7 @@ contract DINModelRegistry is Initializable, OwnableUpgradeable { error AuditorNoLongerSlasher(); error CoordinatorOwnershipChanged(); error AuditorOwnershipChanged(); + error TransferFailed(); event ModelRegistrationRequested( uint256 indexed requestId, @@ -401,7 +402,8 @@ contract DINModelRegistry is Initializable, OwnableUpgradeable { function withdrawFees(address payable to) external onlyOwner { uint256 balance = address(this).balance; - to.transfer(balance); + (bool success, ) = to.call{value: balance}(""); + if (!success) revert TransferFailed(); emit FeesWithdrawn(to, balance); } } diff --git a/hardhat/contracts/DinToken.sol b/hardhat/contracts/DinToken.sol index c1b4204..f871ff4 100644 --- a/hardhat/contracts/DinToken.sol +++ b/hardhat/contracts/DinToken.sol @@ -20,7 +20,7 @@ contract DinToken is Initializable, ERC20Upgradeable, OwnableUpgradeable { _disableInitializers(); } - function initialize() public initializer { + function initialize() external initializer { __ERC20_init("DIN Token", "DIN"); __Ownable_init(msg.sender); } diff --git a/hardhat/contracts/upgrade/DINModelRegistryV2.sol b/hardhat/contracts/upgrade/DINModelRegistryV2.sol index 360026a..4e0f3ee 100644 --- a/hardhat/contracts/upgrade/DINModelRegistryV2.sol +++ b/hardhat/contracts/upgrade/DINModelRegistryV2.sol @@ -4,6 +4,9 @@ pragma solidity ^0.8.28; import "../DINModelRegistry.sol"; /// @custom:oz-upgrades-from DINModelRegistry +// missing-initializer is intentional: V2 adds no new state, so it reuses +// V1's initializer. A reinitializer would only be needed if V2 introduced +// new storage variables that require one-time setup. /// @custom:oz-upgrades-unsafe-allow missing-initializer contract DINModelRegistryV2 is DINModelRegistry { function version() external pure returns (uint256) { diff --git a/hardhat/contracts/upgrade/DinCoordinatorV2.sol b/hardhat/contracts/upgrade/DinCoordinatorV2.sol index 922ca2f..c5eb9ca 100644 --- a/hardhat/contracts/upgrade/DinCoordinatorV2.sol +++ b/hardhat/contracts/upgrade/DinCoordinatorV2.sol @@ -4,6 +4,9 @@ pragma solidity ^0.8.28; import "../DinCoordinator.sol"; /// @custom:oz-upgrades-from DinCoordinator +// missing-initializer is intentional: V2 adds no new state, so it reuses +// V1's initializer. A reinitializer would only be needed if V2 introduced +// new storage variables that require one-time setup. /// @custom:oz-upgrades-unsafe-allow missing-initializer contract DinCoordinatorV2 is DinCoordinator { function version() external pure returns (uint256) { diff --git a/hardhat/contracts/upgrade/DinTokenV2.sol b/hardhat/contracts/upgrade/DinTokenV2.sol index 06392fc..44aac86 100644 --- a/hardhat/contracts/upgrade/DinTokenV2.sol +++ b/hardhat/contracts/upgrade/DinTokenV2.sol @@ -4,6 +4,9 @@ pragma solidity ^0.8.28; import "../DinToken.sol"; /// @custom:oz-upgrades-from DinToken +// missing-initializer is intentional: V2 adds no new state, so it reuses +// V1's initializer. A reinitializer would only be needed if V2 introduced +// new storage variables that require one-time setup. /// @custom:oz-upgrades-unsafe-allow missing-initializer contract DinTokenV2 is DinToken { function version() external pure returns (uint256) { diff --git a/hardhat/contracts/upgrade/DinValidatorStakeV2.sol b/hardhat/contracts/upgrade/DinValidatorStakeV2.sol index 8618b5f..02bf16f 100644 --- a/hardhat/contracts/upgrade/DinValidatorStakeV2.sol +++ b/hardhat/contracts/upgrade/DinValidatorStakeV2.sol @@ -4,6 +4,9 @@ pragma solidity ^0.8.28; import "../DinValidatorStake.sol"; /// @custom:oz-upgrades-from DinValidatorStake +// missing-initializer is intentional: V2 adds no new state, so it reuses +// V1's initializer. A reinitializer would only be needed if V2 introduced +// new storage variables that require one-time setup. /// @custom:oz-upgrades-unsafe-allow missing-initializer contract DinValidatorStakeV2 is DinValidatorStake { function version() external pure returns (uint256) { diff --git a/hardhat/deploy/types.ts b/hardhat/deploy/types.ts index 7cf229b..f1c0489 100644 --- a/hardhat/deploy/types.ts +++ b/hardhat/deploy/types.ts @@ -4,4 +4,10 @@ export interface PlatformAddresses { dinValidatorStake: string; dinModelRegistry: string; proxyAdmin?: string; + implementations?: { + dinToken?: string; + dinCoordinator?: string; + dinValidatorStake?: string; + dinModelRegistry?: string; + }; } diff --git a/hardhat/scripts/upgrade-platform.ts b/hardhat/scripts/upgrade-platform.ts index afa6057..b4f109a 100644 --- a/hardhat/scripts/upgrade-platform.ts +++ b/hardhat/scripts/upgrade-platform.ts @@ -3,14 +3,13 @@ import hre, { ethers, upgrades } from "hardhat"; import { PLATFORM_CONTRACTS } from "../deploy/constants"; import { loadPlatformAddresses, + savePlatformAddresses, upgradeTransparentProxy, } from "../deploy/helpers"; -import type { PlatformAddresses } from "../deploy/types"; -const PROXY_KEYS: Record< - (typeof PLATFORM_CONTRACTS)[number], - keyof PlatformAddresses -> = { +type ContractProxyKey = "dinToken" | "dinCoordinator" | "dinValidatorStake" | "dinModelRegistry"; + +const PROXY_KEYS: Record<(typeof PLATFORM_CONTRACTS)[number], ContractProxyKey> = { DinToken: "dinToken", DinCoordinator: "dinCoordinator", DinValidatorStake: "dinValidatorStake", @@ -55,6 +54,12 @@ async function main() { console.log("Proxy:", proxyAddress); console.log("Implementation:", implementationAddress); + + addresses.implementations = { + ...addresses.implementations, + [proxyKey]: implementationAddress, + }; + savePlatformAddresses(hre.network.name, addresses); } main().catch((error) => { From b32b2995384d44e4d56215629dd2dfb83efdc6b2 Mon Sep 17 00:00:00 2001 From: robertocarlous Date: Fri, 26 Jun 2026 16:16:05 +0100 Subject: [PATCH 9/9] fix(contracts,tests): address PR review blockers and feedback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add DinValidatorStake.test.ts with 16 behavioural tests (staking, unbonding window, slash active/pending, blacklist, slasher management) using deployPlatform() proxy setup — fixes broken pre-existing test suite - Add _disableInitializers() guard test to all 4 upgrade test files, proving implementation contracts reject direct initialisation - Replace require() string errors in DINModelRegistry.requestModelRegistration with revert CoordinatorNoLongerSlasher / AuditorNoLongerSlasher to match the approval-time checks and drop string encoding cost - Add daoAdmin() / setDAOAdmin() backward-compat shims over OwnableUpgradeable so existing dincli ABI surface is preserved; emits DAOAdminUpdated event - Add uint256[50] __gap to all 4 platform contracts for safe future inheritance-level storage extension - Add comment to DinToken.setCoordinator() documenting the intentional one-shot constraint - Remove /deployments from hardhat/.gitignore so proxy addresses are committed alongside the code --- hardhat/contracts/DINModelRegistry.sol | 28 ++- hardhat/contracts/DinCoordinator.sol | 3 + hardhat/contracts/DinToken.sol | 7 + hardhat/contracts/DinValidatorStake.sol | 3 + hardhat/test/DINModelRegistry.upgrade.test.ts | 10 + hardhat/test/DinCoordinator.upgrade.test.ts | 11 + hardhat/test/DinToken.upgrade.test.ts | 10 + hardhat/test/DinValidatorStake.test.ts | 231 ++++++++++++++++++ .../test/DinValidatorStake.upgrade.test.ts | 10 + 9 files changed, 305 insertions(+), 8 deletions(-) create mode 100644 hardhat/test/DinValidatorStake.test.ts diff --git a/hardhat/contracts/DINModelRegistry.sol b/hardhat/contracts/DINModelRegistry.sol index 6e88b37..1b4dca2 100644 --- a/hardhat/contracts/DINModelRegistry.sol +++ b/hardhat/contracts/DINModelRegistry.sol @@ -62,6 +62,7 @@ contract DINModelRegistry is Initializable, OwnableUpgradeable { uint256 proprietaryUpdateFee ); event FeesWithdrawn(address indexed to, uint256 amount); + event DAOAdminUpdated(address indexed oldAdmin, address indexed newAdmin); struct Model { address owner; @@ -108,6 +109,9 @@ contract DINModelRegistry is Initializable, OwnableUpgradeable { mapping(address => uint256) private _modelIdByTaskAuditor; mapping(uint256 => bool) public modelDisabled; + // Reserved for future state variables at this inheritance level. + uint256[50] private __gap; + /// @custom:oz-upgrades-unsafe-allow constructor constructor() { _disableInitializers(); @@ -143,14 +147,10 @@ contract DINModelRegistry is Initializable, OwnableUpgradeable { uint256 requiredFee = isOpenSource ? openSourceFee : proprietaryFee; if (msg.value < requiredFee) revert InsufficientFee(); - require( - dinValidatorStake.isSlasherContract(taskCoordinator), - "Invalid Coordinator" - ); - require( - dinValidatorStake.isSlasherContract(taskAuditor), - "Invalid Auditor" - ); + if (!dinValidatorStake.isSlasherContract(taskCoordinator)) + revert CoordinatorNoLongerSlasher(); + if (!dinValidatorStake.isSlasherContract(taskAuditor)) + revert AuditorNoLongerSlasher(); if (taskCoordinator == taskAuditor) revert TaskCoordinatorEqualsTaskAuditor(); @@ -406,4 +406,16 @@ contract DINModelRegistry is Initializable, OwnableUpgradeable { if (!success) revert TransferFailed(); emit FeesWithdrawn(to, balance); } + + // Backward-compat shims — dincli calls daoAdmin() / setDAOAdmin(). + // Underlying auth model is OwnableUpgradeable; these are read-through facades. + function daoAdmin() external view returns (address) { + return owner(); + } + + function setDAOAdmin(address newAdmin) external onlyOwner { + address old = owner(); + transferOwnership(newAdmin); + emit DAOAdminUpdated(old, newAdmin); + } } diff --git a/hardhat/contracts/DinCoordinator.sol b/hardhat/contracts/DinCoordinator.sol index c20fdc0..d638a08 100644 --- a/hardhat/contracts/DinCoordinator.sol +++ b/hardhat/contracts/DinCoordinator.sol @@ -22,6 +22,9 @@ contract DinCoordinator is uint256 public dinPerEth; + // Reserved for future state variables at this inheritance level. + uint256[50] private __gap; + event EthDepositAndDINminted( address indexed user, uint256 ethAmount, diff --git a/hardhat/contracts/DinToken.sol b/hardhat/contracts/DinToken.sol index f871ff4..b5ced0a 100644 --- a/hardhat/contracts/DinToken.sol +++ b/hardhat/contracts/DinToken.sol @@ -12,6 +12,9 @@ contract DinToken is Initializable, ERC20Upgradeable, OwnableUpgradeable { address public coordinator; + // Reserved for future state variables at this inheritance level. + uint256[50] private __gap; + event TokensMinted(address indexed to, uint256 amount); event CoordinatorSet(address indexed coordinator); @@ -25,6 +28,10 @@ contract DinToken is Initializable, ERC20Upgradeable, OwnableUpgradeable { __Ownable_init(msg.sender); } + // One-shot: coordinator is set to the proxy address once and never changed. + // DinCoordinator is behind a Transparent Proxy so its address is stable + // across implementation upgrades; a coordinator address change would require + // deploying a new proxy, which is an intentional design constraint. function setCoordinator(address coordinator_) external onlyOwner { if (coordinator != address(0)) revert CoordinatorAlreadySet(); if (coordinator_ == address(0)) revert InvalidAddress(); diff --git a/hardhat/contracts/DinValidatorStake.sol b/hardhat/contracts/DinValidatorStake.sol index 8d6632e..01cd0cb 100644 --- a/hardhat/contracts/DinValidatorStake.sol +++ b/hardhat/contracts/DinValidatorStake.sol @@ -72,6 +72,9 @@ contract DinValidatorStake is mapping(address => ValidatorInfo) public validators; + // Reserved for future state variables at this inheritance level. + uint256[50] private __gap; + /// @custom:oz-upgrades-unsafe-allow constructor constructor() { _disableInitializers(); diff --git a/hardhat/test/DINModelRegistry.upgrade.test.ts b/hardhat/test/DINModelRegistry.upgrade.test.ts index 105239d..2888ef1 100644 --- a/hardhat/test/DINModelRegistry.upgrade.test.ts +++ b/hardhat/test/DINModelRegistry.upgrade.test.ts @@ -113,4 +113,14 @@ describe("DINModelRegistry upgrade", function () { await dinModelRegistry.connect(deployer).approveModel(0); expect(await dinModelRegistry.totalModels()).to.equal(1); }); + + it("implementation contract cannot be initialized directly", async function () { + const DINModelRegistry = await ethers.getContractFactory("DINModelRegistry"); + const impl = await DINModelRegistry.deploy(); + await impl.waitForDeployment(); + const [signer] = await ethers.getSigners(); + await expect( + impl.initialize(signer.address) + ).to.be.revertedWithCustomError(impl, "InvalidInitialization"); + }); }); diff --git a/hardhat/test/DinCoordinator.upgrade.test.ts b/hardhat/test/DinCoordinator.upgrade.test.ts index dff16c8..52d4ea8 100644 --- a/hardhat/test/DinCoordinator.upgrade.test.ts +++ b/hardhat/test/DinCoordinator.upgrade.test.ts @@ -67,4 +67,15 @@ describe("DinCoordinator upgrade", function () { await dinValidatorStake.isSlasherContract(await slasher.getAddress()) ).to.equal(true); }); + + it("implementation contract cannot be initialized directly", async function () { + const DinCoordinator = await ethers.getContractFactory("DinCoordinator"); + const impl = await DinCoordinator.deploy(); + await impl.waitForDeployment(); + const [signer] = await ethers.getSigners(); + await expect(impl.initialize(signer.address)).to.be.revertedWithCustomError( + impl, + "InvalidInitialization" + ); + }); }); diff --git a/hardhat/test/DinToken.upgrade.test.ts b/hardhat/test/DinToken.upgrade.test.ts index 6c62d43..8f2acab 100644 --- a/hardhat/test/DinToken.upgrade.test.ts +++ b/hardhat/test/DinToken.upgrade.test.ts @@ -51,4 +51,14 @@ describe("DinToken upgrade", function () { dinToken.connect(other).setCoordinator(await dinCoordinator.getAddress()) ).to.be.revertedWithCustomError(dinToken, "OwnableUnauthorizedAccount"); }); + + it("implementation contract cannot be initialized directly", async function () { + const DinToken = await ethers.getContractFactory("DinToken"); + const impl = await DinToken.deploy(); + await impl.waitForDeployment(); + await expect(impl.initialize()).to.be.revertedWithCustomError( + impl, + "InvalidInitialization" + ); + }); }); diff --git a/hardhat/test/DinValidatorStake.test.ts b/hardhat/test/DinValidatorStake.test.ts new file mode 100644 index 0000000..d58db20 --- /dev/null +++ b/hardhat/test/DinValidatorStake.test.ts @@ -0,0 +1,231 @@ +import { setBalance } from "@nomicfoundation/hardhat-network-helpers"; +import { time } from "@nomicfoundation/hardhat-network-helpers"; +import { expect } from "chai"; +import { ethers } from "hardhat"; + +import { deployPlatform, mintDinViaDeposit } from "./helpers/platform"; + +const MIN_STAKE = 10n * 10n ** 18n; +const UNBONDING_PERIOD = 7 * 24 * 60 * 60; // 7 days in seconds +const STAKE_DEPOSIT_ETH = 10_000_000_000_000_000n; // 0.01 ETH → ~10,000 DIN at default rate + +async function setup() { + const platform = await deployPlatform(); + const { deployer, user, dinToken, dinCoordinator, dinValidatorStake } = + platform; + const stakeAddr = await dinValidatorStake.getAddress(); + + const MockSlasher = await ethers.getContractFactory("MockSlasher"); + const mockSlasher = await MockSlasher.deploy(deployer.address); + await mockSlasher.waitForDeployment(); + const slasherAddr = await mockSlasher.getAddress(); + await dinCoordinator.addSlasherContract(slasherAddr); + + await mintDinViaDeposit(dinCoordinator, user, STAKE_DEPOSIT_ETH); + await dinToken.connect(user).approve(stakeAddr, ethers.MaxUint256); + + return { ...platform, slasherAddr, stakeAddr }; +} + +describe("DinValidatorStake", function () { + describe("staking", function () { + it("accepts MIN_STAKE and marks validator Active", async function () { + const { user, dinValidatorStake } = await setup(); + await dinValidatorStake.connect(user).stake(MIN_STAKE); + expect(await dinValidatorStake.getStake(user.address)).to.equal(MIN_STAKE); + expect(await dinValidatorStake.isValidatorActive(user.address)).to.equal( + true + ); + }); + + it("reverts when stake is below MIN_STAKE", async function () { + const { user, dinValidatorStake } = await setup(); + await expect( + dinValidatorStake.connect(user).stake(MIN_STAKE - 1n) + ).to.be.revertedWithCustomError( + dinValidatorStake, + "AmountLessThanMinStake" + ); + }); + + it("reverts when validator is blacklisted", async function () { + const { deployer, user, dinValidatorStake } = await setup(); + await dinValidatorStake.connect(deployer).blacklistValidator(user.address); + await expect( + dinValidatorStake.connect(user).stake(MIN_STAKE) + ).to.be.revertedWithCustomError(dinValidatorStake, "ValidatorIsBlacklisted"); + }); + }); + + describe("unstake and unbonding window", function () { + it("moves stake to pending and sets withdrawAvailableAt", async function () { + const { user, dinValidatorStake } = await setup(); + await dinValidatorStake.connect(user).stake(MIN_STAKE); + await dinValidatorStake.connect(user).unstake(MIN_STAKE); + + const info = await dinValidatorStake.validators(user.address); + expect(info.activeStake).to.equal(0n); + expect(info.pendingWithdrawals).to.equal(MIN_STAKE); + expect(info.withdrawAvailableAt).to.be.gt(0n); + }); + + it("reverts claimUnstaked before unbonding period elapses", async function () { + const { user, dinValidatorStake } = await setup(); + await dinValidatorStake.connect(user).stake(MIN_STAKE); + await dinValidatorStake.connect(user).unstake(MIN_STAKE); + await expect( + dinValidatorStake.connect(user).claimUnstaked() + ).to.be.revertedWithCustomError(dinValidatorStake, "WithdrawalNotReady"); + }); + + it("allows claimUnstaked after unbonding period and returns tokens", async function () { + const { user, dinToken, dinValidatorStake } = await setup(); + await dinValidatorStake.connect(user).stake(MIN_STAKE); + const balanceBefore = await dinToken.balanceOf(user.address); + + await dinValidatorStake.connect(user).unstake(MIN_STAKE); + await time.increase(UNBONDING_PERIOD + 1); + await dinValidatorStake.connect(user).claimUnstaked(); + + expect(await dinToken.balanceOf(user.address)).to.equal( + balanceBefore + MIN_STAKE + ); + expect(await dinValidatorStake.getStake(user.address)).to.equal(0n); + }); + + it("reverts second unstake when a pending withdrawal exists", async function () { + const { user, dinToken, dinCoordinator, dinValidatorStake, stakeAddr } = + await setup(); + await mintDinViaDeposit(dinCoordinator, user, STAKE_DEPOSIT_ETH); + await dinToken.connect(user).approve(stakeAddr, ethers.MaxUint256); + + await dinValidatorStake.connect(user).stake(MIN_STAKE * 2n); + await dinValidatorStake.connect(user).unstake(MIN_STAKE); + await expect( + dinValidatorStake.connect(user).unstake(MIN_STAKE) + ).to.be.revertedWithCustomError( + dinValidatorStake, + "PendingWithdrawalExists" + ); + }); + }); + + describe("slashing", function () { + it("slashes active stake via an authorized slasher", async function () { + const { user, dinValidatorStake, slasherAddr } = await setup(); + await dinValidatorStake.connect(user).stake(MIN_STAKE); + + await setBalance(slasherAddr, ethers.parseEther("1")); + const slasherSigner = await ethers.getImpersonatedSigner(slasherAddr); + + const slashAmount = MIN_STAKE / 2n; + await dinValidatorStake + .connect(slasherSigner) + .slash(user.address, slashAmount, ethers.id("test-reason")); + + expect(await dinValidatorStake.getStake(user.address)).to.equal( + MIN_STAKE - slashAmount + ); + }); + + it("slashes pending withdrawals when active stake is exhausted", async function () { + const { user, dinValidatorStake, slasherAddr } = await setup(); + await dinValidatorStake.connect(user).stake(MIN_STAKE); + await dinValidatorStake.connect(user).unstake(MIN_STAKE); + + await setBalance(slasherAddr, ethers.parseEther("1")); + const slasherSigner = await ethers.getImpersonatedSigner(slasherAddr); + + await dinValidatorStake + .connect(slasherSigner) + .slash(user.address, MIN_STAKE, ethers.id("test-reason")); + + const info = await dinValidatorStake.validators(user.address); + expect(info.activeStake).to.equal(0n); + expect(info.pendingWithdrawals).to.equal(0n); + }); + + it("reverts slash from an unauthorized address", async function () { + const { user, other, dinValidatorStake } = await setup(); + await dinValidatorStake.connect(user).stake(MIN_STAKE); + await expect( + dinValidatorStake + .connect(other) + .slash(user.address, MIN_STAKE, ethers.id("test")) + ).to.be.revertedWithCustomError(dinValidatorStake, "NotSlasherContract"); + }); + }); + + describe("blacklisting", function () { + it("owner can blacklist a validator and block further activity", async function () { + const { deployer, user, dinValidatorStake } = await setup(); + await dinValidatorStake.connect(user).stake(MIN_STAKE); + + await dinValidatorStake.connect(deployer).blacklistValidator(user.address); + expect(await dinValidatorStake.isValidatorActive(user.address)).to.equal( + false + ); + await expect( + dinValidatorStake.connect(user).unstake(MIN_STAKE) + ).to.be.revertedWithCustomError(dinValidatorStake, "ValidatorIsBlacklisted"); + }); + + it("owner can unblacklist and restore Active status", async function () { + const { deployer, user, dinValidatorStake } = await setup(); + await dinValidatorStake.connect(user).stake(MIN_STAKE); + await dinValidatorStake.connect(deployer).blacklistValidator(user.address); + + await dinValidatorStake + .connect(deployer) + .unblacklistValidator(user.address); + expect(await dinValidatorStake.isValidatorActive(user.address)).to.equal( + true + ); + }); + + it("non-owner cannot blacklist", async function () { + const { user, other, dinValidatorStake } = await setup(); + await expect( + dinValidatorStake.connect(other).blacklistValidator(user.address) + ).to.be.revertedWithCustomError( + dinValidatorStake, + "OwnableUnauthorizedAccount" + ); + }); + + it("reverts unblacklist on a validator that is not blacklisted", async function () { + const { deployer, user, dinValidatorStake } = await setup(); + await expect( + dinValidatorStake.connect(deployer).unblacklistValidator(user.address) + ).to.be.revertedWithCustomError( + dinValidatorStake, + "ValidatorNotBlacklisted" + ); + }); + }); + + describe("slasher contract management", function () { + it("only DIN_COORDINATOR can register slasher contracts", async function () { + const { other, dinValidatorStake } = await setup(); + await expect( + dinValidatorStake.connect(other).addSlasherContract(other.address) + ).to.be.revertedWithCustomError(dinValidatorStake, "NotDINCoordinator"); + }); + + it("reverts registering the same slasher twice", async function () { + const { deployer, dinCoordinator, dinValidatorStake } = await setup(); + const MockSlasher = await ethers.getContractFactory("MockSlasher"); + const extra = await MockSlasher.deploy(deployer.address); + await extra.waitForDeployment(); + const addr = await extra.getAddress(); + + await dinCoordinator.addSlasherContract(addr); + await expect( + dinCoordinator.addSlasherContract(addr) + ).to.be.revertedWithCustomError( + dinValidatorStake, + "SlasherContractAlreadyAdded" + ); + }); + }); +}); diff --git a/hardhat/test/DinValidatorStake.upgrade.test.ts b/hardhat/test/DinValidatorStake.upgrade.test.ts index 1bd79d3..bb366ca 100644 --- a/hardhat/test/DinValidatorStake.upgrade.test.ts +++ b/hardhat/test/DinValidatorStake.upgrade.test.ts @@ -83,4 +83,14 @@ describe("DinValidatorStake upgrade", function () { "OwnableUnauthorizedAccount" ); }); + + it("implementation contract cannot be initialized directly", async function () { + const DinValidatorStake = await ethers.getContractFactory("DinValidatorStake"); + const impl = await DinValidatorStake.deploy(); + await impl.waitForDeployment(); + const [s1, s2] = await ethers.getSigners(); + await expect( + impl.initialize(s1.address, s2.address) + ).to.be.revertedWithCustomError(impl, "InvalidInitialization"); + }); });