@@ -4,7 +4,7 @@ import hre from "hardhat";
44import { deploy , getContractAt , setupTests } from "./helpers" ;
55import { addressToBytes32 , ZERO_ADDRESS , DEFAULT_ADMIN_ROLE } from "../scripts/common" ;
66import {
7- TestUSDC , TestWETH , PaxosOracle , StashDex , TestLiquidityPool , TransparentUpgradeableProxy ,
7+ TestUSDC , TestWETH , PaxosOracle , StashDex , TestLiquidityPool , TransparentUpgradeableProxy , MockBorrowSwap ,
88} from "../typechain-types" ;
99
1010describe ( "StashDex" , function ( ) {
@@ -42,11 +42,13 @@ describe("StashDex", function () {
4242 const CONFIG_ROLE = await stashDex . CONFIG_ROLE ( ) ;
4343 const PAUSER_ROLE = await stashDex . PAUSER_ROLE ( ) ;
4444 const FORWARD_ROLE = await stashDex . FORWARD_ROLE ( ) ;
45+ const USER_ROLE = await stashDex . USER_ROLE ( ) ;
46+ await stashDex . connect ( admin ) . grantRole ( USER_ROLE , user ) ;
4547
4648 return {
4749 deployer, admin, configAdmin, pauser, forwarder, user, user2, processor, receiver,
4850 tokenA, tokenB, tokenC, usdcRef, oracle, pool, stashDex, stashDexImpl,
49- CONFIG_ROLE , PAUSER_ROLE , FORWARD_ROLE , USDC , WETH ,
51+ CONFIG_ROLE , PAUSER_ROLE , FORWARD_ROLE , USER_ROLE , USDC , WETH ,
5052 } ;
5153 } ;
5254
@@ -346,6 +348,60 @@ describe("StashDex", function () {
346348 } ) ;
347349 } ) ;
348350
351+ describe ( "USER_ROLE" , function ( ) {
352+ it ( "swap reverts Unauthorized when tx.origin lacks USER_ROLE" , async function ( ) {
353+ const { stashDex, user2, tokenA, tokenB, USDC } = await loadFixture ( deployAll ) ;
354+ await expect ( stashDex . connect ( user2 ) . swap ( tokenA , tokenB , 10_000n * USDC , 9_997n * USDC , user2 ) )
355+ . to . be . revertedWithCustomError ( stashDex , "Unauthorized" ) ;
356+ } ) ;
357+
358+ it ( "exchange(5 params) reverts Unauthorized when tx.origin lacks USER_ROLE" , async function ( ) {
359+ const { stashDex, user2, tokenA, tokenB, USDC } = await loadFixture ( deployAll ) ;
360+ await expect ( stashDex . connect ( user2 ) [ "exchange(uint256,uint256,uint256,uint256,address)" ] (
361+ BigInt ( await tokenA . getAddress ( ) ) , BigInt ( await tokenB . getAddress ( ) ) , 10_000n * USDC , 9_997n * USDC , user2
362+ ) ) . to . be . revertedWithCustomError ( stashDex , "Unauthorized" ) ;
363+ } ) ;
364+
365+ it ( "exchange(4 params) reverts Unauthorized when tx.origin lacks USER_ROLE" , async function ( ) {
366+ const { stashDex, user2, tokenA, tokenB, USDC } = await loadFixture ( deployAll ) ;
367+ await expect ( stashDex . connect ( user2 ) [ "exchange(uint256,uint256,uint256,uint256)" ] (
368+ BigInt ( await tokenA . getAddress ( ) ) , BigInt ( await tokenB . getAddress ( ) ) , 10_000n * USDC , 9_997n * USDC
369+ ) ) . to . be . revertedWithCustomError ( stashDex , "Unauthorized" ) ;
370+ } ) ;
371+
372+ it ( "tx.origin is checked: user (USER_ROLE) succeeds via mock, user2 reverts via mock" , async function ( ) {
373+ const { stashDex, configAdmin, user, user2, tokenA, tokenB, pool, processor, USDC } = await loadFixture ( deployAll ) ;
374+ const mock = ( await deploy ( "MockBorrowSwap" , user ) ) as MockBorrowSwap ;
375+
376+ await stashDex . connect ( configAdmin ) . setPool ( tokenB , pool ) ;
377+ await stashDex . connect ( configAdmin ) . setRoute ( { tokenIn : tokenA , tokenOut : tokenB , feeBps : 3 , processor} ) ;
378+ await tokenA . mint ( mock , 10_000n * USDC ) ;
379+ await tokenB . mint ( pool , 9_997n * USDC ) ;
380+
381+ // mock (msg.sender) approves stashDex to pull its tokenA during swap
382+ const approveData = await tokenA . approve . populateTransaction ( stashDex , 10_000n * USDC ) ;
383+ await mock . connect ( user ) . callBorrow ( tokenA , approveData . data ) ;
384+
385+ const swapData = await stashDex . swap . populateTransaction (
386+ tokenA . target , tokenB . target , 10_000n * USDC , 9_997n * USDC , user . address ,
387+ ) ;
388+
389+ // user2 has no USER_ROLE → tx.origin check fails → Unauthorized
390+ await expect ( mock . connect ( user2 ) . callBorrowBubbleRevert ( stashDex , swapData . data ) )
391+ . to . be . revertedWithCustomError ( stashDex , "Unauthorized" ) ;
392+
393+ // user has USER_ROLE → tx.origin check passes → succeeds
394+ const tx = await mock . connect ( user ) . callBorrow ( stashDex , swapData . data ) ;
395+ await expect ( tx ) . to . emit ( stashDex , "Swapped" )
396+ . withArgs ( tokenA . target , tokenB . target , 10_000n * USDC , 9_997n * USDC , user . address ) ;
397+
398+ expect ( await tokenA . balanceOf ( mock ) ) . to . equal ( 0n ) ;
399+ expect ( await tokenA . balanceOf ( processor ) ) . to . equal ( 10_000n * USDC ) ;
400+ expect ( await tokenB . balanceOf ( user ) ) . to . equal ( 9_997n * USDC ) ;
401+ expect ( await tokenB . balanceOf ( pool ) ) . to . equal ( 0n ) ;
402+ } ) ;
403+ } ) ;
404+
349405 describe ( "swap" , function ( ) {
350406 it ( "reverts RouteNotAllowed when no route is configured" , async function ( ) {
351407 const { stashDex, user, tokenA, tokenB, USDC } = await loadFixture ( deployAll ) ;
0 commit comments