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
78 changes: 3 additions & 75 deletions contracts/Collateral.sol
Original file line number Diff line number Diff line change
@@ -1,77 +1,5 @@
// SPDX-License-Identifier: UNLINCENSED
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.30;

import "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";

contract Collateral is Initializable, Ownable2StepUpgradeable, UUPSUpgradeable {
mapping(address => uint256) public collateralBalances;
uint256 public slashedCollateral;
uint256 public totalCollateral;

error InsufficientBalance();
error InsufficientTotalCollateral();
error InvalidAddress();
error InvalidAmount();

event CollateralDeposited(address indexed account, uint256 amount);
event CollateralSlashed(address indexed account, uint256 amount);
event CollateralWithdrawn(address indexed account, uint256 amount);

// solhint-disable-next-line no-empty-blocks
function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}

function initialize(address initialOwner) public initializer {
__Ownable_init(initialOwner);
__UUPSUpgradeable_init();
}

function balanceOf(address account) external view returns (uint256) {
require(account != address(0), InvalidAddress());

return collateralBalances[account];
}

function deposit(address account, uint256 amount) external onlyOwner {
require(account != address(0), InvalidAddress());
require(amount > 0, InvalidAmount());

collateralBalances[account] += amount;
totalCollateral += amount;

emit CollateralDeposited(account, amount);
}

function getSlashedCollateral() external view returns (uint256) {
return slashedCollateral;
}

function getTotalCollateral() external view returns (uint256) {
return totalCollateral;
}

function slash(address account, uint256 amount) external onlyOwner {
require(account != address(0), InvalidAddress());
require(amount > 0, InvalidAmount());
require(collateralBalances[account] >= amount, InsufficientBalance());

collateralBalances[account] -= amount;
slashedCollateral += amount;
totalCollateral -= amount;

emit CollateralSlashed(account, amount);
}

function withdraw(address account, uint256 amount) external onlyOwner {
require(account != address(0), InvalidAddress());
require(amount > 0, InvalidAmount());
require(collateralBalances[account] >= amount, InsufficientBalance());
require(totalCollateral >= amount, InsufficientTotalCollateral());

collateralBalances[account] -= amount;
totalCollateral -= amount;

emit CollateralWithdrawn(account, amount);
}
}
import "./Collateral_V2.sol";

Check warning on line 4 in contracts/Collateral.sol

View workflow job for this annotation

GitHub Actions / lint

global import of path ./Collateral_V2.sol is not allowed. Specify names to import individually or bind all exports of the module into a name (import "path" as Name)
contract Collateral is Collateral_V2 {}

Check warning on line 5 in contracts/Collateral.sol

View workflow job for this annotation

GitHub Actions / lint

Code contains empty blocks
77 changes: 77 additions & 0 deletions contracts/Collateral_V1.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// SPDX-License-Identifier: UNLINCENSED
pragma solidity 0.8.30;

import "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol";

Check warning on line 4 in contracts/Collateral_V1.sol

View workflow job for this annotation

GitHub Actions / lint

global import of path @openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol is not allowed. Specify names to import individually or bind all exports of the module into a name (import "path" as Name)
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";

Check warning on line 5 in contracts/Collateral_V1.sol

View workflow job for this annotation

GitHub Actions / lint

global import of path @openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol is not allowed. Specify names to import individually or bind all exports of the module into a name (import "path" as Name)
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";

Check warning on line 6 in contracts/Collateral_V1.sol

View workflow job for this annotation

GitHub Actions / lint

global import of path @openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol is not allowed. Specify names to import individually or bind all exports of the module into a name (import "path" as Name)

contract Collateral_V1 is Initializable, Ownable2StepUpgradeable, UUPSUpgradeable {

Check warning on line 8 in contracts/Collateral_V1.sol

View workflow job for this annotation

GitHub Actions / lint

Contract, Structs and Enums should be in CapWords
mapping(address => uint256) public collateralBalances;
uint256 public slashedCollateral;
uint256 public totalCollateral;

error InsufficientBalance();
error InsufficientTotalCollateral();
error InvalidAddress();
error InvalidAmount();

event CollateralDeposited(address indexed account, uint256 amount);
event CollateralSlashed(address indexed account, uint256 amount);
event CollateralWithdrawn(address indexed account, uint256 amount);

// solhint-disable-next-line no-empty-blocks
function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}

function initialize(address initialOwner) public initializer {
__Ownable_init(initialOwner);
__UUPSUpgradeable_init();
}

function balanceOf(address account) external view returns (uint256) {
require(account != address(0), InvalidAddress());

return collateralBalances[account];
}

function deposit(address account, uint256 amount) external onlyOwner {
require(account != address(0), InvalidAddress());
require(amount > 0, InvalidAmount());

collateralBalances[account] += amount;
totalCollateral += amount;

emit CollateralDeposited(account, amount);
}

function getSlashedCollateral() external view returns (uint256) {
return slashedCollateral;
}

function getTotalCollateral() external view returns (uint256) {
return totalCollateral;
}

function slash(address account, uint256 amount) external onlyOwner {
require(account != address(0), InvalidAddress());
require(amount > 0, InvalidAmount());
require(collateralBalances[account] >= amount, InsufficientBalance());

collateralBalances[account] -= amount;
slashedCollateral += amount;
totalCollateral -= amount;

emit CollateralSlashed(account, amount);
}

function withdraw(address account, uint256 amount) external onlyOwner {
require(account != address(0), InvalidAddress());
require(amount > 0, InvalidAmount());
require(collateralBalances[account] >= amount, InsufficientBalance());
require(totalCollateral >= amount, InsufficientTotalCollateral());

collateralBalances[account] -= amount;
totalCollateral -= amount;

emit CollateralWithdrawn(account, amount);
}
}
148 changes: 148 additions & 0 deletions contracts/Collateral_V2.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.30;

import "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol";

Check warning on line 4 in contracts/Collateral_V2.sol

View workflow job for this annotation

GitHub Actions / lint

global import of path @openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol is not allowed. Specify names to import individually or bind all exports of the module into a name (import "path" as Name)
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";

Check warning on line 5 in contracts/Collateral_V2.sol

View workflow job for this annotation

GitHub Actions / lint

global import of path @openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol is not allowed. Specify names to import individually or bind all exports of the module into a name (import "path" as Name)
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";

Check warning on line 6 in contracts/Collateral_V2.sol

View workflow job for this annotation

GitHub Actions / lint

global import of path @openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol is not allowed. Specify names to import individually or bind all exports of the module into a name (import "path" as Name)

contract Collateral_V2 is Initializable, Ownable2StepUpgradeable, UUPSUpgradeable {

Check warning on line 8 in contracts/Collateral_V2.sol

View workflow job for this annotation

GitHub Actions / lint

Contract, Structs and Enums should be in CapWords
// =============================================================
// ======================= STORAGE LAYOUT ======================
// =============================================================

// --- [V1] Direct collateral storage ---
mapping(address => uint256) public collateralBalances;
uint256 public slashedCollateral;
uint256 public totalCollateral;

// --- [V2] Delegated collateral storage ---
mapping(address => mapping(address => uint256)) public contributorBalance; // miner => contributor => amount
mapping(address => uint256) public minerTotalCollateral;
mapping(address => uint256) public minerSlashedCollateral;

uint256 public totalDelegatedCollateral;
uint256 public totalDelegatedSlashedCollateral;

// --- [Reserved for future upgrades] ---
uint256[44] private __gap;

// ======================= STORAGE LAYOUT END ==================

error InsufficientBalance();
error InsufficientTotalCollateral();
error InvalidAddress();
error InvalidAmount();

event CollateralDeposited(address indexed account, uint256 amount);
event CollateralSlashed(address indexed account, uint256 amount);
event CollateralWithdrawn(address indexed account, uint256 amount);

event ContributorDeposited(address indexed miner, address indexed contributor, uint256 amount);
event ContributorWithdrawn(address indexed miner, address indexed contributor, uint256 amount);
event ContributorSlashed(address indexed miner, address indexed contributor, uint256 amount);

// solhint-disable-next-line no-empty-blocks
function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}

function initialize(address initialOwner) public initializer {
__Ownable_init(initialOwner);
__UUPSUpgradeable_init();
}

function balanceOf(address account) external view returns (uint256) {
if (account == address(0)) revert InvalidAddress();

return collateralBalances[account];
}

function deposit(address account, uint256 amount) external onlyOwner {
if (account == address(0)) revert InvalidAddress();
if (amount == 0) revert InvalidAmount();

collateralBalances[account] += amount;
totalCollateral += amount;

emit CollateralDeposited(account, amount);
}

function getSlashedCollateral() external view returns (uint256) {
return slashedCollateral;
}

function getTotalCollateral() external view returns (uint256) {
return totalCollateral;
}

function totalAllCollateral() external view returns (uint256) {
return totalCollateral + totalDelegatedCollateral;
}
function totalAllSlashed() external view returns (uint256) {
return slashedCollateral + totalDelegatedSlashedCollateral;
}

function slash(address account, uint256 amount) external onlyOwner {
if (account == address(0)) revert InvalidAddress();
if (amount == 0) revert InvalidAmount();
if (collateralBalances[account] < amount) revert InsufficientBalance();

collateralBalances[account] -= amount;
slashedCollateral += amount;
totalCollateral -= amount;

emit CollateralSlashed(account, amount);
}

function withdraw(address account, uint256 amount) external onlyOwner {
if (account == address(0)) revert InvalidAddress();
if (amount == 0) revert InvalidAmount();
if (collateralBalances[account] < amount) revert InsufficientBalance();
if (totalCollateral < amount) revert InsufficientTotalCollateral();

collateralBalances[account] -= amount;
totalCollateral -= amount;

emit CollateralWithdrawn(account, amount);
}

function depositFor(address miner, address contributor, uint256 amount) external onlyOwner {
if (miner == address(0) || contributor == address(0)) revert InvalidAddress();
if (amount == 0) revert InvalidAmount();

contributorBalance[miner][contributor] += amount;
minerTotalCollateral[miner] += amount;
totalDelegatedCollateral += amount;

emit ContributorDeposited(miner, contributor, amount);
}

function withdrawFor(address miner, address contributor, uint256 amount) external onlyOwner {
if (miner == address(0) || contributor == address(0)) revert InvalidAddress();
if (amount == 0) revert InvalidAmount();

uint256 bal = contributorBalance[miner][contributor];
if (bal < amount) revert InsufficientBalance();

contributorBalance[miner][contributor] = bal - amount;
minerTotalCollateral[miner] -= amount;
totalDelegatedCollateral -= amount;

emit ContributorWithdrawn(miner, contributor, amount);
}

function slashFromContributor(address miner, address contributor, uint256 amount) external onlyOwner {
if (miner == address(0) || contributor == address(0)) revert InvalidAddress();
if (amount == 0) revert InvalidAmount();

uint256 bal = contributorBalance[miner][contributor];
if (bal < amount) revert InsufficientBalance();

contributorBalance[miner][contributor] = bal - amount;
minerTotalCollateral[miner] -= amount;
minerSlashedCollateral[miner] += amount;

totalDelegatedCollateral -= amount;
totalDelegatedSlashedCollateral += amount;

emit ContributorSlashed(miner, contributor, amount);
}
}
3 changes: 2 additions & 1 deletion scripts/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ async function main() {
const [owner] = await ethers.getSigners();

const Contract = await ethers.getContractFactory("Collateral");
const proxy = await upgrades.deployProxy(Contract, [owner.address], { initializer: "initialize" });
const proxy = await upgrades.deployProxy(Contract, [owner.address], { initializer: "initialize",
kind: "uups", });
await proxy.waitForDeployment();

console.log("Contract deployed at: ", await proxy.getAddress());
Expand Down
39 changes: 39 additions & 0 deletions scripts/local/simulate-upgrade.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { ethers, upgrades } from "hardhat";

async function main() {
const [deployer] = await ethers.getSigners();

// 1. 기존 V1 배포
const CollateralV1 = await ethers.getContractFactory("Collateral_V1", deployer);
const proxy = await upgrades.deployProxy(CollateralV1, [deployer.address], {
initializer: "initialize",
kind: "uups",
});
await proxy.waitForDeployment();

console.log("✅ Deployed Collateral V1 Proxy:", await proxy.getAddress());

// 2. 상태 세팅
await proxy.deposit(deployer.address, 100n);
console.log("Before upgrade: total =", (await proxy.getTotalCollateral()).toString());

// 3. 새 구현 업그레이드 시뮬레이션 (같은 컨트랙트를 V2로 가정)
const CollateralV2 = await ethers.getContractFactory("Collateral", deployer);
const upgraded = await upgrades.upgradeProxy(await proxy.getAddress(), CollateralV2, { kind: "uups" });
console.log("✅ Proxy upgraded to new implementation");

// 4. 업그레이드 후 상태 유지 확인
const totalAfter = await upgraded.getTotalCollateral();
console.log("After upgrade: total =", totalAfter.toString());

if (totalAfter === 100n) {
console.log("✅ State preserved successfully");
} else {
console.error("❌ State mismatch!");
}
}

main().catch((err) => {
console.error(err);
process.exit(1);
});
24 changes: 24 additions & 0 deletions scripts/local/validate-upgrade.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { ethers, upgrades } from "hardhat";

async function main() {
// 1. 먼저 V1 배포
const [deployer] = await ethers.getSigners();
const CollateralV1 = await ethers.getContractFactory("Collateral_V1", deployer);
const proxy = await upgrades.deployProxy(CollateralV1, [deployer.address], {
initializer: "initialize",
kind: "uups",
});
await proxy.waitForDeployment();

console.log("✅ Deployed Collateral V1 Proxy at:", await proxy.getAddress());

const CollateralV2 = await ethers.getContractFactory("Collateral", deployer);
await upgrades.validateUpgrade(await proxy.getAddress(), CollateralV2, { kind: "uups" });

console.log("✅ Storage layout validation passed (local)");
}

main().catch((err) => {
console.error(err);
process.exit(1);
});
Loading