Created
February 21, 2022 05:36
-
-
Save korrio/9631b4ffe7ce3e1e387da2eab9b41e61 to your computer and use it in GitHub Desktop.
Escrow.sol
This file contains hidden or 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
//SPDX-License-Identifier: MIT | |
pragma solidity 0.8.7; | |
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; | |
import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; | |
import "@openzeppelin/contracts/security/Pausable.sol"; | |
import "@openzeppelin/contracts/access/Ownable.sol"; | |
import "@openzeppelin/contracts/access/AccessControl.sol"; | |
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; | |
import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; | |
contract Escrow is Ownable, AccessControl, Pausable,ReentrancyGuard { | |
using SafeERC20 for IERC20; | |
using ECDSA for bytes32; | |
struct CaseInfo { | |
address reporter; | |
bytes32[] proofs; | |
uint256 reward; | |
uint256 startAt; | |
uint256 endAt; | |
bool isClose; | |
} | |
struct ProofInfo { | |
uint256 caseId; | |
address owner; | |
uint256 amount; | |
} | |
bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE"); | |
bytes32 public constant WORKER_ROLE = keccak256("WORKER_ROLE"); | |
address public admin; | |
address public wallet; | |
IERC20 public token; | |
mapping(uint256 => CaseInfo) public caseInfos; | |
mapping(bytes32 => ProofInfo) public proofInfos; | |
mapping(address => uint256) public rewards; | |
event CreateCase( | |
uint256 indexed caseId, | |
address indexed reporter, | |
uint256 reward, | |
uint256 startAt, | |
uint256 endAt | |
); | |
event AddProof( | |
uint256 indexed caseId, | |
uint256 indexed proofId, | |
address owner, | |
uint256 amount | |
); | |
event CloseCase(uint256 indexed caseId); | |
event AddReward(uint256[] proofIds, uint256[] amounts); | |
event ClaimReward(address user, uint256 amount); | |
constructor( | |
address _token, | |
address _admin, | |
address _wallet | |
) { | |
token = IERC20(_token); | |
admin = _admin; | |
wallet = _wallet; | |
_setupRole(DEFAULT_ADMIN_ROLE, msg.sender); | |
} | |
function createCase( | |
uint256 _caseId, | |
address _reporter, | |
uint256 _reward, | |
uint256 _startAt, | |
uint256 _endAt | |
) external onlyRole(ADMIN_ROLE) nonReentrant { | |
require(_endAt > _startAt, "Escrow.sol: invalid timestamp."); | |
CaseInfo storage caseInfo = caseInfos[_caseId]; | |
require(_startAt == 0, "Escrow.sol: duplicate caseId."); | |
caseInfo.reporter = _reporter; | |
caseInfo.reward = _reward; | |
caseInfo.startAt = _startAt; | |
caseInfo.endAt = _endAt; | |
emit CreateCase(_caseId, _reporter, _reward, _startAt, _endAt); | |
} | |
function addProof( | |
uint256 _caseId, | |
uint256 _proofId, | |
uint256 _amount, | |
bytes calldata _sign | |
) external whenNotPaused nonReentrant { | |
bytes32 message = keccak256(abi.encode(_caseId, _proofId, _amount)); | |
require( | |
verifySignature(message, _sign), | |
"Escrow.sol: invalid signature." | |
); | |
token.safeTransferFrom(msg.sender, wallet, _amount); | |
bytes32 proofId = generateProofId(_caseId, _proofId); | |
ProofInfo storage proofInfo = proofInfos[proofId]; | |
proofInfo.caseId = _caseId; | |
proofInfo.owner = msg.sender; | |
proofInfo.amount = _amount; | |
emit AddProof(_caseId, _proofId, msg.sender, _amount); | |
} | |
function closeCase(uint256 _caseId) external onlyRole(ADMIN_ROLE) nonReentrant { | |
CaseInfo storage caseInfo = caseInfos[_caseId]; | |
require(!caseInfo.isClose, "Escrow.sol: already close."); | |
require(caseInfo.startAt != 0, "Escrow.sol: no exist case."); | |
require(caseInfo.endAt <= block.timestamp, "Escrow.sol: not ready."); | |
caseInfo.isClose = true; | |
emit CloseCase(_caseId); | |
} | |
function addReward( | |
uint256 _caseId, | |
uint256[] calldata _proofIds, | |
uint256[] calldata _amounts | |
) external onlyRole(WORKER_ROLE) nonReentrant { | |
require(caseInfos[_caseId].isClose, "Escrow.sol: case still open."); | |
require( | |
_proofIds.length == _amounts.length, | |
"Escrow.sol: invalid index" | |
); | |
for (uint256 i; i < _proofIds.length; i++) { | |
bytes32 proofId = generateProofId(_caseId, _proofIds[i]); | |
require( | |
proofInfos[proofId].caseId == _caseId, | |
"Escrow.sol: invalid proofId." | |
); | |
rewards[proofInfos[proofId].owner] += _amounts[i]; | |
} | |
emit AddReward(_proofIds, _amounts); | |
} | |
function claimReward() external nonReentrant { | |
uint256 reward = rewards[msg.sender]; | |
require(reward > 0, "Escrow.sol: no reward."); | |
rewards[msg.sender] = 0; | |
token.safeTransferFrom(wallet, msg.sender, reward); | |
emit ClaimReward(msg.sender, reward); | |
} | |
function setAdminAddress(address _admin) external onlyOwner { | |
admin = _admin; | |
} | |
function setWalletAddress(address _wallet) external onlyOwner { | |
wallet = _wallet; | |
} | |
function setToken(address _token) external onlyOwner { | |
token = IERC20(_token); | |
} | |
function verifySignature(bytes32 _message, bytes calldata _sign) | |
public | |
view | |
returns (bool) | |
{ | |
bytes32 ethSignedHash = _message.toEthSignedMessageHash(); | |
return (ethSignedHash.recover(_sign) == admin); | |
} | |
function generateProofId(uint256 _caseId, uint256 _proofId) | |
public | |
pure | |
returns (bytes32) | |
{ | |
return keccak256(abi.encode(_caseId, _proofId)); | |
} | |
function pause() external onlyOwner { | |
_pause(); | |
} | |
function unpause() external onlyOwner { | |
_unpause(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment