diff --git a/README.md b/README.md index 10a5943..e7a72e4 100644 --- a/README.md +++ b/README.md @@ -23,5 +23,6 @@ Submitter - 領取稿費 ## Reference -- erc7579-imple -- ecdsavalidator \ No newline at end of file + +- https://github.com/consenlabs/ethtaipei2023-aa-workshop +- https://github.com/erc7579/erc7579-implementation diff --git a/src/RoyaltyAutoClaim.sol b/src/RoyaltyAutoClaim.sol index a481178..cd3db8c 100644 --- a/src/RoyaltyAutoClaim.sol +++ b/src/RoyaltyAutoClaim.sol @@ -31,9 +31,9 @@ contract RoyaltyAutoClaim is UUPSUpgradeable, OwnableUpgradeable, IAccount { error AlreadyClaimed(); error RenounceOwnershipDisabled(); error SubmissionNotExist(); - error NotClaimable(); error NotFromEntryPoint(); error ForbiddenPaymaster(); + error ZeroRoyalty(); uint8 public constant ROYALTY_LEVEL_20 = 20; uint8 public constant ROYALTY_LEVEL_40 = 40; @@ -52,7 +52,7 @@ contract RoyaltyAutoClaim is UUPSUpgradeable, OwnableUpgradeable, IAccount { } mapping(string => Submission) public submissions; - mapping(string => bool) public claimed; + mapping(string => bool) public isClaimed; constructor() { _disableInitializers(); @@ -178,10 +178,13 @@ contract RoyaltyAutoClaim is UUPSUpgradeable, OwnableUpgradeable, IAccount { // TODO: add ReentrancyGuard function claimRoyalty(string memory title) public { require(isSubmissionExist(title), SubmissionNotExist()); - require(isSubmissionClaimable(title), NotClaimable()); - uint256 royaltyAmount = getRoyalty(title); - claimed[title] = true; - IERC20(token).safeTransfer(submissions[title].royaltyRecipient, royaltyAmount); + require(!isClaimed[title], AlreadyClaimed()); + require(isSubmissionClaimable(title), NotEnoughReviews()); + uint256 amount = getRoyalty(title); + require(amount > 0, ZeroRoyalty()); + + isClaimed[title] = true; + IERC20(token).safeTransfer(submissions[title].royaltyRecipient, amount); } // ================================ View ================================ @@ -191,20 +194,17 @@ contract RoyaltyAutoClaim is UUPSUpgradeable, OwnableUpgradeable, IAccount { } function isSubmissionClaimable(string memory title) public view returns (bool) { - if (!isSubmissionExist(title) || claimed[title]) { + if (!isSubmissionExist(title) || isClaimed[title]) { return false; } - return submissions[title].reviewCount >= 2 && getRoyalty(title) > 0; + return submissions[title].reviewCount >= 2; } function getRoyalty(string memory title) public view returns (uint256 royalty) { - require(isSubmissionExist(title), SubmissionNotExist()); - Submission memory submission = submissions[title]; - if (submission.reviewCount == 0) { - return 0; - } + if (!isSubmissionClaimable(title)) return 0; + // TODO: add multiplier variable or just use 1e18? - return (submission.totalRoyaltyLevel * 1e18) / submission.reviewCount; + return (uint256(submissions[title].totalRoyaltyLevel) * 1e18) / submissions[title].reviewCount; } /** diff --git a/test/RoyaltyAutoClaim.t.sol b/test/RoyaltyAutoClaim.t.sol index dab21ca..853da21 100644 --- a/test/RoyaltyAutoClaim.t.sol +++ b/test/RoyaltyAutoClaim.t.sol @@ -4,25 +4,35 @@ pragma solidity ^0.8.27; import "../src/RoyaltyAutoClaim.sol"; import "./MockV2.sol"; import "./AATest.t.sol"; +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +contract MockERC20 is ERC20 { + constructor(address owner) ERC20("Test", "TEST") { + _mint(owner, 100 ether); + } +} contract RoyaltyAutoClaimTest is AATest { - RoyaltyAutoClaim royaltyAutoClaim; address owner = vm.addr(1); address admin = vm.addr(2); - address token = vm.addr(3); address[] reviewers = new address[](3); + + RoyaltyAutoClaim royaltyAutoClaim; UUPSProxy proxy; + IERC20 token; function setUp() public override { super.setUp(); - reviewers[0] = vm.addr(4); - reviewers[1] = vm.addr(5); - reviewers[2] = vm.addr(6); + reviewers[0] = vm.addr(3); + reviewers[1] = vm.addr(4); + + token = new MockERC20(owner); royaltyAutoClaim = new RoyaltyAutoClaim(); proxy = new UUPSProxy( - address(royaltyAutoClaim), abi.encodeCall(RoyaltyAutoClaim.initialize, (owner, admin, token, reviewers)) + address(royaltyAutoClaim), + abi.encodeCall(RoyaltyAutoClaim.initialize, (owner, admin, address(token), reviewers)) ); bytes32 v = vm.load(address(proxy), ERC1967Utils.IMPLEMENTATION_SLOT); @@ -30,13 +40,21 @@ contract RoyaltyAutoClaimTest is AATest { // deal vm.deal(address(proxy), 100 ether); + vm.prank(owner); + token.transfer(address(proxy), 100 ether); + + // log + console.log("owner", owner); + console.log("admin", admin); + console.log("reviewer 0", reviewers[0]); + console.log("reviewer 1", reviewers[1]); } function test_upgradeToAndCall() public { address newOwner = vm.randomAddress(); MockV2 v2 = new MockV2(); - vm.prank(vm.addr(123)); + vm.prank(vm.addr(0xbeef)); vm.expectRevert(); MockV2(address(proxy)).upgradeToAndCall(address(v2), abi.encodeCall(MockV2.initialize, (newOwner))); @@ -85,4 +103,40 @@ contract RoyaltyAutoClaimTest is AATest { assertEq(address(uint160(uint256(vm.load(address(proxy), ERC1967Utils.IMPLEMENTATION_SLOT)))), address(v2)); } + + function test_flow() public { + address submitter = vm.randomAddress(); + vm.prank(admin); + RoyaltyAutoClaim(address(proxy)).registerSubmission("test", submitter); + + (address royaltyRecipient, uint8 reviewCount, uint16 totalRoyaltyLevel) = + RoyaltyAutoClaim(address(proxy)).submissions("test"); + assertEq(royaltyRecipient, submitter); + assertEq(reviewCount, 0); + assertEq(totalRoyaltyLevel, 0); + + vm.expectRevert(); + RoyaltyAutoClaim(address(proxy)).claimRoyalty("test"); + + vm.prank(reviewers[0]); + RoyaltyAutoClaim(address(proxy)).reviewSubmission("test", 20); + + vm.expectRevert(); + RoyaltyAutoClaim(address(proxy)).claimRoyalty("test"); + assertEq(RoyaltyAutoClaim(address(proxy)).getRoyalty("test"), 0 ether); + + vm.prank(reviewers[1]); + RoyaltyAutoClaim(address(proxy)).reviewSubmission("test", 40); + + assertEq(RoyaltyAutoClaim(address(proxy)).getRoyalty("test"), 30 ether); + + uint256 proxyBalanceBefore = token.balanceOf(address(proxy)); + + RoyaltyAutoClaim(address(proxy)).claimRoyalty("test"); + + uint256 proxyBalanceAfter = token.balanceOf(address(proxy)); + + assertEq(token.balanceOf(submitter), 30 ether); + assertEq(proxyBalanceAfter, proxyBalanceBefore - 30 ether); + } }