diff --git a/apps/rewards/app/components/App/App.js b/apps/rewards/app/components/App/App.js index 3b61948c4..d768ab305 100644 --- a/apps/rewards/app/components/App/App.js +++ b/apps/rewards/app/components/App/App.js @@ -199,6 +199,7 @@ class App extends React.Component { claimReward = reward => { this.props.api.claimReward(reward.rewardId + reward.claims) .subscribe(claimHash => { + console.log(claimHash) const { claimHashes } = this.state claimHashes[reward.rewardId] = claimHash this.setState({ claimHashes }) diff --git a/apps/rewards/app/components/Content/MyRewards.js b/apps/rewards/app/components/Content/MyRewards.js index 3eb658559..8685f3147 100644 --- a/apps/rewards/app/components/Content/MyRewards.js +++ b/apps/rewards/app/components/Content/MyRewards.js @@ -163,6 +163,10 @@ const StyledIcon = ({ Icon }) => ( }}/> ) +StyledIcon.propTypes = { + Icon: PropTypes.node.isRequired, +} + const OverviewMain = styled.div` background-color: #f8fcfd; ` diff --git a/apps/rewards/contracts/Rewards.sol b/apps/rewards/contracts/Rewards.sol index eaeeea993..efdd3bfb6 100644 --- a/apps/rewards/contracts/Rewards.sol +++ b/apps/rewards/contracts/Rewards.sol @@ -31,7 +31,11 @@ contract Rewards is AragonApp { string private constant ERROR_MAX_OCCURRENCES = "OCURRENCES_LIMIT_REACHED"; string private constant ERROR_START_BLOCK = "START_PERIOD_BEFORE_TOKEN_CREATION"; string private constant ERROR_REWARD_CLAIMED = "REWARD_ALREADY_CLAIMED"; + string private constant ERROR_REWARD_NOT_CLAIMED = "REWARD_NOT_YET_CLAIMED"; + string private constant ERROR_CLAIM_HASH_EXISTS = "CLAIM_HASH_ALREADY_EXISTS"; + string private constant ERROR_CLAIM_HASH_NONEXISTENT = "CLAIM_HASH_DOES_NOT_EXIST"; string private constant ERROR_ZERO_DURATION = "DURATION_MUST_BE_AT_LEAST_ONE_BLOCK"; + string private constant ERROR_ZERO_OCCURRENCE = "OCCURRENCES_LESS_THAN_ONE"; string private constant ERROR_ZERO_REWARD = "NO_REWARD_TO_CLAIM"; string private constant ERROR_EXISTS = "REWARD_DOES_NOT_EXIST"; @@ -61,9 +65,14 @@ contract Rewards is AragonApp { /// Public vault that holds the funds Vault public vault; + /// Claim hashes by reward and claimant + /// reward -> user -> hash + mapping(uint256 => mapping(address => string)) claimHashes; + /// Events event RewardAdded(uint256 rewardId); /// Emitted when a new reward is created event RewardClaimed(uint256 rewardId); /// Emitted when a reward is claimed + event ClaimHashSet(uint256 rewardID, address claimant, string claimHash); /** * @notice Initialize Rewards app for Vault at `_vault` @@ -101,6 +110,44 @@ contract Rewards is AragonApp { return rewardAmount; } + /** + * @notice Stores proof of a user claiming a reward + * @dev Updates `userclaimHashes` with the hash of a claim transaction for a + * specific reward that has been claimed by the current user + * @param _rewardID The ID of the reward + * @param _claimHash The hash of the reward's claim transaction + */ + function setClaimHash(uint256 _rewardID, string _claimHash) + external isInitialized + { + require(_rewardID < rewardsRegistryLength, ERROR_EXISTS); + Reward storage reward = rewards[_rewardID]; + require(reward.timeClaimed[msg.sender] != 0, ERROR_REWARD_NOT_CLAIMED); + require(claimHashes[_rewardID][msg.sender] == 0, + ERROR_CLAIM_HASH_EXISTS); + claimHashes[_rewardID][msg.sender] = _claimHash; + emit ClaimHashSet(_rewardID, msg.sender, _claimHash); + } + + /** + * @notice Retrieves proof of a user claiming a reward + * @dev Gets a claim hash from `userClaimHashes` given its claimant and + * reward id + * @param _rewardID The ID of the claimed reward + * @param _claimant The address of the user who claimed the reward + * @return claimHash The claim hash for that particular reward and claimant + */ + function getClaimHash(address _claimant, uint256 _rewardID) + external view isInitialized returns (string claimHash) + { + require(_rewardID < rewardsRegistryLength, ERROR_EXISTS); + Reward storage reward = rewards[_rewardID]; + require(reward.timeClaimed[msg.sender] != 0, ERROR_REWARD_NOT_CLAIMED); + require(claimHashes[_rewardID][msg.sender] != 0, + ERROR_CLAIM_HASH_NONEXISTENT); + claimHash = claimHashes[_rewardID][_claimant]; + } + /** * @notice Get total rewards count * @dev Gets the lenght of the rewards registry array diff --git a/apps/rewards/test/Rewards.test.js b/apps/rewards/test/Rewards.test.js index 37db4a681..db02efd3e 100644 --- a/apps/rewards/test/Rewards.test.js +++ b/apps/rewards/test/Rewards.test.js @@ -195,6 +195,10 @@ contract('Rewards', accounts => { assert.notEqual(rewardInformation[10].toNumber(), 0, 'reward should have nonzero timestamp') }) + it('sets the claim hash', async () => { + await app.setClaimHash() + }) + it('receives rewards merit', async () => { await app.claimReward(meritRewardIds[0]) const balance = await rewardToken.balanceOf(root)