Skip to content

Commit

Permalink
Merge branch 'reserve-hack-fix' into celo-reserve
Browse files Browse the repository at this point in the history
  • Loading branch information
sirpy committed Nov 28, 2024
2 parents ed2daf1 + 494ba11 commit e2909d6
Show file tree
Hide file tree
Showing 26 changed files with 1,345 additions and 1,327 deletions.
103 changes: 60 additions & 43 deletions contracts/invite/InvitesV2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ contract InvitesV2 is DAOUpgradeableContract {

bool public levelExpirationEnabled;

bytes32 private campaignCode;

event InviteeJoined(address indexed inviter, address indexed invitee);
event InviterBounty(
address indexed inviter,
Expand Down Expand Up @@ -125,8 +127,9 @@ contract InvitesV2 is DAOUpgradeableContract {
address inviter = codeToUser[_inviterCode];
//allow user to set inviter if doesnt have one
require(
user.inviteCode == 0x0 ||
(user.invitedBy == address(0) && inviter != address(0)),
!user.bountyPaid &&
(user.inviteCode == 0x0 ||
(user.invitedBy == address(0) && inviter != address(0))),
"user already joined"
);
if (user.inviteCode == 0x0) {
Expand All @@ -138,10 +141,15 @@ contract InvitesV2 is DAOUpgradeableContract {
if (inviter != address(0)) {
require(inviter != msg.sender, "self invite");
user.invitedBy = inviter;
users[inviter].invitees.push(msg.sender);
users[inviter].pending.push(msg.sender);
stats.totalInvited += 1;
user.bountyAtJoin = levels[users[inviter].level].bounty;
/** support special campaign code without inviter */
if (inviter == address(this)) {
user.bountyAtJoin = levels[0].bounty;
} else {
users[inviter].invitees.push(msg.sender);
users[inviter].pending.push(msg.sender);
user.bountyAtJoin = levels[users[inviter].level].bounty;
}
}

if (canCollectBountyFor(msg.sender)) {
Expand All @@ -165,19 +173,15 @@ contract InvitesV2 is DAOUpgradeableContract {

function canCollectBountyFor(address _invitee) public view returns (bool) {
address invitedBy = users[_invitee].invitedBy;
uint256 daysToComplete = levels[users[invitedBy].level].daysToComplete;
bool isLevelExpired = levelExpirationEnabled == true &&
daysToComplete > 0 &&
daysToComplete <
(users[_invitee].joinedAt - users[invitedBy].levelStarted) / 1 days;

return
invitedBy != address(0) &&
users[_invitee].bountyAtJoin > 0 &&
!users[_invitee].bountyPaid &&
getIdentity().isWhitelisted(_invitee) &&
getIdentity().isWhitelisted(invitedBy) &&
_whitelistedOnChainOrDefault(_invitee) == _chainId() &&
isLevelExpired == false;
(invitedBy == address(0) ||
invitedBy == address(this) ||
getIdentity().isWhitelisted(invitedBy)) &&
_whitelistedOnChainOrDefault(_invitee) == _chainId();
}

function getInvitees(
Expand Down Expand Up @@ -237,44 +241,51 @@ contract InvitesV2 is DAOUpgradeableContract {
bool isSingleBounty
) internal returns (uint256 bounty) {
address invitedBy = users[_invitee].invitedBy;
uint256 joinedAt = users[_invitee].joinedAt;
Level memory level = levels[users[invitedBy].level];
uint256 bountyToPay = users[_invitee].bountyAtJoin;
bool earnedLevel = false;

//hardcoded for users invited before the bountyAtJoin change
if (bountyToPay == 0) {
uint precision = 10 ** goodDollar.decimals();
bountyToPay = joinedAt > 1687878272 ? 1000 * precision : 500 * precision;
}
if (invitedBy != address(this) && invitedBy != address(0)) {
uint256 joinedAt = users[_invitee].joinedAt;
Level memory level = levels[users[invitedBy].level];

//hardcoded for users invited before the bountyAtJoin change
if (bountyToPay == 0) {
uint precision = 10 ** goodDollar.decimals();
bountyToPay = joinedAt > 1687878272
? 1000 * precision
: 500 * precision;
}

// if inviter level is now higher than when invitee joined or the base level has changed
// we give level bounty if it is higher otherwise the original bounty at the time the user registered

bountyToPay = level.bounty > bountyToPay ? level.bounty : bountyToPay;

// if inviter level is now higher than when invitee joined or the base level has changed
// we give level bounty if it is higher otherwise the original bounty at the time the user registered
bool isLevelExpired = level.daysToComplete > 0 &&
joinedAt > users[invitedBy].levelStarted && //prevent overflow in subtraction
level.daysToComplete <
(joinedAt - users[invitedBy].levelStarted) / 1 days; //how long after level started did invitee join

bountyToPay = level.bounty > bountyToPay ? level.bounty : bountyToPay;
users[invitedBy].totalApprovedInvites += 1;
users[invitedBy].totalEarned += bountyToPay;

bool isLevelExpired = level.daysToComplete > 0 &&
joinedAt > users[invitedBy].levelStarted && //prevent overflow in subtraction
level.daysToComplete <
(joinedAt - users[invitedBy].levelStarted) / 1 days; //how long after level started did invitee join
if (
level.toNext > 0 &&
users[invitedBy].totalApprovedInvites >= level.toNext &&
isLevelExpired == false
) {
users[invitedBy].level += 1;
users[invitedBy].levelStarted = block.timestamp;
earnedLevel = true;
}

if (isSingleBounty) goodDollar.transfer(invitedBy, bountyToPay);
}

users[_invitee].bountyPaid = true;
users[invitedBy].totalApprovedInvites += 1;
users[invitedBy].totalEarned += bountyToPay;
stats.totalApprovedInvites += 1;
stats.totalBountiesPaid += bountyToPay;

bool earnedLevel = false;
if (
level.toNext > 0 &&
users[invitedBy].totalApprovedInvites >= level.toNext &&
isLevelExpired == false
) {
users[invitedBy].level += 1;
users[invitedBy].levelStarted = block.timestamp;
earnedLevel = true;
}

if (isSingleBounty) goodDollar.transfer(invitedBy, bountyToPay);
goodDollar.transfer(_invitee, bountyToPay / 2); //pay invitee half the bounty
emit InviterBounty(
invitedBy,
Expand Down Expand Up @@ -322,6 +333,11 @@ contract InvitesV2 is DAOUpgradeableContract {
active = _active;
}

function setCampaignCode(bytes32 _code) public ownerOrAvatar {
campaignCode = _code;
codeToUser[campaignCode] = address(this);
}

function end() public ownerOrAvatar isActive {
uint256 gdBalance = goodDollar.balanceOf(address(this));
goodDollar.transfer(msg.sender, gdBalance);
Expand All @@ -346,8 +362,9 @@ contract InvitesV2 is DAOUpgradeableContract {
* 2 uses uups upgradeable - not compatible upgrade for v1
* 2.1 prevent multichain claims
* 2.2 record bounty at join time
* 2.3 support campaignCode
*/
function version() public pure returns (string memory) {
return "2.2";
return "2.3";
}
}
87 changes: 87 additions & 0 deletions contracts/invite/OneTimeReward.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

import "../Interfaces.sol";
import "../utils/NameService.sol";
import "../utils/DAOUpgradeableContract.sol";

contract OneTimeReward is Ownable, DAOContract {
bool public isActive;
uint public rewardAmount;
mapping(address => bool) public claimed;

event RewardClaimed(address indexed user, uint amount);

constructor(uint256 _rewardAmount, INameService _nameService) {
rewardAmount = _rewardAmount;
isActive = true;
setDAO(_nameService);
}

function getIdentity() public view returns (IIdentityV2) {
return IIdentityV2(nameService.getAddress("IDENTITY"));
}

function updateSettings(
bool _isActive,
uint _rewardAmount
) external onlyOwner {
isActive = _isActive;
rewardAmount = _rewardAmount;
}

function checkActiveAndBalance() public view returns (bool) {
if (!isActive) {
return false;
}

if (nativeToken().balanceOf(address(this)) < rewardAmount) {
return false;
}

return true;
}

function checkCanClaimReward(address _user) public view returns (bool) {
address whitelistedRoot = getIdentity().getWhitelistedRoot(_user);
return canClaimReward(whitelistedRoot);
}

function canClaimReward(
address whitelistedRoot
) internal view returns (bool) {
if (checkActiveAndBalance() == false) {
return false;
}

if (whitelistedRoot == address(0)) {
return false;
}

if (claimed[whitelistedRoot]) {
return false;
}

return true;
}

function claimReward(address _user) public {
address whitelistedRoot = getIdentity().getWhitelistedRoot(_user);
require(canClaimReward(whitelistedRoot), "User cannot claim reward");
claimed[whitelistedRoot] = true;

nativeToken().transfer(_user, rewardAmount);

emit RewardClaimed(_user, rewardAmount);
}

function withdrawAll(address _token) external onlyOwner {
uint balance = IERC20(_token).balanceOf(address(this));
require(balance > 0, "No tokens to withdraw");

IERC20(_token).transfer(msg.sender, balance);
}
}
25 changes: 0 additions & 25 deletions contracts/mocks/CeloDistributionHelperTest.sol

This file was deleted.

Loading

0 comments on commit e2909d6

Please sign in to comment.