-
Notifications
You must be signed in to change notification settings - Fork 230
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Instant rewards smart contract
- Loading branch information
1 parent
ac82253
commit 7781185
Showing
1 changed file
with
107 additions
and
0 deletions.
There are no files selected for viewing
107 changes: 107 additions & 0 deletions
107
packages/zevm-app-contracts/contracts/instant-rewards/InstantRewards.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity 0.8.7; | ||
|
||
import "@openzeppelin/contracts/security/Pausable.sol"; | ||
import "@openzeppelin/contracts/access/Ownable.sol"; | ||
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; | ||
import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; | ||
|
||
contract InstantRewards is Ownable, Pausable, ReentrancyGuard { | ||
/* An ECDSA signature. */ | ||
struct Signature { | ||
uint8 v; | ||
bytes32 r; | ||
bytes32 s; | ||
} | ||
|
||
struct ClaimData { | ||
address to; | ||
Signature signature; | ||
bytes32 taskId; | ||
uint256 amount; | ||
} | ||
|
||
mapping(address => mapping(bytes32 => bool)) public taskCompletedByUser; | ||
|
||
address public signerAddress; | ||
|
||
event Claimed(address indexed to, bytes32 indexed taskId, uint256 amount); | ||
|
||
error InvalidSigner(); | ||
error InvalidAddress(); | ||
error TaskAlreadyClaimed(); | ||
|
||
constructor(address signerAddress_, address owner) Ownable() { | ||
if (signerAddress_ == address(0)) revert InvalidAddress(); | ||
transferOwnership(owner); | ||
signerAddress = signerAddress_; | ||
} | ||
|
||
// Helper function to convert uint to string | ||
function _uint2str(uint _i) internal pure returns (string memory _uintAsString) { | ||
if (_i == 0) { | ||
return "0"; | ||
} | ||
uint j = _i; | ||
uint len; | ||
while (j != 0) { | ||
len++; | ||
j /= 10; | ||
} | ||
bytes memory bstr = new bytes(len); | ||
uint k = len; | ||
while (_i != 0) { | ||
k = k - 1; | ||
uint8 temp = (uint8(48 + (_i % 10))); | ||
bstr[k] = bytes1(temp); | ||
_i /= 10; | ||
} | ||
return string(bstr); | ||
} | ||
|
||
function _verify(ClaimData memory claimData) private view { | ||
bytes32 payloadHash = _calculateHash(claimData); | ||
|
||
bytes32 messageHash = ECDSA.toEthSignedMessageHash(payloadHash); | ||
|
||
address messageSigner = ECDSA.recover( | ||
messageHash, | ||
claimData.signature.v, | ||
claimData.signature.r, | ||
claimData.signature.s | ||
); | ||
|
||
if (signerAddress != messageSigner) revert InvalidSigner(); | ||
} | ||
|
||
// Function to compute the hash of the data and tasks for a token | ||
function _calculateHash(ClaimData memory claimData) private pure returns (bytes32) { | ||
bytes memory encodedData = abi.encode(claimData.to, claimData.taskId, claimData.amount); | ||
|
||
return keccak256(encodedData); | ||
} | ||
|
||
function claim(ClaimData memory claimData) external whenNotPaused nonReentrant { | ||
claimData.to = msg.sender; | ||
_verify(claimData); | ||
|
||
if (taskCompletedByUser[claimData.to][claimData.taskId]) revert TaskAlreadyClaimed(); | ||
|
||
taskCompletedByUser[claimData.to][claimData.taskId] = true; | ||
|
||
payable(claimData.to).transfer(claimData.amount); | ||
|
||
emit Claimed(claimData.to, claimData.taskId, claimData.amount); | ||
} | ||
|
||
function setSignerAddress(address signerAddress_) external onlyOwner { | ||
if (signerAddress_ == address(0)) revert InvalidAddress(); | ||
signerAddress = signerAddress_; | ||
} | ||
|
||
function withdraw(address wallet) external onlyOwner { | ||
payable(wallet).transfer(address(this).balance); | ||
} | ||
|
||
receive() external payable {} | ||
} |