From 827b2d934df0ce35f13762b2886aa16bc05d1847 Mon Sep 17 00:00:00 2001 From: dgornjakovic Date: Thu, 24 Oct 2024 16:22:23 +0200 Subject: [PATCH 1/5] add extra payout per user --- contracts/core/AMM/SportsAMMV2.sol | 20 +++++++++-- scripts/abi/SportsAMMV2.json | 56 ++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 2 deletions(-) diff --git a/contracts/core/AMM/SportsAMMV2.sol b/contracts/core/AMM/SportsAMMV2.sol index 69960685..79fc132d 100644 --- a/contracts/core/AMM/SportsAMMV2.sol +++ b/contracts/core/AMM/SportsAMMV2.sol @@ -101,6 +101,9 @@ contract SportsAMMV2 is Initializable, ProxyOwned, ProxyPausable, ProxyReentranc // support bonus payouts for some collaterals (e.g. THALES) mapping(address => uint) public addedPayoutPercentagePerCollateral; + // support bonus payouts for some users/contracts (e.g. stakingThalesBettingProxy) + mapping(address => uint) public addedPayoutPercentagePerUser; + // support different SB per collateral, namely THALES as a collateral will be directly burned mapping(address => address) public safeBoxPerCollateral; @@ -367,7 +370,9 @@ contract SportsAMMV2 is Initializable, ProxyOwned, ProxyPausable, ProxyReentranc amountsToBuy = new uint[](numOfMarkets); uint maxSupportedOdds = riskManager.maxSupportedOdds(); - uint addedPayoutPercentage = addedPayoutPercentagePerCollateral[_tradeDataQuoteInternal._collateral]; + uint addedPayoutPercentage = addedPayoutPercentagePerUser[msg.sender] > 0 + ? addedPayoutPercentagePerUser[msg.sender] + : addedPayoutPercentagePerCollateral[_tradeDataQuoteInternal._collateral]; for (uint i = 0; i < numOfMarkets; i++) { ISportsAMMV2.TradeData memory marketTradeData = _tradeData[i]; @@ -455,7 +460,9 @@ contract SportsAMMV2 is Initializable, ProxyOwned, ProxyPausable, ProxyReentranc uint totalQuote; uint payout; uint fees; - uint addedPayoutPercentage = addedPayoutPercentagePerCollateral[_tradeDataInternal._collateral]; + uint addedPayoutPercentage = addedPayoutPercentagePerUser[msg.sender] > 0 + ? addedPayoutPercentagePerUser[msg.sender] + : addedPayoutPercentagePerCollateral[_tradeDataInternal._collateral]; if (!_tradeDataInternal._isLive) { (totalQuote, payout, fees, , ) = _tradeQuote( _tradeData, @@ -805,6 +812,14 @@ contract SportsAMMV2 is Initializable, ProxyOwned, ProxyPausable, ProxyReentranc emit SetAddedPayoutPercentagePerCollateral(_collateral, _addedPayout); } + /// @notice sets additional payout percentage for certain _users + /// @param _user to add extra payout for + /// @param _addedPayout percentage amount for extra payout + function setAddedPayoutPercentagePerUser(address _user, uint _addedPayout) external onlyOwner { + addedPayoutPercentagePerUser[_user] = _addedPayout; + emit SetAddedPayoutPercentagePerUser(_user, _addedPayout); + } + /// @notice sets dedicated SafeBox per collateral /// @param _collateral to set dedicated SafeBox for /// @param _safeBox for the given collateral @@ -863,5 +878,6 @@ contract SportsAMMV2 is Initializable, ProxyOwned, ProxyPausable, ProxyReentranc event SetFreeBetsHolder(address freeBetsHolder); event SetStakingThalesBettingProxy(address _stakingThalesBettingProxy); event SetAddedPayoutPercentagePerCollateral(address _collateral, uint _addedPayout); + event SetAddedPayoutPercentagePerUser(address _user, uint _addedPayout); event SetSafeBoxPerCollateral(address _collateral, address _safeBox); } diff --git a/scripts/abi/SportsAMMV2.json b/scripts/abi/SportsAMMV2.json index 1b659f3b..a5fbcd7a 100644 --- a/scripts/abi/SportsAMMV2.json +++ b/scripts/abi/SportsAMMV2.json @@ -378,6 +378,25 @@ "name": "SetAddedPayoutPercentagePerCollateral", "type": "event" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "_user", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "_addedPayout", + "type": "uint256" + } + ], + "name": "SetAddedPayoutPercentagePerUser", + "type": "event" + }, { "anonymous": false, "inputs": [ @@ -587,6 +606,25 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "addedPayoutPercentagePerUser", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -1006,6 +1044,24 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "address", + "name": "_user", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_addedPayout", + "type": "uint256" + } + ], + "name": "setAddedPayoutPercentagePerUser", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { From 0ea5c2e699e400fbf3774de6ca8296867f6f70e3 Mon Sep 17 00:00:00 2001 From: dgornjakovic Date: Thu, 24 Oct 2024 16:46:19 +0200 Subject: [PATCH 2/5] fixes for live betting and removed msg.sender from quote as it makes little sense to use it --- contracts/core/AMM/SportsAMMV2.sol | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/contracts/core/AMM/SportsAMMV2.sol b/contracts/core/AMM/SportsAMMV2.sol index 79fc132d..b91f4e57 100644 --- a/contracts/core/AMM/SportsAMMV2.sol +++ b/contracts/core/AMM/SportsAMMV2.sol @@ -101,15 +101,15 @@ contract SportsAMMV2 is Initializable, ProxyOwned, ProxyPausable, ProxyReentranc // support bonus payouts for some collaterals (e.g. THALES) mapping(address => uint) public addedPayoutPercentagePerCollateral; - // support bonus payouts for some users/contracts (e.g. stakingThalesBettingProxy) - mapping(address => uint) public addedPayoutPercentagePerUser; - // support different SB per collateral, namely THALES as a collateral will be directly burned mapping(address => address) public safeBoxPerCollateral; // the contract that processes betting with StakedTHALES address public stakingThalesBettingProxy; + // support bonus payouts for some users/contracts (e.g. stakingThalesBettingProxy) + mapping(address => uint) public addedPayoutPercentagePerUser; + struct TradeDataQuoteInternal { uint _buyInAmount; bool _shouldCheckRisks; @@ -370,9 +370,7 @@ contract SportsAMMV2 is Initializable, ProxyOwned, ProxyPausable, ProxyReentranc amountsToBuy = new uint[](numOfMarkets); uint maxSupportedOdds = riskManager.maxSupportedOdds(); - uint addedPayoutPercentage = addedPayoutPercentagePerUser[msg.sender] > 0 - ? addedPayoutPercentagePerUser[msg.sender] - : addedPayoutPercentagePerCollateral[_tradeDataQuoteInternal._collateral]; + uint addedPayoutPercentage = addedPayoutPercentagePerCollateral[_tradeDataQuoteInternal._collateral]; for (uint i = 0; i < numOfMarkets; i++) { ISportsAMMV2.TradeData memory marketTradeData = _tradeData[i]; @@ -460,8 +458,8 @@ contract SportsAMMV2 is Initializable, ProxyOwned, ProxyPausable, ProxyReentranc uint totalQuote; uint payout; uint fees; - uint addedPayoutPercentage = addedPayoutPercentagePerUser[msg.sender] > 0 - ? addedPayoutPercentagePerUser[msg.sender] + uint addedPayoutPercentage = addedPayoutPercentagePerUser[_tradeDataInternal.recipient] > 0 + ? addedPayoutPercentagePerUser[_tradeDataInternal.recipient] : addedPayoutPercentagePerCollateral[_tradeDataInternal._collateral]; if (!_tradeDataInternal._isLive) { (totalQuote, payout, fees, , ) = _tradeQuote( From 5782c508b4aa4328ff0a25b8b20cb8814cd1770f Mon Sep 17 00:00:00 2001 From: dgornjakovic Date: Thu, 24 Oct 2024 16:49:48 +0200 Subject: [PATCH 3/5] typos --- contracts/core/AMM/SportsAMMV2.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/core/AMM/SportsAMMV2.sol b/contracts/core/AMM/SportsAMMV2.sol index b91f4e57..223070b5 100644 --- a/contracts/core/AMM/SportsAMMV2.sol +++ b/contracts/core/AMM/SportsAMMV2.sol @@ -458,8 +458,8 @@ contract SportsAMMV2 is Initializable, ProxyOwned, ProxyPausable, ProxyReentranc uint totalQuote; uint payout; uint fees; - uint addedPayoutPercentage = addedPayoutPercentagePerUser[_tradeDataInternal.recipient] > 0 - ? addedPayoutPercentagePerUser[_tradeDataInternal.recipient] + uint addedPayoutPercentage = addedPayoutPercentagePerUser[_tradeDataInternal._recipient] > 0 + ? addedPayoutPercentagePerUser[_tradeDataInternal._recipient] : addedPayoutPercentagePerCollateral[_tradeDataInternal._collateral]; if (!_tradeDataInternal._isLive) { (totalQuote, payout, fees, , ) = _tradeQuote( From 6347ddf147680dfc0641787e1479e90252dbe62e Mon Sep 17 00:00:00 2001 From: dgornjakovic Date: Thu, 24 Oct 2024 17:26:20 +0200 Subject: [PATCH 4/5] unit tests --- contracts/core/AMM/SportsAMMV2.sol | 2 + test/constants/overtime.js | 4 ++ .../QuotesAndTradesTHALESCollateral.js | 67 +++++++++++++++++++ 3 files changed, 73 insertions(+) diff --git a/contracts/core/AMM/SportsAMMV2.sol b/contracts/core/AMM/SportsAMMV2.sol index 223070b5..1bf093f8 100644 --- a/contracts/core/AMM/SportsAMMV2.sol +++ b/contracts/core/AMM/SportsAMMV2.sol @@ -806,6 +806,7 @@ contract SportsAMMV2 is Initializable, ProxyOwned, ProxyPausable, ProxyReentranc /// @param _collateral to add extra payout for /// @param _addedPayout percentage amount for extra payout function setAddedPayoutPercentagePerCollateral(address _collateral, uint _addedPayout) external onlyOwner { + require(_addedPayout <= 3e16, "Bonus payout can't exceed 3%"); addedPayoutPercentagePerCollateral[_collateral] = _addedPayout; emit SetAddedPayoutPercentagePerCollateral(_collateral, _addedPayout); } @@ -814,6 +815,7 @@ contract SportsAMMV2 is Initializable, ProxyOwned, ProxyPausable, ProxyReentranc /// @param _user to add extra payout for /// @param _addedPayout percentage amount for extra payout function setAddedPayoutPercentagePerUser(address _user, uint _addedPayout) external onlyOwner { + require(_addedPayout <= 3e16, "Bonus payout can't exceed 3%"); addedPayoutPercentagePerUser[_user] = _addedPayout; emit SetAddedPayoutPercentagePerUser(_user, _addedPayout); } diff --git a/test/constants/overtime.js b/test/constants/overtime.js index 72fc0096..24ffcbde 100644 --- a/test/constants/overtime.js +++ b/test/constants/overtime.js @@ -48,6 +48,8 @@ const BUY_IN_AMOUNT = ethers.parseEther('10'); const BUY_IN_AMOUNT_SIX_DECIMALS = Number(10000000); const ETH_BUY_IN_AMOUNT = ethers.parseEther('0.0028571428571429'); const ADDITIONAL_SLIPPAGE = ethers.parseEther('0.02'); +const BONUS_PAYOUT = ethers.parseEther('0.03'); +const BONUS_PAYOUT_OUT_OF_RANGE = ethers.parseEther('0.04'); const DEFAULT_AMOUNT = ethers.parseEther('10000'); const DEFAULT_AMOUNT_SIX_DECIMALS = Number('10000000000'); @@ -92,6 +94,8 @@ module.exports = { BUY_IN_AMOUNT_SIX_DECIMALS, ETH_BUY_IN_AMOUNT, ADDITIONAL_SLIPPAGE, + BONUS_PAYOUT, + BONUS_PAYOUT_OUT_OF_RANGE, DEFAULT_AMOUNT, DEFAULT_AMOUNT_SIX_DECIMALS, ETH_DEFAULT_AMOUNT, diff --git a/test/contracts/Overtime/SportsAMMV2/QuotesAndTradesTHALESCollateral.js b/test/contracts/Overtime/SportsAMMV2/QuotesAndTradesTHALESCollateral.js index 99082975..5d1f33a2 100644 --- a/test/contracts/Overtime/SportsAMMV2/QuotesAndTradesTHALESCollateral.js +++ b/test/contracts/Overtime/SportsAMMV2/QuotesAndTradesTHALESCollateral.js @@ -7,6 +7,8 @@ const { const { BUY_IN_AMOUNT, ADDITIONAL_SLIPPAGE, + BONUS_PAYOUT, + BONUS_PAYOUT_OUT_OF_RANGE, RESULT_TYPE, SPORT_ID_NBA, } = require('../../../constants/overtime'); @@ -112,6 +114,71 @@ describe('SportsAMMV2 Quotes And Trades', () => { expect(safeBoxTHALESBalance).greaterThan(0); }); + it('Should revert if bonus is set too high', async () => { + const firstTraderAddress = await firstTrader.getAddress(); + expect( + sportsAMMV2.setAddedPayoutPercentagePerUser(firstTraderAddress, BONUS_PAYOUT_OUT_OF_RANGE) + ).to.be.revertedWith("Bonus payout can't exceed 3%"); + }); + + it('Should buy a ticket (1 market) in THALES with extra payout for user', async () => { + const firstTraderAddress = await firstTrader.getAddress(); + await sportsAMMV2.setAddedPayoutPercentagePerUser(firstTraderAddress, BONUS_PAYOUT); + + const quote = await sportsAMMV2.tradeQuote( + tradeDataCurrentRound, + BUY_IN_AMOUNT, + ZERO_ADDRESS, + false + ); + + const quoteTHALES = await sportsAMMV2.tradeQuote( + tradeDataCurrentRound, + BUY_IN_AMOUNT, + collateralTHALESAddress, + false + ); + + console.log('Normal quote is: ' + quote.totalQuote); + console.log('THALES quote is: ' + quoteTHALES.totalQuote); + + expect(quoteTHALES.payout).greaterThan(quote.payout); + + await sportsAMMV2 + .connect(firstTrader) + .trade( + tradeDataCurrentRound, + BUY_IN_AMOUNT, + quote.totalQuote, + ADDITIONAL_SLIPPAGE, + ZERO_ADDRESS, + collateralTHALESAddress, + false + ); + + await sportsAMMV2ResultManager.setResultTypesPerMarketTypes([0], [RESULT_TYPE.ExactPosition]); + + await sportsAMMV2ResultManager.setResultsPerMarkets( + [tradeDataCurrentRound[0].gameId], + [tradeDataCurrentRound[0].typeId], + [tradeDataCurrentRound[0].playerId], + [[0]] + ); + + const activeTickets = await sportsAMMV2Manager.getActiveTickets(0, 100); + const ticketAddress = activeTickets[0]; + + const TicketContract = await ethers.getContractFactory('Ticket'); + const userTicket = await TicketContract.attach(ticketAddress); + + const marketData = await userTicket.markets(0); + + console.log('Normal quote is: ' + quote.totalQuote); + console.log('Bonus payour for this user is: ' + marketData.odd); + + expect(quoteTHALES.totalQuote).greaterThan(marketData.odd); + }); + it('Should buy a ticket (1 market) in THALES with Referrer', async () => { const quote = await sportsAMMV2.tradeQuote( tradeDataCurrentRound, From 0906e62d2485d772d20a78ffeeb25b09b1626ee8 Mon Sep 17 00:00:00 2001 From: dgornjakovic Date: Fri, 25 Oct 2024 11:39:29 +0200 Subject: [PATCH 5/5] added live test --- .../QuotesAndTradesTHALESCollateral.js | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/test/contracts/Overtime/SportsAMMV2/QuotesAndTradesTHALESCollateral.js b/test/contracts/Overtime/SportsAMMV2/QuotesAndTradesTHALESCollateral.js index 5d1f33a2..863ce84f 100644 --- a/test/contracts/Overtime/SportsAMMV2/QuotesAndTradesTHALESCollateral.js +++ b/test/contracts/Overtime/SportsAMMV2/QuotesAndTradesTHALESCollateral.js @@ -367,6 +367,60 @@ describe('SportsAMMV2 Quotes And Trades', () => { expect(await userTicket.isLive()).to.eq(true); }); + it('Should buy a live trade with bonus user payout', async () => { + const firstTraderAddress = await firstTrader.getAddress(); + await sportsAMMV2.setAddedPayoutPercentagePerUser(firstTraderAddress, BONUS_PAYOUT); + + const quote = await sportsAMMV2.tradeQuote( + tradeDataCurrentRound, + BUY_IN_AMOUNT, + ZERO_ADDRESS, + false + ); + + const quoteTHALES = await sportsAMMV2.tradeQuote( + tradeDataCurrentRound, + BUY_IN_AMOUNT, + collateralTHALESAddress, + false + ); + + await sportsAMMV2RiskManager.setLiveTradingPerSportAndTypeEnabled(SPORT_ID_NBA, 0, true); + + await liveTradingProcessor.connect(firstTrader).requestLiveTrade({ + _gameId: tradeDataCurrentRound[0].gameId, + _sportId: tradeDataCurrentRound[0].sportId, + _typeId: tradeDataCurrentRound[0].typeId, + _line: tradeDataCurrentRound[0].line, + _position: tradeDataCurrentRound[0].position, + _buyInAmount: BUY_IN_AMOUNT, + _expectedQuote: quote.totalQuote, + _additionalSlippage: ADDITIONAL_SLIPPAGE, + _referrer: ZERO_ADDRESS, + _collateral: collateralTHALESAddress, + }); + + let requestId = await liveTradingProcessor.counterToRequestId(0); + console.log('requestId is ' + requestId); + + await mockChainlinkOracle.fulfillLiveTrade(requestId, true, quote.totalQuote); + + const activeTickets = await sportsAMMV2Manager.getActiveTickets(0, 100); + const ticketAddress = activeTickets[0]; + + const TicketContract = await ethers.getContractFactory('Ticket'); + const userTicket = await TicketContract.attach(ticketAddress); + + const marketData = await userTicket.markets(0); + + console.log('Normal quote is: ' + quote.totalQuote); + console.log('User live quote is: ' + marketData.odd); + + expect(quoteTHALES.totalQuote).greaterThan(marketData.odd); + + expect(await userTicket.isLive()).to.eq(true); + }); + it('Should buy a ticket (1 market) in THALES which gets cancelled', async () => { const quoteTHALES = await sportsAMMV2.tradeQuote( tradeDataCurrentRound,