From e2188e116140ae2dd94186a78b3939689bfcd6a1 Mon Sep 17 00:00:00 2001 From: Johnson Chen Date: Wed, 8 Jan 2025 19:02:57 +0800 Subject: [PATCH] feat: UUPS functions --- .env.example | 3 +++ README.md | 13 ++++++++- remappings.txt | 2 ++ script/deploy_v1.s.sol | 28 +++++++++++++++++++ src/Counter.sol | 3 +++ src/RoyaltyAutoClaim.sol | 49 ++++++++++++++++++++++++++++++++- src/RoyaltyAutoClaimV2.sol | 43 +++++++++++++++++++++++++++++ test/Counter.t.sol | 3 +++ test/MockV2.sol | 43 +++++++++++++++++++++++++++++ test/RoyaltyAutoClaim.t.sol | 54 +++++++++++++++++++++++++++++++++++-- 10 files changed, 237 insertions(+), 4 deletions(-) create mode 100644 .env.example create mode 100644 remappings.txt create mode 100644 script/deploy_v1.s.sol create mode 100644 src/RoyaltyAutoClaimV2.sol create mode 100644 test/MockV2.sol diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..5c67d1d --- /dev/null +++ b/.env.example @@ -0,0 +1,3 @@ +PRIVATE_KEY= +ETHERSCAN_API_KEY= +sepolia=https://ethereum-sepolia-rpc.publicnode.com diff --git a/README.md b/README.md index 995bde6..c06a822 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,14 @@ ## RoyaltyAutoClaim -- [去中心化領稿費機制實驗 HackMD](https://hackmd.io/@nic619/SkZDIp2GJl?utm_source=substack&utm_medium=email) \ No newline at end of file +- [去中心化領稿費機制實驗 HackMD](https://hackmd.io/@nic619/SkZDIp2GJl?utm_source=substack&utm_medium=email) + +### Rules + +Owner +- 可以升級合約 +- 可以指定 Admin +- 可以更改稿費幣種 +- 可以轉移所有權 + +Admin +- \ No newline at end of file diff --git a/remappings.txt b/remappings.txt new file mode 100644 index 0000000..5be0b51 --- /dev/null +++ b/remappings.txt @@ -0,0 +1,2 @@ +@openzeppelin/contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/ +@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/ \ No newline at end of file diff --git a/script/deploy_v1.s.sol b/script/deploy_v1.s.sol new file mode 100644 index 0000000..93a1e8d --- /dev/null +++ b/script/deploy_v1.s.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Script, console} from "forge-std/Script.sol"; +import {UUPSProxy, RoyaltyAutoClaim} from "../src/RoyaltyAutoClaim.sol"; + +// forge script script/deploy_v1.s.sol --account dev --rpc-url $sepolia --broadcast --verify + +contract DeployV1Script is Script { + function run() public { + address owner = msg.sender; + address admin = msg.sender; + address token = 0x0000000000000000000000000000000000000000; + address[] memory reviewers = new address[](0); + + vm.startBroadcast(); + + RoyaltyAutoClaim royaltyAutoClaim = new RoyaltyAutoClaim(); + UUPSProxy proxy = new UUPSProxy( + address(royaltyAutoClaim), abi.encodeCall(RoyaltyAutoClaim.initialize, (owner, admin, token, reviewers)) + ); + + console.log("RoyaltyAutoClaim proxy at:", address(proxy)); + console.log("RoyaltyAutoClaim implementation at:", address(royaltyAutoClaim)); + + vm.stopBroadcast(); + } +} diff --git a/src/Counter.sol b/src/Counter.sol index aded799..0b3c65b 100644 --- a/src/Counter.sol +++ b/src/Counter.sol @@ -4,8 +4,11 @@ pragma solidity ^0.8.13; contract Counter { uint256 public number; + event SetNumber(uint256 newNumber); + function setNumber(uint256 newNumber) public { number = newNumber; + emit SetNumber(newNumber); } function increment() public { diff --git a/src/RoyaltyAutoClaim.sol b/src/RoyaltyAutoClaim.sol index d1e1364..fb1dd3a 100644 --- a/src/RoyaltyAutoClaim.sol +++ b/src/RoyaltyAutoClaim.sol @@ -1,4 +1,51 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.27; -contract RoyaltyAutoClaim {} +import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; + +contract UUPSProxy is ERC1967Proxy { + constructor(address _implementation, bytes memory _data) payable ERC1967Proxy(_implementation, _data) { + (bool success,) = _implementation.delegatecall(_data); + require(success, "Initialization failed"); + } +} + +contract RoyaltyAutoClaim is UUPSUpgradeable, OwnableUpgradeable { + string internal constant RENUNCIATION_DISABLED = "Renouncing ownership is disabled"; + + address public admin; + address public token; // 稿費幣種 + address[] public reviewers; + + constructor() { + _disableInitializers(); + } + + function initialize(address _owner, address _admin, address _token, address[] memory _reviewers) + public + initializer + { + __Ownable_init(_owner); + admin = _admin; + token = _token; + reviewers = _reviewers; + } + + // ================================ Ownership ================================ + + function _authorizeUpgrade(address newImplementation) internal override onlyOwner {} + + function changeAdmin(address _admin) public onlyOwner { + admin = _admin; + } + + function changeRoyaltyToken(address _token) public onlyOwner { + token = _token; + } + + function renounceOwnership() public pure override { + revert(RENUNCIATION_DISABLED); + } +} diff --git a/src/RoyaltyAutoClaimV2.sol b/src/RoyaltyAutoClaimV2.sol new file mode 100644 index 0000000..f658ed2 --- /dev/null +++ b/src/RoyaltyAutoClaimV2.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; + +contract RoyaltyAutoClaimV2 is UUPSUpgradeable, OwnableUpgradeable { + string internal constant RENUNCIATION_DISABLED = "Renouncing ownership is disabled"; + + address public admin; + address public token; // 稿費幣種 + address[] public reviewers; + + constructor() { + _disableInitializers(); + } + + function initialize(address _owner, address _admin, address _token, address[] memory _reviewers) + public + reinitializer(2) + { + __Ownable_init(_owner); + admin = _admin; + token = _token; + reviewers = _reviewers; + } + + // ================================ Ownership ================================ + + function _authorizeUpgrade(address newImplementation) internal override onlyOwner {} + + function changeAdmin(address _admin) public onlyOwner { + admin = _admin; + } + + function changeRoyaltyToken(address _token) public onlyOwner { + token = _token; + } + + function renounceOwnership() public pure override { + revert(RENUNCIATION_DISABLED); + } +} diff --git a/test/Counter.t.sol b/test/Counter.t.sol index 54b724f..5023e0e 100644 --- a/test/Counter.t.sol +++ b/test/Counter.t.sol @@ -9,6 +9,9 @@ contract CounterTest is Test { function setUp() public { counter = new Counter(); + + vm.expectEmit(true, false, false, true, address(counter)); + emit Counter.SetNumber(0); counter.setNumber(0); } diff --git a/test/MockV2.sol b/test/MockV2.sol new file mode 100644 index 0000000..62f3b83 --- /dev/null +++ b/test/MockV2.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; + +contract MockV2 is UUPSUpgradeable, OwnableUpgradeable { + string internal constant RENUNCIATION_DISABLED = "Renouncing ownership is disabled"; + + address public admin; + address public token; // 稿費幣種 + address[] public reviewers; + + constructor() { + _disableInitializers(); + } + + function initialize(address _owner, address _admin, address _token, address[] memory _reviewers) + public + reinitializer(2) + { + __Ownable_init(_owner); + admin = _admin; + token = _token; + reviewers = _reviewers; + } + + // ================================ Ownership ================================ + + function _authorizeUpgrade(address newImplementation) internal override onlyOwner {} + + function changeAdmin(address _admin) public onlyOwner { + admin = _admin; + } + + function changeRoyaltyToken(address _token) public onlyOwner { + token = _token; + } + + function renounceOwnership() public pure override { + revert(RENUNCIATION_DISABLED); + } +} diff --git a/test/RoyaltyAutoClaim.t.sol b/test/RoyaltyAutoClaim.t.sol index 1f4b7a9..0180f48 100644 --- a/test/RoyaltyAutoClaim.t.sol +++ b/test/RoyaltyAutoClaim.t.sol @@ -2,9 +2,59 @@ pragma solidity ^0.8.27; import "forge-std/Test.sol"; +import "../src/RoyaltyAutoClaim.sol"; +import "./MockV2.sol"; contract RoyaltyAutoClaimTest is Test { - function setUp() public {} + RoyaltyAutoClaim v1; + address owner = vm.addr(1); + address admin = vm.addr(2); + address token = vm.addr(3); + address[] reviewers = new address[](3); + UUPSProxy proxy; - function test() public {} + function setUp() public { + reviewers[0] = vm.addr(4); + reviewers[1] = vm.addr(5); + reviewers[2] = vm.addr(6); + + v1 = new RoyaltyAutoClaim(); + + proxy = + new UUPSProxy(address(v1), abi.encodeCall(RoyaltyAutoClaim.initialize, (owner, admin, token, reviewers))); + + assertEq(RoyaltyAutoClaim(address(proxy)).owner(), owner); + assertEq(RoyaltyAutoClaim(address(proxy)).admin(), admin); + assertEq(RoyaltyAutoClaim(address(proxy)).token(), token); + assertEq(RoyaltyAutoClaim(address(proxy)).reviewers(0), reviewers[0]); + assertEq(RoyaltyAutoClaim(address(proxy)).reviewers(1), reviewers[1]); + assertEq(RoyaltyAutoClaim(address(proxy)).reviewers(2), reviewers[2]); + bytes32 v = vm.load(address(proxy), ERC1967Utils.IMPLEMENTATION_SLOT); + assertEq(address(uint160(uint256(v))), address(v1)); + } + + function test_upgradeToAndCall() public { + address newOwner = vm.randomAddress(); + address newAdmin = vm.randomAddress(); + address newToken = vm.randomAddress(); + address[] memory newReviewers = new address[](3); + newReviewers[0] = vm.randomAddress(); + newReviewers[1] = vm.randomAddress(); + + MockV2 v2 = new MockV2(); + + vm.prank(owner); + MockV2(address(proxy)).upgradeToAndCall( + address(v2), abi.encodeCall(MockV2.initialize, (newOwner, newAdmin, newToken, newReviewers)) + ); + + assertEq(MockV2(address(proxy)).owner(), newOwner); + assertEq(MockV2(address(proxy)).admin(), newAdmin); + assertEq(MockV2(address(proxy)).token(), newToken); + assertEq(MockV2(address(proxy)).reviewers(0), newReviewers[0]); + assertEq(MockV2(address(proxy)).reviewers(1), newReviewers[1]); + + bytes32 v = vm.load(address(proxy), ERC1967Utils.IMPLEMENTATION_SLOT); + assertEq(address(uint160(uint256(v))), address(v2)); + } }