Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions hardhat/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,5 @@ node_modules
# Hardhat Ignition default folder for deployments against a local node
ignition/deployments/chain-31337

/deployments

199 changes: 55 additions & 144 deletions hardhat/contracts/DINModelRegistry.sol

Large diffs are not rendered by default.

38 changes: 26 additions & 12 deletions hardhat/contracts/DinCoordinator.sol
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -11,11 +12,19 @@ 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;

// Reserved for future state variables at this inheritance level.
uint256[50] private __gap;

event EthDepositAndDINminted(
address indexed user,
uint256 ethAmount,
Expand All @@ -31,24 +40,30 @@ contract DinCoordinator is Ownable, ReentrancyGuardTransient {
error ZeroValue();
error TransferFailed();

constructor() Ownable(msg.sender) {
// Deploy DINToken
dinToken = new DinToken(address(this));
/// @custom:oz-upgrades-unsafe-allow constructor
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);
}

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();
}
Expand Down Expand Up @@ -77,7 +92,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;
Expand Down
56 changes: 33 additions & 23 deletions hardhat/contracts/DinToken.sol
Original file line number Diff line number Diff line change
@@ -1,40 +1,50 @@
// 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;

// Reserved for future state variables at this inheritance level.
uint256[50] private __gap;

// 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;
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}

function initialize() external 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());
// 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();
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);
Expand Down
29 changes: 23 additions & 6 deletions hardhat/contracts/DinValidatorStake.sol
Original file line number Diff line number Diff line change
@@ -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();
Expand All @@ -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;

Expand Down Expand Up @@ -67,10 +72,22 @@ contract DinValidatorStake is Ownable, ReentrancyGuardTransient {

mapping(address => ValidatorInfo) public validators;

constructor(address dinToken, address dinCoordinator) Ownable(msg.sender) {
// Reserved for future state variables at this inheritance level.
uint256[50] private __gap;

/// @custom:oz-upgrades-unsafe-allow constructor
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;
}
Expand Down
8 changes: 8 additions & 0 deletions hardhat/contracts/mocks/MockSlasher.sol
Original file line number Diff line number Diff line change
@@ -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) {}
}
15 changes: 15 additions & 0 deletions hardhat/contracts/upgrade/DINModelRegistryV2.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// SPDX-License-Identifier: MIT
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) {
return 2;
}
}
15 changes: 15 additions & 0 deletions hardhat/contracts/upgrade/DinCoordinatorV2.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// SPDX-License-Identifier: UNLICENSED
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) {
return 2;
}
}
15 changes: 15 additions & 0 deletions hardhat/contracts/upgrade/DinTokenV2.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// SPDX-License-Identifier: MIT
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) {
return 2;
}
}
15 changes: 15 additions & 0 deletions hardhat/contracts/upgrade/DinValidatorStakeV2.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// SPDX-License-Identifier: MIT
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) {
return 2;
}
}
10 changes: 10 additions & 0 deletions hardhat/deploy/constants.ts
Original file line number Diff line number Diff line change
@@ -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];
64 changes: 64 additions & 0 deletions hardhat/deploy/helpers.ts
Original file line number Diff line number Diff line change
@@ -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<T extends ContractFactory>(
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<T extends ContractFactory>(
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<string> {
return upgrades.erc1967.getAdminAddress(proxyAddress);
}
13 changes: 13 additions & 0 deletions hardhat/deploy/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export interface PlatformAddresses {
dinToken: string;
dinCoordinator: string;
dinValidatorStake: string;
dinModelRegistry: string;
proxyAdmin?: string;
implementations?: {
dinToken?: string;
dinCoordinator?: string;
dinValidatorStake?: string;
dinModelRegistry?: string;
};
}
Loading