Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reserve Updates #272

Merged
merged 36 commits into from
Dec 5, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
a8c2688
fix: make sure GOOD rewards are updated on change
sirpy Jan 14, 2024
d9abd4b
fix: limit amount of G$ to sell for fees
sirpy Jan 14, 2024
deeec59
fix: dont count on fundmanager and exchange helper for funds transfer
sirpy Jan 14, 2024
5fd7504
fix: dont allow untrusted staking contracts
sirpy Jan 14, 2024
a88a438
fix: dont allow transfer of hacked funds
sirpy Jan 14, 2024
2bd3054
add: hack fix upgrade script
sirpy Jan 14, 2024
126d323
add: test for reward update fix
sirpy Feb 14, 2024
0529f9f
Merge branch 'master' into reserve-hack-fix
sirpy Feb 15, 2024
22fcfa7
add: celo distribution helper
sirpy May 23, 2024
5b2d9ad
add: basic upgrade script
sirpy May 23, 2024
e605e06
Revert "fix: dont count on fundmanager and exchange helper for funds …
sirpy Jun 6, 2024
4462698
Revert "fix: dont allow transfer of hacked funds"
sirpy Jun 6, 2024
5887127
revert: initial reserve deposit
sirpy Jun 6, 2024
18c354a
fix: reset so other test file dont break
sirpy Jun 6, 2024
4924db9
fix: e2e testing issue
sirpy Jun 9, 2024
7dc7e17
add: todo
sirpy Jun 18, 2024
98c23c8
add: mento upgrade simulation and contract
sirpy Jul 3, 2024
44b3147
add: mint ubi calculation fixes and guardian proposal approval
sirpy Sep 19, 2024
b0afb8a
fix: dont count on fundmanager and exchange helper for funds transfer
sirpy Sep 23, 2024
49dde31
add: only trust reservesupply for ubi minting
sirpy Sep 24, 2024
7d9c65c
fix: new var storage location
sirpy Sep 26, 2024
3b89ff3
add: eth reserve restore upgrade
sirpy Sep 26, 2024
e76b3f2
add: audit fixes
sirpy Oct 15, 2024
87b5a4f
add: fix tests
sirpy Oct 15, 2024
cbe6be3
fix: celodisthelper sell gd
sirpy Oct 20, 2024
7e281c2
fix: celodist helper tests
sirpy Oct 20, 2024
62d1eed
fix: failing test
sirpy Oct 22, 2024
e8c1d66
fix: coverage
sirpy Oct 23, 2024
a908de7
add: use transferfrom to prevent dai lock. upgrade fuse governance
sirpy Oct 30, 2024
4e82ee4
add: remove mento upgrade into separate PR
sirpy Oct 30, 2024
3c268e0
add: removed outdated todo
sirpy Nov 3, 2024
5767441
fix: code review use constant for repeating addresses
sirpy Nov 6, 2024
f506b1c
fix: remove redundant require
sirpy Nov 6, 2024
715b6d4
add: allow to restore reserve in batches
sirpy Nov 6, 2024
e54075a
Merge branch 'master' into reserve-hack-fix
sirpy Nov 28, 2024
494ba11
Merge branch 'master' into reserve-hack-fix
sirpy Nov 28, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@
"bracketSpacing": true,
"explicitTypes": "always"
}
},
{
"files": "test/**/*.ts",
"options": {
"printWidth": 80
}
}
],
"printWidth": 120,
Expand Down
26 changes: 13 additions & 13 deletions contracts/governance/StakersDistribution.sol
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ contract StakersDistribution is
) external {
_onlyAvatar();
monthlyReputationDistribution = newMonthlyReputationDistribution;
_updateRewards();
}

/**
Expand Down Expand Up @@ -174,9 +175,10 @@ contract StakersDistribution is
_claimReputation(_staker, _stakingContracts);
}

function _claimReputation(address _staker, address[] memory _stakingContracts)
internal
{
function _claimReputation(
address _staker,
address[] memory _stakingContracts
) internal {
uint256 totalRep;
GoodFundManager gfm = GoodFundManager(
nameService.getAddress("FUND_MANAGER")
Expand Down Expand Up @@ -206,11 +208,10 @@ contract StakersDistribution is
* @param _user the user to check rewards for
* @return reputation rewards pending for user
*/
function getUserPendingRewards(address[] memory _contracts, address _user)
public
view
returns (uint256)
{
function getUserPendingRewards(
address[] memory _contracts,
address _user
) public view returns (uint256) {
uint256 pending;
for (uint256 i = 0; i < _contracts.length; i++) {
(
Expand Down Expand Up @@ -239,11 +240,10 @@ contract StakersDistribution is
* @param _user account to get rewards status for
* @return (minted, pending) in GDAO 18 decimals
*/
function getUserMintedAndPending(address[] memory _contracts, address _user)
public
view
returns (uint256, uint256)
{
function getUserMintedAndPending(
address[] memory _contracts,
address _user
) public view returns (uint256, uint256) {
uint256 pending = getUserPendingRewards(_contracts, _user);
uint256 minted;
for (uint256 i = 0; i < _contracts.length; i++) {
Expand Down
29 changes: 26 additions & 3 deletions contracts/reserve/DistributionHelper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -182,10 +182,11 @@ contract DistributionHelper is
if (toDistribute == 0) return;

if (address(this).balance < feeSettings.minBalanceForFees) {
uint256 gdToSellfForFee = (toDistribute *
uint256 gdToSellForFee = (toDistribute *
feeSettings.percentageToSellForFee) / 100;
toDistribute -= gdToSellfForFee;
buyNativeWithGD(gdToSellfForFee);
gdToSellForFee = calcGDToSell(gdToSellForFee);
toDistribute -= gdToSellForFee;
buyNativeWithGD(gdToSellForFee);
}

uint256 totalDistributed;
Expand Down Expand Up @@ -276,6 +277,28 @@ contract DistributionHelper is
}
}

function calcGDToSell(
uint256 maxAmountToSell
) public view returns (uint256 gdToSell) {
uint24[] memory fees = new uint24[](1);
fees[0] = 500;

uint256 ethToBuy = feeSettings.minBalanceForFees * 3;
(uint256 ethValueInUSDC, ) = STATIC_ORACLE
.quoteSpecificFeeTiersWithTimePeriod(
uint128(ethToBuy),
WETH_TOKEN,
USDC_TOKEN,
fees,
60 //last 1 minute
);

uint256 gdPriceInDai = GoodReserveCDai(nameService.getAddress("RESERVE"))
.currentPriceDAI();
gdToSell = (ethValueInUSDC * 1e12 * 100) / gdPriceInDai; //* 1e12 to increase usdc to 12 decimals, mul by 100 so result is in 2 G$ 2 decimals
gdToSell = gdToSell > maxAmountToSell ? maxAmountToSell : gdToSell;
}

function buyNativeWithGD(uint256 amountToSell) internal {
address[] memory path = new address[](2);
path[0] = nameService.getAddress("DAI");
Expand Down
24 changes: 17 additions & 7 deletions contracts/reserve/ExchangeHelper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,17 @@ contract ExchangeHelper is DAOUpgradeableContract {
cDaiAddress = nameService.getAddress("CDAI");
// Approve transfer to cDAI contract
ERC20(daiAddress).approve(cDaiAddress, type(uint256).max);
address reserve = nameService.getAddress("RESERVE");
if (reserve != address(0)) {
ERC20(nameService.getAddress("GOODDOLLAR")).approve(
nameService.getAddress("RESERVE"),
type(uint256).max
);
ERC20(cDaiAddress).approve(
nameService.getAddress("RESERVE"),
type(uint256).max
);
}
ERC20(daiAddress).approve(
nameService.getAddress("UNISWAP_ROUTER"),
type(uint256).max
Expand Down Expand Up @@ -121,11 +132,9 @@ contract ExchangeHelper is DAOUpgradeableContract {
require(
ERC20(_buyPath[0]).transferFrom(
msg.sender,
address(_buyPath[0]) == cDaiAddress
? address(reserve)
: address(this),
address(this),
_tokenAmount
) == true,
),
"transferFrom failed, make sure you approved input token transfer"
);
}
Expand Down Expand Up @@ -191,8 +200,9 @@ contract ExchangeHelper is DAOUpgradeableContract {
GoodReserveCDai reserve = GoodReserveCDai(
nameService.getAddress("RESERVE")
);
IGoodDollar(nameService.getAddress("GOODDOLLAR")).burnFrom(
ERC20(nameService.getAddress("GOODDOLLAR")).transferFrom(
msg.sender,
address(this),
_gdAmount
);

Expand Down Expand Up @@ -290,7 +300,6 @@ contract ExchangeHelper is DAOUpgradeableContract {
require(cDaiResult == 0, "Minting cDai failed");

uint256 cDaiInput = cDai.balanceOf(address(this)) - currCDaiBalance;
cDai.transfer(address(reserve), cDaiInput);
return reserve.buy(cDaiInput, _minReturn, _targetAddress);
}

Expand Down Expand Up @@ -333,8 +342,9 @@ contract ExchangeHelper is DAOUpgradeableContract {
);
return swap;
} else {
if (isBuy)
if (isBuy) {
ERC20(_inputPath[0]).approve(address(uniswapContract), _tokenAmount);
}
swap = uniswapContract.swapExactTokensForTokens(
_tokenAmount,
isBuy ? _minDAIAmount : _minTokenReturn,
Expand Down
60 changes: 44 additions & 16 deletions contracts/reserve/GoodReserveCDai.sol
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ contract GoodReserveCDai is

bool public gdxDisabled;
bool public discountDisabled;

uint256 private _reentrantStatus;

// Emits when new GD tokens minted
event UBIMinted(
//epoch of UBI
Expand Down Expand Up @@ -186,19 +189,21 @@ contract GoodReserveCDai is
uint256 _tokenAmount,
uint256 _minReturn,
address _targetAddress
) external returns (uint256) {
) external nonReentrant returns (uint256) {
ERC20 buyWith = ERC20(cDaiAddress);
uint256 gdReturn = getMarketMaker().buy(buyWith, _tokenAmount);
_targetAddress = _targetAddress == address(0x0)
? msg.sender
: _targetAddress;
address exchangeHelper = nameService.getAddress("EXCHANGE_HELPER");
if (msg.sender != exchangeHelper)
require(
buyWith.transferFrom(msg.sender, address(this), _tokenAmount) == true,
"transferFrom failed, make sure you approved input token transfer"
);

require(
buyWith.transferFrom(msg.sender, address(this), _tokenAmount) == true,
"transferFrom failed, make sure you approved input token transfer"
);

require(gdReturn >= _minReturn, "GD return must be above the minReturn");

_mintGoodDollars(_targetAddress, gdReturn, true);
//mint GDX
_mintGDX(_targetAddress, gdReturn);
Expand All @@ -224,7 +229,7 @@ contract GoodReserveCDai is
address _token,
address _to,
uint256 _amount
) external {
) external nonReentrant {
getMarketMaker().mintFromReserveRatio(ERC20(_token), _amount);
_mintGoodDollars(_to, _amount, false);
//mint GDX
Expand All @@ -247,16 +252,24 @@ contract GoodReserveCDai is
uint256 _minReturn,
address _target,
address _seller
) external returns (uint256, uint256) {
) external nonReentrant returns (uint256, uint256) {
require(paused() == false, "paused");
GoodMarketMaker mm = getMarketMaker();
if (msg.sender != nameService.getAddress("EXCHANGE_HELPER")) {
IGoodDollar(nameService.getAddress("GOODDOLLAR")).burnFrom(
msg.sender,
_gdAmount
);
_seller = msg.sender;
}

/**
* transferfrom and then burn instead of burnfrom to make sure funds after fee are valid
* ie specifically for addresses that hold stolen funds who has 100% fee applied
*/
IGoodDollar(nameService.getAddress("GOODDOLLAR")).transferFrom(
msg.sender,
address(this),
_gdAmount
);
IGoodDollar(nameService.getAddress("GOODDOLLAR")).burn(_gdAmount);

_target = _target == address(0x0) ? msg.sender : _target;
//discount on exit contribution based on gdx

Expand Down Expand Up @@ -358,18 +371,21 @@ contract GoodReserveCDai is
* @dev only FundManager or other with mint G$ permission can call this to trigger minting.
* Reserve sends UBI + interest to FundManager.
* @param _daiToConvert DAI amount to convert cDAI
* @param _startingCDAIBalance Initial cDAI balance before staking collect process start
* @param _interestToken The token that was transfered to the reserve
* @return gdUBI,interestInCdai how much GD UBI was minted and how much cDAI collected from staking contracts
*/
function mintUBI(
uint256 _daiToConvert,
uint256 _startingCDAIBalance,
uint256 /*_startingCDAIBalance*/, // dont trust it, use reserveSupply from marketmaker instead
ERC20 _interestToken
) external returns (uint256, uint256) {
) external nonReentrant returns (uint256, uint256) {
cERC20(cDaiAddress).mint(_daiToConvert);

(uint256 reserveSupply, , , ) = getMarketMaker().reserveTokens(cDaiAddress);

uint256 interestInCdai = _interestToken.balanceOf(address(this)) -
_startingCDAIBalance;
reserveSupply;

uint256 gdInterestToMint = getMarketMaker().mintInterest(
_interestToken,
interestInCdai
Expand Down Expand Up @@ -519,4 +535,16 @@ contract GoodReserveCDai is
function when() public pure override returns (CallPhase) {
return CallPhase.Pre;
}

modifier nonReentrant() {
// On the first call to nonReentrant, _status will be _NOT_ENTERED
require(_reentrantStatus != 1, "ReentrancyGuard: reentrant call");

// Any calls to nonReentrant after this point will fail
_reentrantStatus = 1;
_;
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_reentrantStatus = 0;
}
}
34 changes: 33 additions & 1 deletion contracts/staking/GoodFundManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ contract GoodFundManager is DAOUpgradeableContract, DSMath {
//address of the active staking contracts
address[] public activeContracts;

uint256 private _reentrantStatus;

event GasCostSet(uint256 newGasCost);
event CollectInterestTimeThresholdSet(
uint256 newCollectInterestTimeThreshold
Expand Down Expand Up @@ -221,7 +223,7 @@ contract GoodFundManager is DAOUpgradeableContract, DSMath {
function collectInterest(
address[] calldata _stakingContracts,
bool _forceAndWaiverRewards
) external {
) external nonReentrant {
uint256 initialGas = gasleft();
uint256 gdUBI;
uint256 interestInCdai;
Expand All @@ -242,6 +244,10 @@ contract GoodFundManager is DAOUpgradeableContract, DSMath {
// elements are sorted by balances from lowest to highest

if (_stakingContracts[i - 1] != address(0x0)) {
require(
isActiveContract(_stakingContracts[i - 1]),
"collectInterest: not a dao contract"
);
IGoodStaking(_stakingContracts[i - 1]).collectUBIInterest(
reserveAddress
);
Expand Down Expand Up @@ -299,6 +305,20 @@ contract GoodFundManager is DAOUpgradeableContract, DSMath {
lastCollectedInterestBlock = block.number;
}

/**
* @dev verifies that contract was added to the approved staking contracts in the past, and is not blacklisted
* @param _contract address of the contract
* @return isActive true if contract is active
**/
function isActiveContract(
address _contract
) public view returns (bool isActive) {
return
!rewardsForStakingContract[_contract].isBlackListed &&
(rewardsForStakingContract[_contract].blockStart > 0 ||
rewardsForStakingContract[_contract].blockEnd > 0);
}

/**
* @dev Function that get interest informations of staking contracts in the sorted array by highest interest to lowest interest amount
* @return array of interestInfo struct
Expand Down Expand Up @@ -467,4 +487,16 @@ contract GoodFundManager is DAOUpgradeableContract, DSMath {
function getActiveContractsCount() public view returns (uint256) {
return activeContracts.length;
}

modifier nonReentrant() {
// On the first call to nonReentrant, _status will be _NOT_ENTERED
require(_reentrantStatus != 1, "ReentrancyGuard: reentrant call");

// Any calls to nonReentrant after this point will fail
_reentrantStatus = 1;
_;
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_reentrantStatus = 0;
}
}
2 changes: 1 addition & 1 deletion contracts/token/MultichainFeeFormula.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ contract MultichainFeeFormula is IFeesFormula {
sender == address(0xD17652350Cfd2A37bA2f947C910987a3B1A1c60d) ||
sender == address(0xeC577447D314cf1e443e9f4488216651450DBE7c) ||
sender == address(0x6738fA889fF31F82d9Fe8862ec025dbE318f3Fde)
) fee = value;
) revert("locked funds");
if (recipient == address(0xD17652350Cfd2A37bA2f947C910987a3B1A1c60d))
revert("multichain hack");
}
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@
"typechain": "^8.3.1",
"typescript": "^5.3.3"
},
"packageManager": "yarn@3.6.0",
"packageManager": "yarn@3.6.1",
"resolutions": {
"@swc/core": "1.3.96"
},
Expand Down
Loading
Loading