This implementation allows malicious users to manipulate referral rates and potentially rug referrers of their agreed distributions.
pragma solidity ^0.8.13;
import "forge-std/Test.sol";
import "../src/core/PreMarkets.sol";
import "../src/libraries/GenerateAddress.sol";
import "../src/libraries/Constants.sol";
import "../src/interfaces/ITokenManager.sol";
import "../src/interfaces/ISystemConfig.sol";
import {MockERC20Token} from "./mocks/MockERC20Token.sol";
import {SystemConfig} from "../src/core/SystemConfig.sol";
import {CapitalPool} from "../src/core/CapitalPool.sol";
import {TokenManager} from "../src/core/TokenManager.sol";
import {PreMarktes} from "../src/core/PreMarkets.sol";
import {DeliveryPlace} from "../src/core/DeliveryPlace.sol";
import {TadleFactory} from "../src/factory/TadleFactory.sol";
import {WETH9} from "./mocks/WETH9.sol";
contract IssueTestTemplate is Test {
SystemConfig systemConfig;
CapitalPool capitalPool;
TokenManager tokenManager;
PreMarktes preMarktes;
DeliveryPlace deliveryPlace;
address marketPlace;
string marketPlaceName = "Backpack";
WETH9 weth9;
MockERC20Token mockUSDCToken;
MockERC20Token mockPointToken;
address guardian;
address maker;
address taker1;
address taker2;
address taker3;
uint256 basePlatformFeeRate = 5_000;
uint256 baseReferralRate = 300_000;
bytes4 private constant INITIALIZE_OWNERSHIP_SELECTOR =
bytes4(keccak256(bytes("initializeOwnership(address)")));
function setUp() public {
guardian = makeAddr("guardian");
maker = makeAddr("maker");
taker1 = makeAddr("taker1");
taker2 = makeAddr("taker2");
taker3 = makeAddr("taker3");
vm.label(guardian, "guardian");
vm.label(maker, "maker");
vm.label(taker1, "taker1");
vm.label(taker2, "taker2");
vm.label(taker3, "taker3");
weth9 = new WETH9();
TadleFactory tadleFactory = new TadleFactory(guardian);
mockUSDCToken = new MockERC20Token();
mockPointToken = new MockERC20Token();
SystemConfig systemConfigLogic = new SystemConfig();
CapitalPool capitalPoolLogic = new CapitalPool();
TokenManager tokenManagerLogic = new TokenManager();
PreMarktes preMarktesLogic = new PreMarktes();
DeliveryPlace deliveryPlaceLogic = new DeliveryPlace();
bytes memory deploy_data = abi.encodeWithSelector(
INITIALIZE_OWNERSHIP_SELECTOR,
guardian
);
vm.startPrank(guardian);
address systemConfigProxy = tadleFactory.deployUpgradeableProxy(
1,
address(systemConfigLogic),
bytes(deploy_data)
);
address preMarktesProxy = tadleFactory.deployUpgradeableProxy(
2,
address(preMarktesLogic),
bytes(deploy_data)
);
address deliveryPlaceProxy = tadleFactory.deployUpgradeableProxy(
3,
address(deliveryPlaceLogic),
bytes(deploy_data)
);
address capitalPoolProxy = tadleFactory.deployUpgradeableProxy(
4,
address(capitalPoolLogic),
bytes(deploy_data)
);
address tokenManagerProxy = tadleFactory.deployUpgradeableProxy(
5,
address(tokenManagerLogic),
bytes(deploy_data)
);
vm.label(systemConfigProxy, "systemConfigProxy");
vm.label(preMarktesProxy, "preMarktesProxy");
vm.label(deliveryPlaceProxy, "deliveryPlaceProxy");
vm.label(capitalPoolProxy, "capitalPoolProxy");
vm.label(tokenManagerProxy, "tokenManagerProxy");
vm.stopPrank();
systemConfig = SystemConfig(systemConfigProxy);
capitalPool = CapitalPool(capitalPoolProxy);
tokenManager = TokenManager(tokenManagerProxy);
preMarktes = PreMarktes(preMarktesProxy);
deliveryPlace = DeliveryPlace(deliveryPlaceProxy);
vm.label(address(systemConfig), "systemConfig");
vm.label(address(tokenManager), "tokenManager");
vm.label(address(preMarktes), "preMarktes");
vm.label(address(deliveryPlace), "deliveryPlace");
vm.startPrank(guardian);
systemConfig.initialize(basePlatformFeeRate, baseReferralRate);
tokenManager.initialize(address(weth9));
address[] memory tokenAddressList = new address[](2);
tokenAddressList[0] = address(mockUSDCToken);
tokenAddressList[1] = address(weth9);
tokenManager.updateTokenWhiteListed(tokenAddressList, true);
systemConfig.createMarketPlace(marketPlaceName, false);
vm.stopPrank();
deal(address(mockUSDCToken), maker, 100000000 * 10 ** 18);
deal(address(mockPointToken), maker, 100000000 * 10 ** 18);
deal(maker, 100000000 * 10 ** 18);
deal(address(mockUSDCToken), taker1, 100000000 * 10 ** 18);
deal(address(mockUSDCToken), taker2, 100000000 * 10 ** 18);
deal(address(mockUSDCToken), taker3, 100000000 * 10 ** 18);
deal(address(mockPointToken), taker2, 100000000 * 10 ** 18);
marketPlace = GenerateAddress.generateMarketPlaceAddress("Backpack");
vm.warp(1719826275);
vm.prank(maker);
mockUSDCToken.approve(address(tokenManager), type(uint256).max);
vm.startPrank(taker2);
mockUSDCToken.approve(address(tokenManager), type(uint256).max);
mockPointToken.approve(address(tokenManager), type(uint256).max);
vm.stopPrank();
}
The referralInfoMap is meant to represent the refer info for some specific account. Leading to
profits obtained for referrers. However, those profits will be entirely redirected to the taker
account that is suppose to be mapped to some specific referrer.
*/
function testReferralBalanceFailsUpdate() public {
address referrer = makeAddr("referrer");
vm.label(referrer, "referrer");
uint256 initialReferrerBalance = tokenManager.userTokenBalanceMap(
referrer,
address(mockUSDCToken),
TokenBalanceType.ReferralBonus
);
vm.startPrank(maker);
preMarktes.createOffer(
CreateOfferParams(
marketPlace,
address(mockUSDCToken),
1000,
1e18,
12000,
300,
OfferType.Ask,
OfferSettleType.Turbo
)
);
address offerAddr = GenerateAddress.generateOfferAddress(0);
vm.stopPrank();
vm.startPrank(guardian);
vm.expectRevert(abi.encodeWithSelector(ISystemConfig.InvalidTotalRate.selector, 10000000300000));
systemConfig.updateReferralExtraRateMap(referrer, 10000000000000);
uint256 maxReferrerRate = Constants.REFERRAL_RATE_DECIMAL_SCALER - systemConfig.getBaseReferralRate();
systemConfig.updateReferralExtraRateMap(referrer, maxReferrerRate);
vm.startPrank(taker1);
uint256 _baseReferralRate = systemConfig.getBaseReferralRate();
uint256 referrerRate = _baseReferralRate + maxReferrerRate;
uint256 authorityRate = 0;
systemConfig.updateReferrerInfo(referrer, referrerRate, authorityRate);
mockUSDCToken.approve(address(tokenManager), type(uint256).max);
preMarktes.createTaker(offerAddr, 500);
vm.stopPrank();
vm.prank(guardian);
systemConfig.updateMarket(
"Backpack",
address(mockPointToken),
0.01 * 1e18,
block.timestamp - 1,
3600
);
vm.startPrank(maker);
vm.expectRevert(Rescuable.TransferFailed.selector);
deliveryPlace.settleAskMaker(offerAddr, 500);
mockPointToken.approve(address(tokenManager), 10000 * 10 ** 18);
deliveryPlace.settleAskMaker(offerAddr, 500);
vm.stopPrank();
uint256 finalReferrerBalance = tokenManager.userTokenBalanceMap(
referrer,
address(mockUSDCToken),
TokenBalanceType.ReferralBonus
);
(uint256 expectedReferralIncome, uint256 expectedTakerIncome) = calculateExpectedReferralBonuses(offerAddr, 1000, 1e18, 500);
assertEq(
finalReferrerBalance - initialReferrerBalance,
expectedReferralIncome,
"Referral balance should increase by the expected amount"
);
assertGt(finalReferrerBalance - initialReferrerBalance,0, "Referral balance should increase");
}
function createOffer(address offerer, address tokenAddress, uint256 totalPoints, uint256 tokenAmount, uint256 collateralRate, uint256 tradeTax, OfferType offerType, OfferSettleType settleType) internal returns (address offerAddr) {
vm.startPrank(offerer);
mockUSDCToken.approve(address(tokenManager), type(uint256).max);
preMarktes.createOffer(
CreateOfferParams(
marketPlace,
tokenAddress,
totalPoints,
tokenAmount,
collateralRate,
tradeTax,
offerType,
settleType
)
);
offerAddr = GenerateAddress.generateOfferAddress(0);
vm.stopPrank();
return offerAddr;
}
function createTakerAndGetInitialBalances(address referrer, address offerAddr, uint256 _purchasedPoints) internal returns (uint256 initialReferrerBalance, uint256 initialTakerBalance) {
initialReferrerBalance = tokenManager.userTokenBalanceMap(
referrer,
address(mockUSDCToken),
TokenBalanceType.ReferralBonus
);
initialTakerBalance = tokenManager.userTokenBalanceMap(
taker1,
address(mockUSDCToken),
TokenBalanceType.ReferralBonus
);
vm.startPrank(taker1);
mockUSDCToken.approve(address(tokenManager), type(uint256).max);
uint256 purchasedPoints = _purchasedPoints;
preMarktes.createTaker(offerAddr, purchasedPoints);
vm.stopPrank();
return (initialReferrerBalance, initialTakerBalance);
}
function closeOffer(address offerer, address offerAddr) internal {
vm.prank(offerer);
preMarktes.closeOffer(GenerateAddress.generateStockAddress(0), offerAddr);
}
function abortAskOffer(address offerer, address _stock, address _offer) internal {
vm.prank(offerer);
preMarktes.abortAskOffer(_stock, _offer);
}
function abortBidTaker(address offerer, address _stock, address _offer) internal {
vm.prank(offerer);
preMarktes.abortBidTaker(_stock, _offer);
}
function verifyAccountTypeBalance(address _token, address account, TokenBalanceType accountType, uint256 expectedBalance) internal returns(uint256 balance) {
balance = getAccountTypeBalance(_token, account, accountType);
console2.log("AccountType", uint256(accountType));
assertEq(
balance,
expectedBalance,
"Account should have the correct amount for account type"
);
return balance;
}
function getAccountTypeBalance(address _token, address account, TokenBalanceType accountType) public returns(uint256 balance) {
balance = tokenManager.userTokenBalanceMap(
account,
_token,
accountType
);
return balance;
}
function calculateExpectedReferralBonuses(address offerAddr, uint256 offerPoints, uint256 offerAmount, uint256 purchasedPoints) internal view returns (uint256 expectedReferrerBonus, uint256 expectedTakerBonus) {
OfferInfo memory offerInfo = preMarktes.getOfferInfo(offerAddr);
uint256 depositAmount = purchasedPoints * offerAmount / offerPoints;
uint256 platformFeeRate = systemConfig.getPlatformFeeRate(taker1);
uint256 platformFee = depositAmount * platformFeeRate / Constants.PLATFORM_FEE_DECIMAL_SCALER;
ReferralInfo memory referralInfo = systemConfig.getReferralInfo(taker1);
expectedReferrerBonus = (platformFee * referralInfo.referrerRate) / Constants.REFERRAL_RATE_DECIMAL_SCALER;
expectedTakerBonus = (platformFee * referralInfo.authorityRate) / Constants.REFERRAL_RATE_DECIMAL_SCALER;
return (expectedReferrerBonus, expectedTakerBonus);
}
function calculateExpectedSalesRevenue(address offerAddr, uint256 purchasedPoints) internal view returns (uint256 expectedSalesRevenue) {
OfferInfo memory offerInfo = preMarktes.getOfferInfo(offerAddr);
expectedSalesRevenue = purchasedPoints * offerInfo.amount / offerInfo.points;
return expectedSalesRevenue;
}
function calculateExpectedTaxIncome(address offerAddr, bool isMaker, address _maker, uint256 depositAmount) internal view returns (uint256 expectedTaxIncome) {
uint256 tradeTax = isMaker? preMarktes.getMakerInfo(_maker).eachTradeTax : preMarktes.getOfferInfo(offerAddr).tradeTax;
assertGt(tradeTax, 0, "Trade tax should be greater than 0");
expectedTaxIncome = (depositAmount * tradeTax) / Constants.EACH_TRADE_TAX_DECIMAL_SCALER;
return expectedTaxIncome;
}
}
Manual Review and Testing.
NOTE: This fix can be used in the provided POC directly to confirm that expected referrals are tracked correctly.