From 6f4c95f3574d6dca8538e945cd48b21564fd95ce Mon Sep 17 00:00:00 2001 From: Ray Huang Date: Sat, 2 Mar 2024 18:05:43 +0800 Subject: [PATCH 1/5] add referral manager --- script/DeploySetup.s.sol | 21 +- script/DeploySetupConfig.sol | 4 + src/helpers/ReferralManager.sol | 93 +++++ src/helpers/SatoshiBORouter.sol | 31 +- src/helpers/interfaces/IReferralManager.sol | 26 ++ src/helpers/interfaces/ISatoshiBORouter.sol | 12 +- test/ReferralManager.t.sol | 370 ++++++++++++++++++++ test/SatoshiBORouter.t.sol | 35 +- test/UpgradeContract.t.sol | 9 +- test/utils/DeployBase.t.sol | 27 +- test/utils/Events.sol | 3 + 11 files changed, 605 insertions(+), 26 deletions(-) create mode 100644 src/helpers/ReferralManager.sol create mode 100644 src/helpers/interfaces/IReferralManager.sol create mode 100644 test/ReferralManager.t.sol diff --git a/script/DeploySetup.s.sol b/script/DeploySetup.s.sol index df3d4c7..34ba349 100644 --- a/script/DeploySetup.s.sol +++ b/script/DeploySetup.s.sol @@ -19,6 +19,7 @@ import {IPriceFeed} from "../src/interfaces/dependencies/IPriceFeed.sol"; import {IMultiCollateralHintHelpers} from "../src/helpers/interfaces/IMultiCollateralHintHelpers.sol"; import {IMultiTroveGetter} from "../src/helpers/interfaces/IMultiTroveGetter.sol"; import {ISatoshiBORouter} from "../src/helpers/interfaces/ISatoshiBORouter.sol"; +import {IReferralManager} from "../src/helpers/interfaces/IReferralManager.sol"; import {IWETH} from "../src/helpers/interfaces/IWETH.sol"; import {SortedTroves} from "../src/core/SortedTroves.sol"; import {SatoshiCore} from "../src/core/SatoshiCore.sol"; @@ -33,6 +34,7 @@ import {Factory} from "../src/core/Factory.sol"; import {MultiCollateralHintHelpers} from "../src/helpers/MultiCollateralHintHelpers.sol"; import {MultiTroveGetter} from "../src/helpers/MultiTroveGetter.sol"; import {SatoshiBORouter} from "../src/helpers/SatoshiBORouter.sol"; +import {ReferralManager} from "../src/helpers/ReferralManager.sol"; import { SATOSHI_CORE_OWNER, SATOSHI_CORE_GUARDIAN, @@ -42,7 +44,9 @@ import { DEBT_TOKEN_SYMBOL, BO_MIN_NET_DEBT, GAS_COMPENSATION, - WETH_ADDRESS + WETH_ADDRESS, + REFERRAL_START_TIMESTAMP, + REFERRAL_END_TIMESTAMP } from "./DeploySetupConfig.sol"; contract DeploySetupScript is Script { @@ -74,6 +78,7 @@ contract DeploySetupScript is Script { IMultiCollateralHintHelpers hintHelpers; IMultiTroveGetter multiTroveGetter; ISatoshiBORouter satoshiBORouter; + IReferralManager referralManager; /* computed contracts for deployment */ // implementation contracts @@ -245,7 +250,18 @@ contract DeploySetupScript is Script { multiTroveGetter = new MultiTroveGetter(); // SatoshiBORouter - satoshiBORouter = new SatoshiBORouter(debtToken, borrowerOperationsProxy, IWETH(WETH_ADDRESS)); + address cpSatoshiBORouterAddr = vm.computeCreateAddress(deployer, ++nonce); + address cpReferralManagerAddr = vm.computeCreateAddress(deployer, ++nonce); + satoshiBORouter = new SatoshiBORouter( + debtToken, borrowerOperationsProxy, IReferralManager(cpReferralManagerAddr), IWETH(WETH_ADDRESS) + ); + assert(cpSatoshiBORouterAddr == address(satoshiBORouter)); + + // ReferralManager + referralManager = new ReferralManager( + ISatoshiBORouter(cpSatoshiBORouterAddr), REFERRAL_START_TIMESTAMP, REFERRAL_END_TIMESTAMP + ); + assert(cpReferralManagerAddr == address(referralManager)); console.log("Deployed contracts:"); console.log("priceFeedAggregatorImpl:", address(priceFeedAggregatorImpl)); @@ -267,6 +283,7 @@ contract DeploySetupScript is Script { console.log("hintHelpers:", address(hintHelpers)); console.log("multiTroveGetter:", address(multiTroveGetter)); console.log("satoshiBORouter:", address(satoshiBORouter)); + console.log("referralManager:", address(referralManager)); vm.stopBroadcast(); } diff --git a/script/DeploySetupConfig.sol b/script/DeploySetupConfig.sol index 9725529..9a72ea2 100644 --- a/script/DeploySetupConfig.sol +++ b/script/DeploySetupConfig.sol @@ -13,3 +13,7 @@ string constant DEBT_TOKEN_NAME = "Statoshi Stablecoin"; string constant DEBT_TOKEN_SYMBOL = "SAT"; address constant WETH_ADDRESS = 0x51abb19F1ebc7B64040aFd0ef3C789d75C8707e0; + +//TODO: Replace with the actual timestamp +uint256 constant REFERRAL_START_TIMESTAMP = 1709251200; // 2024-03-01 00:00:00 UTC +uint256 constant REFERRAL_END_TIMESTAMP = 1711929600; // 2024-04-01 00:00:00 UTC diff --git a/src/helpers/ReferralManager.sol b/src/helpers/ReferralManager.sol new file mode 100644 index 0000000..612045a --- /dev/null +++ b/src/helpers/ReferralManager.sol @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.13; + +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {IReferralManager} from "./interfaces/IReferralManager.sol"; +import {ISatoshiBORouter} from "./interfaces/ISatoshiBORouter.sol"; + +contract ReferralManager is IReferralManager, Ownable { + ISatoshiBORouter public immutable satoshiBORouter; + + uint256 public startTimestamp; + uint256 public endTimestamp; + + uint256 internal totalPoints; + mapping(address => uint256) internal points; + + event SetStartTimestamp(uint256 _startTimestamp); + event SetEndTimestamp(uint256 _endTimestamp); + event ExecuteReferral(address indexed borrower, address indexed referrer, uint256 points); + + error InvalidTimestamp(uint256 timestamp); + error InvalidZeroAddress(); + error Unauthorized(address _caller); + + modifier onlySatoshiBORouter() { + if (msg.sender != address(satoshiBORouter)) revert Unauthorized(msg.sender); + _; + } + + constructor(ISatoshiBORouter _satoshiBORouter, uint256 _startTimestamp, uint256 _endTimestamp) { + if (address(_satoshiBORouter) == address(0)) revert InvalidZeroAddress(); + + satoshiBORouter = _satoshiBORouter; + startTimestamp = _startTimestamp; + endTimestamp = _endTimestamp; + } + + function executeReferral(address _borrower, address _referrer, uint256 _points) external onlySatoshiBORouter { + if (_isReferralActive()) { + _addPoint(_referrer, _points); + _addTotalPoints(_points); + + emit ExecuteReferral(_borrower, _referrer, _points); + } else { + // do nothing + } + } + + function isReferralActive() external view returns (bool) { + return _isReferralActive(); + } + + function _addPoint(address _account, uint256 _points) internal { + points[_account] += _points; + } + + function _addTotalPoints(uint256 _points) internal { + totalPoints += _points; + } + + function _isReferralActive() internal view returns (bool) { + uint256 _timestamp = block.timestamp; + return _timestamp >= startTimestamp && _timestamp <= endTimestamp; + } + + function getTotalPoints() external view returns (uint256) { + return totalPoints; + } + + function getBatchPoints(address[] calldata _accounts) external view returns (uint256[] memory) { + uint256[] memory _points = new uint256[](_accounts.length); + for (uint256 i = 0; i < _accounts.length; i++) { + _points[i] = points[_accounts[i]]; + } + return _points; + } + + function getPoints(address _account) external view returns (uint256) { + return points[_account]; + } + + function setStartTimestamp(uint256 _startTimestamp) external onlyOwner { + if (_startTimestamp > endTimestamp) revert InvalidTimestamp(_startTimestamp); + startTimestamp = _startTimestamp; + emit SetStartTimestamp(_startTimestamp); + } + + function setEndTimestamp(uint256 _endTimestamp) external onlyOwner { + if (_endTimestamp < startTimestamp) revert InvalidTimestamp(_endTimestamp); + endTimestamp = _endTimestamp; + emit SetEndTimestamp(_endTimestamp); + } +} diff --git a/src/helpers/SatoshiBORouter.sol b/src/helpers/SatoshiBORouter.sol index 8a20fc8..d926458 100644 --- a/src/helpers/SatoshiBORouter.sol +++ b/src/helpers/SatoshiBORouter.sol @@ -7,6 +7,7 @@ import {IBorrowerOperations} from "../interfaces/core/IBorrowerOperations.sol"; import {ITroveManager} from "../interfaces/core/ITroveManager.sol"; import {IDebtToken} from "../interfaces/core/IDebtToken.sol"; import {ISatoshiBORouter} from "./interfaces/ISatoshiBORouter.sol"; +import {IReferralManager} from "./interfaces/IReferralManager.sol"; import {SatoshiMath} from "../dependencies/SatoshiMath.sol"; /** @@ -16,15 +17,23 @@ import {SatoshiMath} from "../dependencies/SatoshiMath.sol"; contract SatoshiBORouter is ISatoshiBORouter { IDebtToken public immutable debtToken; IBorrowerOperations public immutable borrowerOperationsProxy; + IReferralManager public immutable referralManager; IWETH public immutable weth; - constructor(IDebtToken _debtToken, IBorrowerOperations _borrowerOperationsProxy, IWETH _weth) { + constructor( + IDebtToken _debtToken, + IBorrowerOperations _borrowerOperationsProxy, + IReferralManager _referralManager, + IWETH _weth + ) { if (address(_debtToken) == address(0)) revert InvalidZeroAddress(); if (address(_borrowerOperationsProxy) == address(0)) revert InvalidZeroAddress(); + if (address(_referralManager) == address(0)) revert InvalidZeroAddress(); if (address(_weth) == address(0)) revert InvalidZeroAddress(); debtToken = _debtToken; borrowerOperationsProxy = _borrowerOperationsProxy; + referralManager = _referralManager; weth = _weth; } @@ -37,7 +46,8 @@ contract SatoshiBORouter is ISatoshiBORouter { uint256 _collAmount, uint256 _debtAmount, address _upperHint, - address _lowerHint + address _lowerHint, + address _referrer ) external payable { IERC20 collateralToken = troveManager.collateralToken(); @@ -52,7 +62,7 @@ contract SatoshiBORouter is ISatoshiBORouter { uint256 debtTokenBalanceAfter = debtToken.balanceOf(address(this)); uint256 userDebtAmount = debtTokenBalanceAfter - debtTokenBalanceBefore; require(userDebtAmount == _debtAmount, "SatoshiBORouter: Debt amount mismatch"); - _afterWithdrawDebt(userDebtAmount); + _afterWithdrawDebt(account, _referrer, userDebtAmount); } function addColl( @@ -93,7 +103,8 @@ contract SatoshiBORouter is ISatoshiBORouter { uint256 _maxFeePercentage, uint256 _debtAmount, address _upperHint, - address _lowerHint + address _lowerHint, + address _referrer ) external { uint256 debtTokenBalanceBefore = debtToken.balanceOf(address(this)); borrowerOperationsProxy.withdrawDebt( @@ -102,7 +113,7 @@ contract SatoshiBORouter is ISatoshiBORouter { uint256 debtTokenBalanceAfter = debtToken.balanceOf(address(this)); uint256 userDebtAmount = debtTokenBalanceAfter - debtTokenBalanceBefore; require(userDebtAmount == _debtAmount, "SatoshiBORouter: Debt amount mismatch"); - _afterWithdrawDebt(userDebtAmount); + _afterWithdrawDebt(account, _referrer, userDebtAmount); } function repayDebt( @@ -126,7 +137,8 @@ contract SatoshiBORouter is ISatoshiBORouter { uint256 _debtChange, bool _isDebtIncrease, address _upperHint, - address _lowerHint + address _lowerHint, + address _referrer ) external payable { if (_collDeposit != 0 && _collWithdrawal != 0) revert CannotWithdrawAndAddColl(); @@ -162,7 +174,7 @@ contract SatoshiBORouter is ISatoshiBORouter { if (_isDebtIncrease) { uint256 userDebtAmount = debtTokenBalanceAfter - debtTokenBalanceBefore; require(userDebtAmount == _debtChange, "SatoshiBORouter: Debt amount mismatch"); - _afterWithdrawDebt(userDebtAmount); + _afterWithdrawDebt(account, _referrer, userDebtAmount); } } @@ -215,10 +227,13 @@ contract SatoshiBORouter is ISatoshiBORouter { debtToken.transferFrom(msg.sender, address(this), debtAmount); } - function _afterWithdrawDebt(uint256 debtAmount) private { + function _afterWithdrawDebt(address _borrower, address _referrer, uint256 debtAmount) private { if (debtAmount == 0) return; debtToken.transfer(msg.sender, debtAmount); + + // execute referral + referralManager.executeReferral(_borrower, _referrer, debtAmount); } receive() external payable { diff --git a/src/helpers/interfaces/IReferralManager.sol b/src/helpers/interfaces/IReferralManager.sol new file mode 100644 index 0000000..60638ef --- /dev/null +++ b/src/helpers/interfaces/IReferralManager.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.13; + +import {ISatoshiBORouter} from "./ISatoshiBORouter.sol"; + +interface IReferralManager { + function satoshiBORouter() external view returns (ISatoshiBORouter); + + function startTimestamp() external view returns (uint256); + + function endTimestamp() external view returns (uint256); + + function executeReferral(address _borrower, address _referrer, uint256 _points) external; + + function isReferralActive() external view returns (bool); + + function getTotalPoints() external view returns (uint256); + + function getBatchPoints(address[] calldata _accounts) external view returns (uint256[] memory); + + function getPoints(address _account) external view returns (uint256); + + function setStartTimestamp(uint256 _startTimestamp) external; + + function setEndTimestamp(uint256 _endTimestamp) external; +} diff --git a/src/helpers/interfaces/ISatoshiBORouter.sol b/src/helpers/interfaces/ISatoshiBORouter.sol index 8b17d86..cc7d2b2 100644 --- a/src/helpers/interfaces/ISatoshiBORouter.sol +++ b/src/helpers/interfaces/ISatoshiBORouter.sol @@ -4,6 +4,7 @@ pragma solidity 0.8.13; import {IDebtToken} from "../../interfaces/core/IDebtToken.sol"; import {IBorrowerOperations} from "../../interfaces/core/IBorrowerOperations.sol"; import {ITroveManager} from "../../interfaces/core/ITroveManager.sol"; +import {IReferralManager} from "./IReferralManager.sol"; import {IWETH} from "./IWETH.sol"; interface ISatoshiBORouter { @@ -17,6 +18,8 @@ interface ISatoshiBORouter { function borrowerOperationsProxy() external view returns (IBorrowerOperations); + function referralManager() external view returns (IReferralManager); + function weth() external view returns (IWETH); function openTrove( @@ -26,7 +29,8 @@ interface ISatoshiBORouter { uint256 _collAmount, uint256 _debtAmount, address _upperHint, - address _lowerHint + address _lowerHint, + address _referrer ) external payable; function addColl( @@ -51,7 +55,8 @@ interface ISatoshiBORouter { uint256 _maxFeePercentage, uint256 _debtAmount, address _upperHint, - address _lowerHint + address _lowerHint, + address _referrer ) external; function repayDebt( @@ -71,7 +76,8 @@ interface ISatoshiBORouter { uint256 _debtChange, bool _isDebtIncrease, address _upperHint, - address _lowerHint + address _lowerHint, + address _referrer ) external payable; function closeTrove(ITroveManager troveManager, address account) external; diff --git a/test/ReferralManager.t.sol b/test/ReferralManager.t.sol new file mode 100644 index 0000000..0275a49 --- /dev/null +++ b/test/ReferralManager.t.sol @@ -0,0 +1,370 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Test, console} from "forge-std/Test.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; +import {ISortedTroves} from "../src/interfaces/core/ISortedTroves.sol"; +import {ITroveManager, TroveManagerOperation} from "../src/interfaces/core/ITroveManager.sol"; +import {ISatoshiBORouter} from "../src/helpers/interfaces/ISatoshiBORouter.sol"; +import {IMultiCollateralHintHelpers} from "../src/helpers/interfaces/IMultiCollateralHintHelpers.sol"; +import {IWETH} from "../src/helpers/interfaces/IWETH.sol"; +import {IReferralManager} from "../src/helpers/interfaces/IReferralManager.sol"; +import {SatoshiMath} from "../src/dependencies/SatoshiMath.sol"; +import {DeployBase, LocalVars} from "./utils/DeployBase.t.sol"; +import {HintLib} from "./utils/HintLib.sol"; +import {DEPLOYER, OWNER, GAS_COMPENSATION, TestConfig} from "./TestConfig.sol"; +import {TroveBase} from "./utils/TroveBase.t.sol"; +import {Events} from "./utils/Events.sol"; + +contract ReferralManagerTest is Test, DeployBase, TroveBase, TestConfig, Events { + using Math for uint256; + + ISortedTroves sortedTrovesBeaconProxy; + ITroveManager troveManagerBeaconProxy; + IWETH weth; + IMultiCollateralHintHelpers hintHelpers; + ISatoshiBORouter satoshiBORouter; + IReferralManager referralManager; + address user; + address referrer; + + function setUp() public override { + super.setUp(); + + // testing user + user = vm.addr(1); + referrer = vm.addr(2); + + // use WETH as collateral + weth = IWETH(_deployWETH(DEPLOYER)); + deal(address(weth), 10000e18); + + // setup contracts and deploy one instance + (sortedTrovesBeaconProxy, troveManagerBeaconProxy) = _deploySetupAndInstance( + DEPLOYER, OWNER, ORACLE_MOCK_DECIMALS, ORACLE_MOCK_VERSION, initRoundData, weth, deploymentParams + ); + + // deploy helper contracts + hintHelpers = IMultiCollateralHintHelpers(_deployHintHelpers(DEPLOYER)); + + uint64 nonce = vm.getNonce(DEPLOYER); + address cpSatoshiBORouterAddr = vm.computeCreateAddress(DEPLOYER, nonce); + address cpReferralManagerAddr = vm.computeCreateAddress(DEPLOYER, ++nonce); + satoshiBORouter = + ISatoshiBORouter(_deploySatoshiBORouter(DEPLOYER, IReferralManager(cpReferralManagerAddr), weth)); + referralManager = IReferralManager(_deployReferralManager(DEPLOYER, ISatoshiBORouter(cpSatoshiBORouterAddr))); + + // user set delegate approval for satoshiBORouter + vm.startPrank(user); + borrowerOperationsProxy.setDelegateApproval(address(satoshiBORouter), true); + vm.stopPrank(); + } + + function testOpenTroveReferral() public { + LocalVars memory vars; + // open trove params + vars.collAmt = 1e18; // price defined in `TestConfig.roundData` + vars.debtAmt = 10000e18; // 10000 USD + + vm.startPrank(user); + deal(user, 1e18); + + // state before + uint256 totalPointsBefore = referralManager.getTotalPoints(); + uint256 referrerPointsBefore = referralManager.getPoints(referrer); + + /* check events emitted correctly in tx */ + // check ExecuteReferral event + vm.expectEmit(true, true, true, true, address(referralManager)); + emit ExecuteReferral(user, referrer, vars.debtAmt); + + // calc hint + (vars.upperHint, vars.lowerHint) = HintLib.getHint( + hintHelpers, sortedTrovesBeaconProxy, troveManagerBeaconProxy, vars.collAmt, vars.debtAmt, GAS_COMPENSATION + ); + // tx execution + satoshiBORouter.openTrove{value: vars.collAmt}( + troveManagerBeaconProxy, + user, + 0.05e18, /* vars.maxFeePercentage 5% */ + vars.collAmt, + vars.debtAmt, + vars.upperHint, + vars.lowerHint, + referrer + ); + + // state after + uint256 totalPointsAfter = referralManager.getTotalPoints(); + uint256 referrerPointsAfter = referralManager.getPoints(referrer); + + // check state + assertEq(totalPointsAfter, totalPointsBefore + vars.debtAmt); + assertEq(referrerPointsAfter, referrerPointsBefore + vars.debtAmt); + + vm.stopPrank(); + } + + function testWithdrawDebtReferral() public { + LocalVars memory vars; + // pre open trove + vars.collAmt = 1e18; // price defined in `TestConfig.roundData` + vars.debtAmt = 10000e18; // 10000 USD + vars.maxFeePercentage = 0.05e18; // 5% + TroveBase.openTrove( + borrowerOperationsProxy, + sortedTrovesBeaconProxy, + troveManagerBeaconProxy, + hintHelpers, + GAS_COMPENSATION, + user, + user, + weth, + vars.collAmt, + vars.debtAmt, + vars.maxFeePercentage + ); + + vars.withdrawDebtAmt = 10000e18; + vars.totalNetDebtAmt = vars.debtAmt + vars.withdrawDebtAmt; + + vm.startPrank(user); + + // state before + uint256 totalPointsBefore = referralManager.getTotalPoints(); + uint256 referrerPointsBefore = referralManager.getPoints(referrer); + + /* check events emitted correctly in tx */ + // check ExecuteReferral event + vm.expectEmit(true, true, true, true, address(referralManager)); + emit ExecuteReferral(user, referrer, vars.withdrawDebtAmt); + + // calc hint + (vars.upperHint, vars.lowerHint) = HintLib.getHint( + hintHelpers, + sortedTrovesBeaconProxy, + troveManagerBeaconProxy, + vars.collAmt, + vars.totalNetDebtAmt, + GAS_COMPENSATION + ); + // tx execution + satoshiBORouter.withdrawDebt( + troveManagerBeaconProxy, + user, + vars.maxFeePercentage, + vars.withdrawDebtAmt, + vars.upperHint, + vars.lowerHint, + referrer + ); + + // state after + uint256 totalPointsAfter = referralManager.getTotalPoints(); + uint256 referrerPointsAfter = referralManager.getPoints(referrer); + + // check state + assertEq(totalPointsAfter, totalPointsBefore + vars.withdrawDebtAmt); + assertEq(referrerPointsAfter, referrerPointsBefore + vars.withdrawDebtAmt); + + vm.stopPrank(); + } + + function testAdjustTroveReferral_AddCollAndWithdrawDebt() public { + LocalVars memory vars; + // pre open trove + vars.collAmt = 1e18; // price defined in `TestConfig.roundData` + vars.debtAmt = 10000e18; // 10000 USD + vars.maxFeePercentage = 0.05e18; // 5% + TroveBase.openTrove( + borrowerOperationsProxy, + sortedTrovesBeaconProxy, + troveManagerBeaconProxy, + hintHelpers, + GAS_COMPENSATION, + user, + user, + weth, + vars.collAmt, + vars.debtAmt, + vars.maxFeePercentage + ); + + vars.addCollAmt = 0.5e18; + vars.withdrawDebtAmt = 5000e18; + vars.totalCollAmt = vars.collAmt + vars.addCollAmt; + vars.totalNetDebtAmt = vars.debtAmt + vars.withdrawDebtAmt; + + vm.startPrank(user); + deal(user, vars.addCollAmt); + + // state before + uint256 totalPointsBefore = referralManager.getTotalPoints(); + uint256 referrerPointsBefore = referralManager.getPoints(referrer); + + /* check events emitted correctly in tx */ + // check ExecuteReferral event + vm.expectEmit(true, true, true, true, address(referralManager)); + emit ExecuteReferral(user, referrer, vars.withdrawDebtAmt); + + // calc hint + (vars.upperHint, vars.lowerHint) = HintLib.getHint( + hintHelpers, + sortedTrovesBeaconProxy, + troveManagerBeaconProxy, + vars.totalCollAmt, + vars.totalNetDebtAmt, + GAS_COMPENSATION + ); + + // tx execution + satoshiBORouter.adjustTrove{value: vars.addCollAmt}( + troveManagerBeaconProxy, + user, + vars.maxFeePercentage, + vars.addCollAmt, + 0, /* collWithdrawalAmt */ + vars.withdrawDebtAmt, + true, /* debtIncrease */ + vars.upperHint, + vars.lowerHint, + referrer + ); + + // state after + uint256 totalPointsAfter = referralManager.getTotalPoints(); + uint256 referrerPointsAfter = referralManager.getPoints(referrer); + + // check state + assertEq(totalPointsAfter, totalPointsBefore + vars.withdrawDebtAmt); + assertEq(referrerPointsAfter, referrerPointsBefore + vars.withdrawDebtAmt); + + vm.stopPrank(); + } + + function testAdjustTroveReferral_WithdrawCollAndWithdrawDebt() public { + LocalVars memory vars; + // pre open trove + vars.collAmt = 1e18; // price defined in `TestConfig.roundData` + vars.debtAmt = 10000e18; // 10000 USD + vars.maxFeePercentage = 0.05e18; // 5% + TroveBase.openTrove( + borrowerOperationsProxy, + sortedTrovesBeaconProxy, + troveManagerBeaconProxy, + hintHelpers, + GAS_COMPENSATION, + user, + user, + weth, + vars.collAmt, + vars.debtAmt, + vars.maxFeePercentage + ); + + vars.withdrawCollAmt = 0.5e18; + vars.withdrawDebtAmt = 2000e18; + vars.totalCollAmt = vars.collAmt - vars.withdrawCollAmt; + vars.totalNetDebtAmt = vars.debtAmt + vars.withdrawDebtAmt; + + vm.startPrank(user); + + // state before + uint256 totalPointsBefore = referralManager.getTotalPoints(); + uint256 referrerPointsBefore = referralManager.getPoints(referrer); + + /* check events emitted correctly in tx */ + // check ExecuteReferral event + vm.expectEmit(true, true, true, true, address(referralManager)); + emit ExecuteReferral(user, referrer, vars.withdrawDebtAmt); + + // calc hint + (vars.upperHint, vars.lowerHint) = HintLib.getHint( + hintHelpers, + sortedTrovesBeaconProxy, + troveManagerBeaconProxy, + vars.totalCollAmt, + vars.totalNetDebtAmt, + GAS_COMPENSATION + ); + + // tx execution + satoshiBORouter.adjustTrove( + troveManagerBeaconProxy, + user, + vars.maxFeePercentage, + 0, /* collAdditionAmt */ + vars.withdrawCollAmt, + vars.withdrawDebtAmt, + true, /* debtIncrease */ + vars.upperHint, + vars.lowerHint, + referrer + ); + + // state after + uint256 totalPointsAfter = referralManager.getTotalPoints(); + uint256 referrerPointsAfter = referralManager.getPoints(referrer); + + // check state + assertEq(totalPointsAfter, totalPointsBefore + vars.withdrawDebtAmt); + assertEq(referrerPointsAfter, referrerPointsBefore + vars.withdrawDebtAmt); + + vm.stopPrank(); + } + + function testFailExecuteReferral() public { + vm.startPrank(user); + + referralManager.executeReferral(user, user, 10000); + + vm.stopPrank(); + } + + function testOpenTroveNotInReferralTime() public { + // reset referral start and end time + vm.startPrank(DEPLOYER); + referralManager.setStartTimestamp(0); + referralManager.setEndTimestamp(0); + vm.stopPrank(); + + LocalVars memory vars; + // open trove params + vars.collAmt = 1e18; // price defined in `TestConfig.roundData` + vars.debtAmt = 10000e18; // 10000 USD + + vm.startPrank(user); + deal(user, 1e18); + + // state before + uint256 totalPointsBefore = referralManager.getTotalPoints(); + uint256 referrerPointsBefore = referralManager.getPoints(referrer); + + // calc hint + (vars.upperHint, vars.lowerHint) = HintLib.getHint( + hintHelpers, sortedTrovesBeaconProxy, troveManagerBeaconProxy, vars.collAmt, vars.debtAmt, GAS_COMPENSATION + ); + // tx execution + satoshiBORouter.openTrove{value: vars.collAmt}( + troveManagerBeaconProxy, + user, + 0.05e18, /* vars.maxFeePercentage 5% */ + vars.collAmt, + vars.debtAmt, + vars.upperHint, + vars.lowerHint, + referrer + ); + + // state after + uint256 totalPointsAfter = referralManager.getTotalPoints(); + uint256 referrerPointsAfter = referralManager.getPoints(referrer); + + // check state + assertEq(totalPointsAfter, totalPointsBefore); + assertEq(referrerPointsAfter, referrerPointsBefore); + + vm.stopPrank(); + } +} diff --git a/test/SatoshiBORouter.t.sol b/test/SatoshiBORouter.t.sol index d25e267..7e6023d 100644 --- a/test/SatoshiBORouter.t.sol +++ b/test/SatoshiBORouter.t.sol @@ -9,6 +9,7 @@ import {ITroveManager, TroveManagerOperation} from "../src/interfaces/core/ITrov import {ISatoshiBORouter} from "../src/helpers/interfaces/ISatoshiBORouter.sol"; import {IMultiCollateralHintHelpers} from "../src/helpers/interfaces/IMultiCollateralHintHelpers.sol"; import {IWETH} from "../src/helpers/interfaces/IWETH.sol"; +import {IReferralManager} from "../src/helpers/interfaces/IReferralManager.sol"; import {SatoshiMath} from "../src/dependencies/SatoshiMath.sol"; import {DeployBase, LocalVars} from "./utils/DeployBase.t.sol"; import {HintLib} from "./utils/HintLib.sol"; @@ -24,13 +25,16 @@ contract SatoshiBORouterTest is Test, DeployBase, TroveBase, TestConfig, Events IWETH weth; IMultiCollateralHintHelpers hintHelpers; ISatoshiBORouter satoshiBORouter; + IReferralManager referralManager; address user; + address referrer; function setUp() public override { super.setUp(); // testing user user = vm.addr(1); + referrer = vm.addr(2); // use WETH as collateral weth = IWETH(_deployWETH(DEPLOYER)); @@ -43,7 +47,13 @@ contract SatoshiBORouterTest is Test, DeployBase, TroveBase, TestConfig, Events // deploy helper contracts hintHelpers = IMultiCollateralHintHelpers(_deployHintHelpers(DEPLOYER)); - satoshiBORouter = ISatoshiBORouter(_deploySatoshiBORouter(DEPLOYER, weth)); + + uint64 nonce = vm.getNonce(DEPLOYER); + address cpSatoshiBORouterAddr = vm.computeCreateAddress(DEPLOYER, nonce); + address cpReferralManagerAddr = vm.computeCreateAddress(DEPLOYER, ++nonce); + satoshiBORouter = + ISatoshiBORouter(_deploySatoshiBORouter(DEPLOYER, IReferralManager(cpReferralManagerAddr), weth)); + referralManager = IReferralManager(_deployReferralManager(DEPLOYER, ISatoshiBORouter(cpSatoshiBORouterAddr))); // user set delegate approval for satoshiBORouter vm.startPrank(user); @@ -102,7 +112,8 @@ contract SatoshiBORouterTest is Test, DeployBase, TroveBase, TestConfig, Events vars.collAmt, vars.debtAmt, vars.upperHint, - vars.lowerHint + vars.lowerHint, + referrer ); // state after @@ -338,7 +349,13 @@ contract SatoshiBORouterTest is Test, DeployBase, TroveBase, TestConfig, Events ); // tx execution satoshiBORouter.withdrawDebt( - troveManagerBeaconProxy, user, vars.maxFeePercentage, vars.withdrawDebtAmt, vars.upperHint, vars.lowerHint + troveManagerBeaconProxy, + user, + vars.maxFeePercentage, + vars.withdrawDebtAmt, + vars.upperHint, + vars.lowerHint, + referrer ); // state after @@ -508,7 +525,8 @@ contract SatoshiBORouterTest is Test, DeployBase, TroveBase, TestConfig, Events vars.repayDebtAmt, false, /* debtIncrease */ vars.upperHint, - vars.lowerHint + vars.lowerHint, + referrer ); // state after @@ -602,7 +620,8 @@ contract SatoshiBORouterTest is Test, DeployBase, TroveBase, TestConfig, Events vars.withdrawDebtAmt, true, /* debtIncrease */ vars.upperHint, - vars.lowerHint + vars.lowerHint, + referrer ); // state after @@ -699,7 +718,8 @@ contract SatoshiBORouterTest is Test, DeployBase, TroveBase, TestConfig, Events vars.repayDebtAmt, false, /* debtIncrease */ vars.upperHint, - vars.lowerHint + vars.lowerHint, + referrer ); // state after @@ -792,7 +812,8 @@ contract SatoshiBORouterTest is Test, DeployBase, TroveBase, TestConfig, Events vars.withdrawDebtAmt, true, /* debtIncrease */ vars.upperHint, - vars.lowerHint + vars.lowerHint, + referrer ); // state after diff --git a/test/UpgradeContract.t.sol b/test/UpgradeContract.t.sol index 35c2725..7a00a97 100644 --- a/test/UpgradeContract.t.sol +++ b/test/UpgradeContract.t.sol @@ -11,11 +11,10 @@ import {IFactory} from "../src/interfaces/core/IFactory.sol"; import {IBorrowerOperations} from "../src/interfaces/core/IBorrowerOperations.sol"; import {BorrowerOperations} from "../src/core/BorrowerOperations.sol"; import {DeployBase} from "./utils/DeployBase.t.sol"; -import {DEPLOYER, OWNER, GAS_COMPENSATION, BO_MIN_NET_DEBT,TestConfig} from "./TestConfig.sol"; +import {DEPLOYER, OWNER, GAS_COMPENSATION, BO_MIN_NET_DEBT, TestConfig} from "./TestConfig.sol"; import {Events} from "./utils/Events.sol"; contract UpgradteContractTest is Test, DeployBase, TestConfig, Events { - ISortedTroves sortedTrovesBeaconProxy; ITroveManager troveManagerBeaconProxy; address user1; @@ -41,11 +40,13 @@ contract UpgradteContractTest is Test, DeployBase, TestConfig, Events { // upgrade to new borrower operations implementation BorrowerOperations borrowerOperationsProxy = BorrowerOperations(address(borrowerOperationsProxy)); borrowerOperationsProxy.upgradeTo(address(newBorrowerOperationsImpl)); - bytes32 s = vm.load(address(borrowerOperationsProxy), BorrowerOperations(address(newBorrowerOperationsImpl)).proxiableUUID()); + bytes32 s = vm.load( + address(borrowerOperationsProxy), BorrowerOperations(address(newBorrowerOperationsImpl)).proxiableUUID() + ); // `0x000...address` << 96 -> `0xaddress000...000` s <<= 96; assertEq(s, bytes32(bytes20(address(newBorrowerOperationsImpl)))); - + vm.stopPrank(); } } diff --git a/test/utils/DeployBase.t.sol b/test/utils/DeployBase.t.sol index 5da0e21..4f849e9 100644 --- a/test/utils/DeployBase.t.sol +++ b/test/utils/DeployBase.t.sol @@ -24,6 +24,7 @@ import {Factory, DeploymentParams} from "../../src/core/Factory.sol"; import {RoundData, OracleMock} from "../../src/mocks/OracleMock.sol"; import {PriceFeedChainlink} from "../../src/dependencies/priceFeed/PriceFeedChainlink.sol"; import {AggregatorV3Interface} from "../../src/interfaces/dependencies/priceFeed/AggregatorV3Interface.sol"; +import {ReferralManager} from "../../src/helpers/ReferralManager.sol"; import {IWETH} from "../../src/helpers/interfaces/IWETH.sol"; import {ISortedTroves} from "../../src/interfaces/core/ISortedTroves.sol"; import {IPriceFeedAggregator} from "../../src/interfaces/core/IPriceFeedAggregator.sol"; @@ -36,6 +37,8 @@ import {ISatoshiCore} from "../../src/interfaces/core/ISatoshiCore.sol"; import {IDebtToken} from "../../src/interfaces/core/IDebtToken.sol"; import {IFactory} from "../../src/interfaces/core/IFactory.sol"; import {IPriceFeed} from "../../src/interfaces/dependencies/IPriceFeed.sol"; +import {ISatoshiBORouter} from "../../src/helpers/interfaces/ISatoshiBORouter.sol"; +import {IReferralManager} from "../../src/helpers/interfaces/IReferralManager.sol"; import { DEPLOYER, OWNER, @@ -473,17 +476,37 @@ abstract contract DeployBase is Test { return wethAddr; } - function _deploySatoshiBORouter(address deployer, IWETH weth) internal returns (address) { + function _deploySatoshiBORouter(address deployer, IReferralManager referralManager, IWETH weth) + internal + returns (address) + { vm.startPrank(deployer); assert(debtToken != IDebtToken(address(0))); // check if debt token contract is deployed assert(borrowerOperationsProxy != IBorrowerOperations(address(0))); // check if borrower operations proxy contract is deployed + assert(referralManager != IReferralManager(address(0))); // check if referral manager contract is not zero address assert(weth != IWETH(address(0))); // check if WETH contract is deployed - address satoshiBORouterAddr = address(new SatoshiBORouter(debtToken, borrowerOperationsProxy, weth)); + address satoshiBORouterAddr = + address(new SatoshiBORouter(debtToken, borrowerOperationsProxy, referralManager, weth)); vm.stopPrank(); return satoshiBORouterAddr; } + function _deployReferralManager(address deployer, ISatoshiBORouter satoshiBORouter) internal returns (address) { + vm.startPrank(deployer); + assert(satoshiBORouter != ISatoshiBORouter(address(0))); // check if satoshiBORouter contract is not zero address + uint256 startTimestamp = block.timestamp; + uint256 endTimestamp = startTimestamp + 30 days; + address referralManagerAddr = address(new ReferralManager(satoshiBORouter, startTimestamp, endTimestamp)); + assert(IReferralManager(referralManagerAddr).satoshiBORouter() == satoshiBORouter); + assert(IReferralManager(referralManagerAddr).startTimestamp() == startTimestamp); + assert(IReferralManager(referralManagerAddr).endTimestamp() == endTimestamp); + assert(IReferralManager(referralManagerAddr).getTotalPoints() == 0); + vm.stopPrank(); + + return referralManagerAddr; + } + /* ============ Deploy DebtTokenTester Contracts ============ */ function _deployDebtTokenTester() internal { vm.startPrank(DEPLOYER); diff --git a/test/utils/Events.sol b/test/utils/Events.sol index ae17200..3129089 100644 --- a/test/utils/Events.sol +++ b/test/utils/Events.sol @@ -18,4 +18,7 @@ abstract contract Events { address indexed _borrower, uint256 _debt, uint256 _coll, uint256 _stake, TroveManagerOperation _operation ); event TotalStakesUpdated(uint256 _newTotalStakes); + + // ReferralManager + event ExecuteReferral(address indexed borrower, address indexed referrer, uint256 points); } From ea956cd33fa51ef7afd9ea383cc1c6d44014877d Mon Sep 17 00:00:00 2001 From: Ray Huang Date: Sat, 2 Mar 2024 20:45:43 +0800 Subject: [PATCH 2/5] add checker --- src/helpers/ReferralManager.sol | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/helpers/ReferralManager.sol b/src/helpers/ReferralManager.sol index 612045a..733e24b 100644 --- a/src/helpers/ReferralManager.sol +++ b/src/helpers/ReferralManager.sol @@ -20,6 +20,8 @@ contract ReferralManager is IReferralManager, Ownable { error InvalidTimestamp(uint256 timestamp); error InvalidZeroAddress(); + error InvalidSelfReferral(); + error InvalidZeroPoints(); error Unauthorized(address _caller); modifier onlySatoshiBORouter() { @@ -37,6 +39,9 @@ contract ReferralManager is IReferralManager, Ownable { function executeReferral(address _borrower, address _referrer, uint256 _points) external onlySatoshiBORouter { if (_isReferralActive()) { + if(_borrower == _referrer) revert InvalidSelfReferral(); + if(_points == 0) revert InvalidZeroPoints(); + _addPoint(_referrer, _points); _addTotalPoints(_points); From 0af6b6ba3d05193b635e6ed695c7f09fea81f0f7 Mon Sep 17 00:00:00 2001 From: Ray Huang Date: Sun, 3 Mar 2024 00:04:14 +0800 Subject: [PATCH 3/5] update referral --- src/helpers/ReferralManager.sol | 36 ++++++++++---- src/helpers/SatoshiBORouter.sol | 10 ++-- src/helpers/interfaces/IReferralManager.sol | 2 + src/helpers/interfaces/ISatoshiBORouter.sol | 6 +-- test/ReferralManager.t.sol | 53 +++++++-------------- test/SatoshiBORouter.t.sol | 20 ++------ 6 files changed, 61 insertions(+), 66 deletions(-) diff --git a/src/helpers/ReferralManager.sol b/src/helpers/ReferralManager.sol index 733e24b..2ae8486 100644 --- a/src/helpers/ReferralManager.sol +++ b/src/helpers/ReferralManager.sol @@ -12,6 +12,9 @@ contract ReferralManager is IReferralManager, Ownable { uint256 public endTimestamp; uint256 internal totalPoints; + // borrower => referrer + mapping(address => address) internal referrers; + // referrer => points mapping(address => uint256) internal points; event SetStartTimestamp(uint256 _startTimestamp); @@ -21,7 +24,6 @@ contract ReferralManager is IReferralManager, Ownable { error InvalidTimestamp(uint256 timestamp); error InvalidZeroAddress(); error InvalidSelfReferral(); - error InvalidZeroPoints(); error Unauthorized(address _caller); modifier onlySatoshiBORouter() { @@ -39,13 +41,23 @@ contract ReferralManager is IReferralManager, Ownable { function executeReferral(address _borrower, address _referrer, uint256 _points) external onlySatoshiBORouter { if (_isReferralActive()) { - if(_borrower == _referrer) revert InvalidSelfReferral(); - if(_points == 0) revert InvalidZeroPoints(); - - _addPoint(_referrer, _points); - _addTotalPoints(_points); - - emit ExecuteReferral(_borrower, _referrer, _points); + if (_borrower == _referrer) revert InvalidSelfReferral(); + + // have referrer + if (_referrer != address(0)) { + address currentReferrer = referrers[_borrower]; + if (currentReferrer == address(0)) { + _setReferrer(_borrower, _referrer); + } else { + // use existing referrer + _referrer = currentReferrer; + } + + _addPoint(_referrer, _points); + _addTotalPoints(_points); + + emit ExecuteReferral(_borrower, _referrer, _points); + } } else { // do nothing } @@ -63,6 +75,10 @@ contract ReferralManager is IReferralManager, Ownable { totalPoints += _points; } + function _setReferrer(address _account, address _referrer) internal { + referrers[_account] = _referrer; + } + function _isReferralActive() internal view returns (bool) { uint256 _timestamp = block.timestamp; return _timestamp >= startTimestamp && _timestamp <= endTimestamp; @@ -84,6 +100,10 @@ contract ReferralManager is IReferralManager, Ownable { return points[_account]; } + function getReferrer(address _account) external view returns (address) { + return referrers[_account]; + } + function setStartTimestamp(uint256 _startTimestamp) external onlyOwner { if (_startTimestamp > endTimestamp) revert InvalidTimestamp(_startTimestamp); startTimestamp = _startTimestamp; diff --git a/src/helpers/SatoshiBORouter.sol b/src/helpers/SatoshiBORouter.sol index d926458..85bc87d 100644 --- a/src/helpers/SatoshiBORouter.sol +++ b/src/helpers/SatoshiBORouter.sol @@ -103,8 +103,7 @@ contract SatoshiBORouter is ISatoshiBORouter { uint256 _maxFeePercentage, uint256 _debtAmount, address _upperHint, - address _lowerHint, - address _referrer + address _lowerHint ) external { uint256 debtTokenBalanceBefore = debtToken.balanceOf(address(this)); borrowerOperationsProxy.withdrawDebt( @@ -113,6 +112,8 @@ contract SatoshiBORouter is ISatoshiBORouter { uint256 debtTokenBalanceAfter = debtToken.balanceOf(address(this)); uint256 userDebtAmount = debtTokenBalanceAfter - debtTokenBalanceBefore; require(userDebtAmount == _debtAmount, "SatoshiBORouter: Debt amount mismatch"); + + address _referrer = referralManager.getReferrer(account); _afterWithdrawDebt(account, _referrer, userDebtAmount); } @@ -137,8 +138,7 @@ contract SatoshiBORouter is ISatoshiBORouter { uint256 _debtChange, bool _isDebtIncrease, address _upperHint, - address _lowerHint, - address _referrer + address _lowerHint ) external payable { if (_collDeposit != 0 && _collWithdrawal != 0) revert CannotWithdrawAndAddColl(); @@ -174,6 +174,8 @@ contract SatoshiBORouter is ISatoshiBORouter { if (_isDebtIncrease) { uint256 userDebtAmount = debtTokenBalanceAfter - debtTokenBalanceBefore; require(userDebtAmount == _debtChange, "SatoshiBORouter: Debt amount mismatch"); + + address _referrer = referralManager.getReferrer(account); _afterWithdrawDebt(account, _referrer, userDebtAmount); } } diff --git a/src/helpers/interfaces/IReferralManager.sol b/src/helpers/interfaces/IReferralManager.sol index 60638ef..af0662b 100644 --- a/src/helpers/interfaces/IReferralManager.sol +++ b/src/helpers/interfaces/IReferralManager.sol @@ -20,6 +20,8 @@ interface IReferralManager { function getPoints(address _account) external view returns (uint256); + function getReferrer(address _account) external view returns (address); + function setStartTimestamp(uint256 _startTimestamp) external; function setEndTimestamp(uint256 _endTimestamp) external; diff --git a/src/helpers/interfaces/ISatoshiBORouter.sol b/src/helpers/interfaces/ISatoshiBORouter.sol index cc7d2b2..baff6de 100644 --- a/src/helpers/interfaces/ISatoshiBORouter.sol +++ b/src/helpers/interfaces/ISatoshiBORouter.sol @@ -55,8 +55,7 @@ interface ISatoshiBORouter { uint256 _maxFeePercentage, uint256 _debtAmount, address _upperHint, - address _lowerHint, - address _referrer + address _lowerHint ) external; function repayDebt( @@ -76,8 +75,7 @@ interface ISatoshiBORouter { uint256 _debtChange, bool _isDebtIncrease, address _upperHint, - address _lowerHint, - address _referrer + address _lowerHint ) external payable; function closeTrove(ITroveManager troveManager, address account) external; diff --git a/test/ReferralManager.t.sol b/test/ReferralManager.t.sol index 0275a49..6efaf92 100644 --- a/test/ReferralManager.t.sol +++ b/test/ReferralManager.t.sol @@ -112,18 +112,15 @@ contract ReferralManagerTest is Test, DeployBase, TroveBase, TestConfig, Events vars.collAmt = 1e18; // price defined in `TestConfig.roundData` vars.debtAmt = 10000e18; // 10000 USD vars.maxFeePercentage = 0.05e18; // 5% - TroveBase.openTrove( - borrowerOperationsProxy, - sortedTrovesBeaconProxy, + satoshiBORouter.openTrove{value: vars.collAmt}( troveManagerBeaconProxy, - hintHelpers, - GAS_COMPENSATION, user, - user, - weth, + 0.05e18, /* vars.maxFeePercentage 5% */ vars.collAmt, vars.debtAmt, - vars.maxFeePercentage + vars.upperHint, + vars.lowerHint, + referrer ); vars.withdrawDebtAmt = 10000e18; @@ -151,13 +148,7 @@ contract ReferralManagerTest is Test, DeployBase, TroveBase, TestConfig, Events ); // tx execution satoshiBORouter.withdrawDebt( - troveManagerBeaconProxy, - user, - vars.maxFeePercentage, - vars.withdrawDebtAmt, - vars.upperHint, - vars.lowerHint, - referrer + troveManagerBeaconProxy, user, vars.maxFeePercentage, vars.withdrawDebtAmt, vars.upperHint, vars.lowerHint ); // state after @@ -177,18 +168,15 @@ contract ReferralManagerTest is Test, DeployBase, TroveBase, TestConfig, Events vars.collAmt = 1e18; // price defined in `TestConfig.roundData` vars.debtAmt = 10000e18; // 10000 USD vars.maxFeePercentage = 0.05e18; // 5% - TroveBase.openTrove( - borrowerOperationsProxy, - sortedTrovesBeaconProxy, + satoshiBORouter.openTrove{value: vars.collAmt}( troveManagerBeaconProxy, - hintHelpers, - GAS_COMPENSATION, user, - user, - weth, + 0.05e18, /* vars.maxFeePercentage 5% */ vars.collAmt, vars.debtAmt, - vars.maxFeePercentage + vars.upperHint, + vars.lowerHint, + referrer ); vars.addCollAmt = 0.5e18; @@ -228,8 +216,7 @@ contract ReferralManagerTest is Test, DeployBase, TroveBase, TestConfig, Events vars.withdrawDebtAmt, true, /* debtIncrease */ vars.upperHint, - vars.lowerHint, - referrer + vars.lowerHint ); // state after @@ -249,18 +236,15 @@ contract ReferralManagerTest is Test, DeployBase, TroveBase, TestConfig, Events vars.collAmt = 1e18; // price defined in `TestConfig.roundData` vars.debtAmt = 10000e18; // 10000 USD vars.maxFeePercentage = 0.05e18; // 5% - TroveBase.openTrove( - borrowerOperationsProxy, - sortedTrovesBeaconProxy, + satoshiBORouter.openTrove{value: vars.collAmt}( troveManagerBeaconProxy, - hintHelpers, - GAS_COMPENSATION, - user, user, - weth, + 0.05e18, /* vars.maxFeePercentage 5% */ vars.collAmt, vars.debtAmt, - vars.maxFeePercentage + vars.upperHint, + vars.lowerHint, + referrer ); vars.withdrawCollAmt = 0.5e18; @@ -299,8 +283,7 @@ contract ReferralManagerTest is Test, DeployBase, TroveBase, TestConfig, Events vars.withdrawDebtAmt, true, /* debtIncrease */ vars.upperHint, - vars.lowerHint, - referrer + vars.lowerHint ); // state after diff --git a/test/SatoshiBORouter.t.sol b/test/SatoshiBORouter.t.sol index 7e6023d..4f03f11 100644 --- a/test/SatoshiBORouter.t.sol +++ b/test/SatoshiBORouter.t.sol @@ -349,13 +349,7 @@ contract SatoshiBORouterTest is Test, DeployBase, TroveBase, TestConfig, Events ); // tx execution satoshiBORouter.withdrawDebt( - troveManagerBeaconProxy, - user, - vars.maxFeePercentage, - vars.withdrawDebtAmt, - vars.upperHint, - vars.lowerHint, - referrer + troveManagerBeaconProxy, user, vars.maxFeePercentage, vars.withdrawDebtAmt, vars.upperHint, vars.lowerHint ); // state after @@ -525,8 +519,7 @@ contract SatoshiBORouterTest is Test, DeployBase, TroveBase, TestConfig, Events vars.repayDebtAmt, false, /* debtIncrease */ vars.upperHint, - vars.lowerHint, - referrer + vars.lowerHint ); // state after @@ -620,8 +613,7 @@ contract SatoshiBORouterTest is Test, DeployBase, TroveBase, TestConfig, Events vars.withdrawDebtAmt, true, /* debtIncrease */ vars.upperHint, - vars.lowerHint, - referrer + vars.lowerHint ); // state after @@ -718,8 +710,7 @@ contract SatoshiBORouterTest is Test, DeployBase, TroveBase, TestConfig, Events vars.repayDebtAmt, false, /* debtIncrease */ vars.upperHint, - vars.lowerHint, - referrer + vars.lowerHint ); // state after @@ -812,8 +803,7 @@ contract SatoshiBORouterTest is Test, DeployBase, TroveBase, TestConfig, Events vars.withdrawDebtAmt, true, /* debtIncrease */ vars.upperHint, - vars.lowerHint, - referrer + vars.lowerHint ); // state after From 57b7b4e38af47b2515d17e9c0f54dc1c73457c10 Mon Sep 17 00:00:00 2001 From: Ray Huang Date: Sun, 3 Mar 2024 00:27:29 +0800 Subject: [PATCH 4/5] executeReferral --- src/helpers/ReferralManager.sol | 35 +++++++++++++++------------------ 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/src/helpers/ReferralManager.sol b/src/helpers/ReferralManager.sol index 2ae8486..549c395 100644 --- a/src/helpers/ReferralManager.sol +++ b/src/helpers/ReferralManager.sol @@ -40,27 +40,24 @@ contract ReferralManager is IReferralManager, Ownable { } function executeReferral(address _borrower, address _referrer, uint256 _points) external onlySatoshiBORouter { - if (_isReferralActive()) { - if (_borrower == _referrer) revert InvalidSelfReferral(); - - // have referrer - if (_referrer != address(0)) { - address currentReferrer = referrers[_borrower]; - if (currentReferrer == address(0)) { - _setReferrer(_borrower, _referrer); - } else { - // use existing referrer - _referrer = currentReferrer; - } - - _addPoint(_referrer, _points); - _addTotalPoints(_points); - - emit ExecuteReferral(_borrower, _referrer, _points); - } + // only execute referral if it's active + if (!_isReferralActive()) return; + // no referrer + if (_referrer == address(0)) return; + if (_borrower == _referrer) revert InvalidSelfReferral(); + + address currentReferrer = referrers[_borrower]; + if (currentReferrer == address(0)) { + _setReferrer(_borrower, _referrer); } else { - // do nothing + // use existing referrer + _referrer = currentReferrer; } + + _addPoint(_referrer, _points); + _addTotalPoints(_points); + + emit ExecuteReferral(_borrower, _referrer, _points); } function isReferralActive() external view returns (bool) { From fbb677889cd751b042a8839c38a3697d60835a8f Mon Sep 17 00:00:00 2001 From: Ray Huang Date: Sun, 3 Mar 2024 20:39:52 +0800 Subject: [PATCH 5/5] add batch referrers --- src/helpers/ReferralManager.sol | 8 ++++++++ src/helpers/interfaces/IReferralManager.sol | 2 ++ 2 files changed, 10 insertions(+) diff --git a/src/helpers/ReferralManager.sol b/src/helpers/ReferralManager.sol index 549c395..f89544e 100644 --- a/src/helpers/ReferralManager.sol +++ b/src/helpers/ReferralManager.sol @@ -97,6 +97,14 @@ contract ReferralManager is IReferralManager, Ownable { return points[_account]; } + function getBatchReferrers(address[] calldata _accounts) external view returns (address[] memory) { + address[] memory _referrers = new address[](_accounts.length); + for (uint256 i = 0; i < _accounts.length; i++) { + _referrers[i] = referrers[_accounts[i]]; + } + return _referrers; + } + function getReferrer(address _account) external view returns (address) { return referrers[_account]; } diff --git a/src/helpers/interfaces/IReferralManager.sol b/src/helpers/interfaces/IReferralManager.sol index af0662b..44aa1b9 100644 --- a/src/helpers/interfaces/IReferralManager.sol +++ b/src/helpers/interfaces/IReferralManager.sol @@ -20,6 +20,8 @@ interface IReferralManager { function getPoints(address _account) external view returns (uint256); + function getBatchReferrers(address[] calldata _accounts) external view returns (address[] memory); + function getReferrer(address _account) external view returns (address); function setStartTimestamp(uint256 _startTimestamp) external;