Skip to content

Commit

Permalink
Add SwapperCalleePsm (#66)
Browse files Browse the repository at this point in the history
* Add SwapperCalleePsm

* Remove USDT swap tests

* Add tests for USDT PSM

* Add missing empty line

* Rename keg -> pocket

* Remove separate Pocket contract for PsmMock

* Update src/funnels/callees/SwapperCalleePsm.sol

Co-authored-by: sunbreak1211 <[email protected]>

* Update test/mocks/PsmMock.sol

Co-authored-by: sunbreak1211 <[email protected]>

---------

Co-authored-by: telome <>
Co-authored-by: sunbreak1211 <[email protected]>
  • Loading branch information
telome and sunbreak1211 authored Nov 8, 2023
1 parent 7692abe commit 7268b57
Show file tree
Hide file tree
Showing 4 changed files with 273 additions and 0 deletions.
71 changes: 71 additions & 0 deletions src/funnels/callees/SwapperCalleePsm.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// SPDX-FileCopyrightText: © 2023 Dai Foundation <www.daifoundation.org>
// SPDX-License-Identifier: AGPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

pragma solidity ^0.8.16;

interface GemLike {
function approve(address, uint256) external;
function decimals() external view returns (uint8);
}

interface PsmLike {
function sellGemNoFee(address, uint256) external returns (uint256);
function buyGemNoFee(address, uint256) external returns (uint256);
function dai() external returns (address);
function gem() external returns (address);
}

contract SwapperCalleePsm {
mapping (address => uint256) public wards;

address public immutable psm;
address public immutable gem;
uint256 public immutable to18ConversionFactor;

event Rely(address indexed usr);
event Deny(address indexed usr);

constructor(address _psm) {
psm = _psm;
gem = PsmLike(psm).gem();
GemLike(PsmLike(psm).dai()).approve(address(psm), type(uint256).max);
GemLike(gem).approve(address(psm), type(uint256).max);
to18ConversionFactor = 10 ** (18 - GemLike(gem).decimals());

wards[msg.sender] = 1;
emit Rely(msg.sender);
}

modifier auth() {
require(wards[msg.sender] == 1, "SwapperCalleePsm/not-authorized");
_;
}

function rely(address usr) external auth {
wards[usr] = 1;
emit Rely(usr);
}

function deny(address usr) external auth {
wards[usr] = 0;
emit Deny(usr);
}

function swapCallback(address src, address /* dst */, uint256 amt, uint256 /* minOut */, address to, bytes calldata /* data */) external auth {
if (src == gem) PsmLike(psm).sellGemNoFee(to, amt);
else PsmLike(psm).buyGemNoFee (to, amt / to18ConversionFactor);
}
}
42 changes: 42 additions & 0 deletions test/funnels/Swapper.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ pragma solidity ^0.8.16;
import "dss-test/DssTest.sol";
import { Swapper } from "src/funnels/Swapper.sol";
import { SwapperCalleeUniV3 } from "src/funnels/callees/SwapperCalleeUniV3.sol";
import { SwapperCalleePsm } from "src/funnels/callees/SwapperCalleePsm.sol";
import { AllocatorRoles } from "src/AllocatorRoles.sol";
import { AllocatorBuffer } from "src/AllocatorBuffer.sol";
import { PsmMock } from "test/mocks/PsmMock.sol";

interface GemLike {
function balanceOf(address) external view returns (uint256);
Expand Down Expand Up @@ -172,6 +174,46 @@ contract SwapperTest is DssTest {
assertEq(end, initialTime + 7200);
}

function testSwapPsmCallee() public {
PsmMock psm = new PsmMock(DAI, USDC);
SwapperCalleePsm swapperCalleePsm = new SwapperCalleePsm(address(psm));
psm.rely(address(swapperCalleePsm));
swapperCalleePsm.rely(address(swapper));
deal(DAI, address(psm), 1_000 * WAD, true);

uint256 prevSrc = GemLike(USDC).balanceOf(address(buffer));
uint256 prevDst = GemLike(DAI).balanceOf(address(buffer));

vm.expectEmit(true, true, true, true);
emit Swap(FACILITATOR, USDC, DAI, 1_000 * 10**6, 1_000 * WAD);
vm.prank(FACILITATOR); uint256 out = swapper.swap(USDC, DAI, 1_000 * 10**6, 1_000 * WAD, address(swapperCalleePsm), "");

assertEq(out, 1_000 * WAD);
assertEq(GemLike(USDC).balanceOf(address(buffer)), prevSrc - 1_000 * 10**6);
assertEq(GemLike(DAI).balanceOf(address(buffer)), prevDst + 1_000 * WAD);
assertEq(GemLike(DAI).balanceOf(address(swapper)), 0);
assertEq(GemLike(USDC).balanceOf(address(swapper)), 0);
assertEq(GemLike(DAI).balanceOf(address(swapperCalleePsm)), 0);
assertEq(GemLike(USDC).balanceOf(address(swapperCalleePsm)), 0);

vm.warp(uint32(block.timestamp) + 3600);

prevSrc = GemLike(DAI).balanceOf(address(buffer));
prevDst = GemLike(USDC).balanceOf(address(buffer));

vm.expectEmit(true, true, true, false);
emit Swap(FACILITATOR, DAI, USDC, 1_000 * WAD, 1_000 * 10**6);
vm.prank(FACILITATOR); out = swapper.swap(DAI, USDC, 1_000 * WAD, 1_000 * 10**6, address(swapperCalleePsm), "");

assertEq(out, 1_000 * 10**6);
assertEq(GemLike(DAI).balanceOf(address(buffer)), prevSrc - 1_000 * WAD);
assertEq(GemLike(USDC).balanceOf(address(buffer)), prevDst + 1_000 * 10**6);
assertEq(GemLike(DAI).balanceOf(address(swapper)), 0);
assertEq(GemLike(USDC).balanceOf(address(swapper)), 0);
assertEq(GemLike(DAI).balanceOf(address(swapperCalleePsm)), 0);
assertEq(GemLike(USDC).balanceOf(address(swapperCalleePsm)), 0);
}

function testSwapAllAferEra() public {
vm.prank(FACILITATOR); swapper.swap(USDC, DAI, 10_000 * 10**6, 9900 * WAD, address(uniV3Callee), USDC_DAI_PATH);
(, uint64 era,,) = swapper.limits(USDC, DAI);
Expand Down
92 changes: 92 additions & 0 deletions test/funnels/callees/SwapperCalleePsm.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity ^0.8.16;

import "dss-test/DssTest.sol";
import { SwapperCalleePsm } from "src/funnels/callees/SwapperCalleePsm.sol";
import { PsmMock } from "test/mocks/PsmMock.sol";

interface GemLike {
function balanceOf(address) external view returns (uint256);
function transfer(address, uint256) external;
function decimals() external view returns (uint8);
}

contract SwapperCalleePsmTest is DssTest {

PsmMock psm;
PsmMock psmUSDT;
SwapperCalleePsm callee;
SwapperCalleePsm calleeUSDT;

address constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F;
address constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
address constant USDT = 0xdAC17F958D2ee523a2206206994597C13D831ec7;

function setUp() public {
vm.createSelectFork(vm.envString("ETH_RPC_URL"));

psm = new PsmMock(DAI, USDC);
callee = new SwapperCalleePsm(address(psm));
psm.rely(address(callee));
callee.rely(address(this));

psmUSDT = new PsmMock(DAI, USDT);
calleeUSDT = new SwapperCalleePsm(address(psmUSDT));
psmUSDT.rely(address(calleeUSDT));
calleeUSDT.rely(address(this));

deal(DAI, address(this), 1_000_000 * WAD, true);
deal(DAI, address(psm), 1_000_000 * WAD, true);
deal(DAI, address(psmUSDT), 1_000_000 * WAD, true);
deal(USDC, address(this), 1_000_000 * 10**6, true);
deal(USDC, psm.pocket(), 1_000_000 * 10**6, true);
deal(USDT, address(this), 1_000_000 * 10**6, true);
deal(USDT, psmUSDT.pocket(), 1_000_000 * 10**6, true);
}

function testConstructor() public {
SwapperCalleePsm c = new SwapperCalleePsm(address(psm));
assertEq(c.psm(), address(psm));
assertEq(c.gem(), USDC);
assertEq(c.to18ConversionFactor(), 10**12);
assertEq(c.wards(address(this)), 1);
}

function testAuth() public {
checkAuth(address(callee), "SwapperCalleePsm");
}

function testModifiers() public {
bytes4[] memory authedMethods = new bytes4[](1);
authedMethods[0] = callee.swapCallback.selector;

vm.startPrank(address(0xBEEF));
checkModifier(address(callee), "SwapperCalleePsm/not-authorized", authedMethods);
vm.stopPrank();
}

function checkPsmSwap(SwapperCalleePsm callee_, address from, address to) public {
uint256 prevFrom = GemLike(from).balanceOf(address(this));
uint256 prevTo = GemLike(to).balanceOf(address(this));
uint8 fromDecimals = GemLike(from).decimals();
uint8 toDecimals = GemLike(to).decimals();

GemLike(from).transfer(address(callee_), 10_000 * 10**fromDecimals);
callee_.swapCallback(from, to, 10_000 * 10**fromDecimals, 0, address(this), "");

assertEq(GemLike(from).balanceOf(address(this)), prevFrom - 10_000 * 10**fromDecimals);
assertEq(GemLike(to ).balanceOf(address(this)), prevTo + 10_000 * 10**toDecimals );
assertEq(GemLike(from).balanceOf(address(callee_)), 0);
assertEq(GemLike(to ).balanceOf(address(callee_)), 0);
}

function testDaiToGemSwap() public {
checkPsmSwap(callee, DAI, USDC);
checkPsmSwap(calleeUSDT, DAI, USDT);
}

function testGemToDaiSwap() public {
checkPsmSwap(callee, USDC, DAI);
checkPsmSwap(calleeUSDT, DAI, USDT);
}
}
68 changes: 68 additions & 0 deletions test/mocks/PsmMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity ^0.8.16;

interface GemLike {
function approve(address, uint256) external;
function transfer(address, uint256) external;
function transferFrom(address, address, uint256) external;
function decimals() external view returns (uint8);
}

contract PsmMock {
mapping(address => uint256) public wards;

address public immutable dai;
address public immutable gem;
uint256 public immutable to18ConversionFactor;

event Rely(address indexed usr);
event Deny(address indexed usr);
event SellGem(address indexed owner, uint256 value, uint256 fee);
event BuyGem(address indexed owner, uint256 value, uint256 fee);

modifier auth() {
require(wards[msg.sender] == 1, "PsmMock/not-authorized");
_;
}

constructor(address dai_, address gem_) {
dai = dai_;
gem = gem_;
to18ConversionFactor = 10**(18 - GemLike(gem_).decimals());

wards[msg.sender] = 1;
emit Rely(msg.sender);
}

function rely(address usr) external auth {
wards[usr] = 1;
emit Rely(usr);
}

function deny(address usr) external auth {
wards[usr] = 0;
emit Deny(usr);
}

function pocket() external view returns (address) {
return address(this);
}

function sellGemNoFee(address usr, uint256 gemAmt) external auth returns (uint256 daiOutWad) {
daiOutWad = gemAmt * to18ConversionFactor;

GemLike(gem).transferFrom(msg.sender, address(this), gemAmt);
GemLike(dai).transfer(usr, daiOutWad);

emit SellGem(usr, gemAmt, 0);
}

function buyGemNoFee(address usr, uint256 gemAmt) external auth returns (uint256 daiInWad) {
daiInWad = gemAmt * to18ConversionFactor;

GemLike(dai).transferFrom(msg.sender, address(this), daiInWad);
GemLike(gem).transfer(usr, gemAmt);

emit BuyGem(usr, gemAmt, 0);
}
}

0 comments on commit 7268b57

Please sign in to comment.