Skip to content

Commit f027617

Browse files
authored
Merge pull request #263 from sprintertech/feat/stashdex-origin-gating
Add basic tx.origin gating to StashDex
2 parents e1ec9cb + 4020824 commit f027617

5 files changed

Lines changed: 85 additions & 3 deletions

File tree

contracts/StashDex.sol

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ contract StashDex is AccessControlUpgradeable {
2222
bytes32 public constant CONFIG_ROLE = "CONFIG_ROLE";
2323
bytes32 public constant PAUSER_ROLE = "PAUSER_ROLE";
2424
bytes32 public constant FORWARD_ROLE = "FORWARD_ROLE";
25+
bytes32 public constant USER_ROLE = "USER_ROLE";
2526

2627
uint256 private constant BPS = 10_000;
2728

@@ -72,6 +73,7 @@ contract StashDex is AccessControlUpgradeable {
7273
error PoolNotConfigured();
7374
error EnforcedPause();
7475
error ExpectedPause();
76+
error Unauthorized();
7577

7678
modifier whenNotPaused() {
7779
require(!_getStorage().paused, EnforcedPause());
@@ -137,6 +139,9 @@ contract StashDex is AccessControlUpgradeable {
137139
uint256 amountOut,
138140
address recipient
139141
) public whenNotPaused() {
142+
// Checking the role on tx.origin instead of _msgSender() because StashDex is called through another contract.
143+
/* solhint-disable avoid-tx-origin */
144+
require(hasRole(USER_ROLE, tx.origin), Unauthorized());
140145
StashDexStorage storage $ = _getStorage();
141146
RouteConfig memory route = $.routes[tokenIn][tokenOut];
142147
require(route.allowed, RouteNotAllowed());

coverage-baseline.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"lines": "99.71",
33
"functions": "99.69",
4-
"branches": "93.68",
4+
"branches": "93.69",
55
"statements": "99.71"
66
}

deployments/deploy-ethereum-stage.log

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,3 +460,23 @@ Threshold met (1/1). Executing on-chain...
460460
Executed. On-chain TX hash: 0xfffdba1d5234084edcb4ae47945f65fa30ab69bfe6b60e41e1bf582ca0ad797b
461461
0xfffdba1d5234084edcb4ae47945f65fa30ab69bfe6b60e41e1bf582ca0ad797b
462462
StashDex upgraded.
463+
464+
Safe 0xA8eeA59b4A17CE2689E57B4dE9e825FD25705414: verified 0xdeC18ca09f163C841Fc3d44237bfD0EBbC8414dE as owner.
465+
Deployment ID: MVP
466+
Upgrade ID: STASHDEX_ORIGIN_GATING
467+
Upgrading StashDex
468+
Using config for: stage, ETHEREUM
469+
Deployer : 0xdeC18ca09f163C841Fc3d44237bfD0EBbC8414dE
470+
DEPLOYER_ADDRESS: 0xdBD91aD22bE5304e385b7b0A2Cfe91164e416e11
471+
StashStablecoinDex proxy: 0x125Bf891F832c3A1422E24f4Ca7ef314c0858740
472+
Oracle: 0x4666013984fD7a624aa8BFADD0E95456870dADEc
473+
Receiver: 0x697ECA1cae710FA0348e2173900e6C09b180C35b
474+
New StashDex implementation deployed to 0x4eaf3771c7C272e0Ec838E0c01b24E47Cb0E36bf
475+
Sending StashDex upgrade transaction.
476+
Transaction proposed to Safe 0xA8eeA59b4A17CE2689E57B4dE9e825FD25705414. Safe TX hash: 0xbf9b7b577e9f9d4808e62ea67456da7840bcf9303c7c1b71ac730d58b7be30ef
477+
Threshold met (1/1). Executing on-chain...
478+
Executed. On-chain TX hash: 0x7eb0f8ac07936f059b99f383b8e6932517e50e5b1eb911720f9eaf5744801fe1
479+
0x7eb0f8ac07936f059b99f383b8e6932517e50e5b1eb911720f9eaf5744801fe1
480+
StashDex upgraded.
481+
USER_ROLE granted to 0xeEa9b38E8C54B52F7387a46A7C81173065fa5A10
482+
Executed. On-chain TX hash: 0x639ee7cd8d1d5b274ca35df9c9a3b6345cc4c64426a04652f07fa011903e1926

test/StashDex.ts

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import hre from "hardhat";
44
import {deploy, getContractAt, setupTests} from "./helpers";
55
import {addressToBytes32, ZERO_ADDRESS, DEFAULT_ADMIN_ROLE} from "../scripts/common";
66
import {
7-
TestUSDC, TestWETH, PaxosOracle, StashDex, TestLiquidityPool, TransparentUpgradeableProxy,
7+
TestUSDC, TestWETH, PaxosOracle, StashDex, TestLiquidityPool, TransparentUpgradeableProxy, MockBorrowSwap,
88
} from "../typechain-types";
99

1010
describe("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);

test/StashDexProcessor.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ describe("StashDexProcessor", function () {
5050
await usdc.mint(pool, debtAmount);
5151
await tokenA.mint(user, debtAmount);
5252
await tokenA.connect(user).approve(stashDex, debtAmount);
53+
await stashDex.connect(admin).grantRole(await stashDex.USER_ROLE(), user);
5354
await stashDex.connect(user).swap(tokenA, usdc, debtAmount, debtAmount, user);
5455

5556
// Deploy StashDexProcessor proxy

0 commit comments

Comments
 (0)