Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ node_modules/
# coverage
lcov.info
lcov.html
coverage/
90 changes: 90 additions & 0 deletions src/PairLauncher.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

import {IButtonswapFactory} from
"buttonswap-periphery_buttonswap-core/interfaces/IButtonswapFactory/IButtonswapFactory.sol";
import {IButtonswapPair} from "buttonswap-periphery_buttonswap-core/interfaces/IButtonswapPair/IButtonswapPair.sol";
import {TransferHelper} from "./libraries/TransferHelper.sol";

contract PairLauncher {
struct PairData {
address tokenA;
address tokenB;
uint256 amountA;
uint256 amountB;
}

IButtonswapFactory public immutable factory;
address public immutable launcher;
address public immutable originalIsCreationRestrictedSetter;
PairData[] public pairStack;

modifier onlyLauncher() {
if (msg.sender != launcher) {
revert();
}
_;
}

modifier onlyLauncherOrOriginalSetter() {
if (msg.sender != launcher && msg.sender != originalIsCreationRestrictedSetter) {
revert();
}
_;
}

constructor(address _launcher, address _originalIsCreationRestrictedSetter, address _factory) {
launcher = _launcher;
originalIsCreationRestrictedSetter = _originalIsCreationRestrictedSetter;
factory = IButtonswapFactory(_factory);
}

// Only callable by the launcher
function returnPermissions() external onlyLauncherOrOriginalSetter {
factory.setIsCreationRestrictedSetter(originalIsCreationRestrictedSetter);
}

// Only callable by the launcher
function enqueuePair(address tokenA, address tokenB, uint256 amountA, uint256 amountB) external onlyLauncher {
pairStack.push(PairData(tokenA, tokenB, amountA, amountB));
}

function _createTopPair() internal {
// Pop the top of the stack
PairData memory pairData = pairStack[pairStack.length - 1];
pairStack.pop();

address pair = factory.createPair(pairData.tokenA, pairData.tokenB);
address tokenA = pairData.tokenA;
address tokenB = pairData.tokenB;
uint256 amountA = pairData.amountA;
uint256 amountB = pairData.amountB;

TransferHelper.safeTransferFrom(tokenA, msg.sender, address(this), amountA);
TransferHelper.safeApprove(tokenA, pair, amountA);
TransferHelper.safeTransferFrom(tokenB, msg.sender, address(this), amountB);
TransferHelper.safeApprove(tokenB, pair, amountB);

if (tokenA < tokenB) {
IButtonswapPair(pair).mint(amountA, amountB, launcher);
} else {
IButtonswapPair(pair).mint(amountB, amountA, launcher);
}
}

function batchCreate5() external onlyLauncher {
for (uint256 i = 0; i < 5; i++) {
if (pairStack.length == 0) {
return;
}
_createTopPair();
}
}

function destroy() external onlyLauncherOrOriginalSetter {
if (factory.isCreationRestrictedSetter() != originalIsCreationRestrictedSetter) {
revert();
}
selfdestruct(payable(launcher));
}
}
108 changes: 108 additions & 0 deletions test/PairLauncher.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import {Test} from "buttonswap-periphery_forge-std/Test.sol";
import {IButtonswapPair} from "buttonswap-periphery_buttonswap-core/interfaces/IButtonswapPair/IButtonswapPair.sol";
import {ButtonswapFactory} from "buttonswap-periphery_buttonswap-core/ButtonswapFactory.sol";
import {MockRebasingERC20} from "buttonswap-periphery_mock-contracts/MockRebasingERC20.sol";
import {PairLauncher} from "../src/PairLauncher.sol";

contract PairLauncherTest is Test {
uint256 constant BPS = 10_000;

address public feeToSetter;
uint256 public feeToSetterPrivateKey;
address public isCreationRestrictedSetter;
uint256 public isCreationRestrictedSetterPrivateKey;
address public isPausedSetter;
uint256 public isPausedSetterPrivateKey;
address public paramSetter;
uint256 public paramSetterPrivateKey;
address public userA;
uint256 public userAPrivateKey;
MockRebasingERC20 public tokenA;
MockRebasingERC20 public tokenB;
ButtonswapFactory public buttonswapFactory;
PairLauncher public pairLauncher;

function setUp() public {
(feeToSetter, feeToSetterPrivateKey) = makeAddrAndKey("feeToSetter");
(isCreationRestrictedSetter, isCreationRestrictedSetterPrivateKey) =
makeAddrAndKey("isCreationRestrictedSetter");
(isPausedSetter, isPausedSetterPrivateKey) = makeAddrAndKey("isPausedSetter");
(paramSetter, paramSetterPrivateKey) = makeAddrAndKey("paramSetter");
(userA, userAPrivateKey) = makeAddrAndKey("userA");
tokenA = new MockRebasingERC20("TokenA", "TKNA", 18);
tokenB = new MockRebasingERC20("TokenB", "TKNB", 18);
buttonswapFactory = new ButtonswapFactory(
feeToSetter, isCreationRestrictedSetter, isPausedSetter, paramSetter, "LP Token", "LP"
);

vm.prank(isCreationRestrictedSetter);
buttonswapFactory.setIsCreationRestricted(true);

pairLauncher = new PairLauncher(userA, isCreationRestrictedSetter, address(buttonswapFactory));
}

function test_constructor() public {
assertEq(address(pairLauncher.factory()), address(buttonswapFactory));
assertEq(pairLauncher.launcher(), userA);
assertEq(pairLauncher.originalIsCreationRestrictedSetter(), isCreationRestrictedSetter);
}

function test_flow1(bytes32 saltA, bytes32 saltB, uint256 amountA, uint256 amountB) public {
// Re-assigning tokenA and tokenB to fuzz the order of the tokens
tokenA = new MockRebasingERC20{salt: saltA}("Token A", "TKN_A", 18);
tokenB = new MockRebasingERC20{salt: saltB}("Token B", "TKN_B", 18);

vm.prank(userA);
pairLauncher.enqueuePair(address(tokenA), address(tokenB), amountA, amountB);

(address tokenA1, address tokenB1, uint256 amountA1, uint256 amountB1) = pairLauncher.pairStack(0);
assertEq(tokenA1, address(tokenA));
assertEq(tokenB1, address(tokenB));
assertEq(amountA1, amountA);
assertEq(amountB1, amountB);
}

function test_flow2(bytes32 saltA, bytes32 saltB, uint256 amountA, uint256 amountB) public {
// Re-assigning tokenA and tokenB to fuzz the order of the tokens
tokenA = new MockRebasingERC20{salt: saltA}("Token A", "TKN_A", 18);
tokenB = new MockRebasingERC20{salt: saltB}("Token B", "TKN_B", 18);

amountA = bound(amountA, 10000, type(uint112).max);
amountB = bound(amountB, 10000, type(uint112).max);

vm.prank(userA);
pairLauncher.enqueuePair(address(tokenA), address(tokenB), amountA, amountB);

// Minting enough tokens for userA to use
tokenA.mint(userA, amountA);
vm.prank(userA);
tokenA.approve(address(pairLauncher), amountA);
tokenB.mint(userA, amountB);
vm.prank(userA);
tokenB.approve(address(pairLauncher), amountB);

// CreationRestrictedSetter transferring permissions
vm.prank(isCreationRestrictedSetter);
buttonswapFactory.setIsCreationRestrictedSetter(address(pairLauncher));

assertEq(buttonswapFactory.isCreationRestrictedSetter(), address(pairLauncher));

// userA calling batchCreate5
vm.prank(userA);
pairLauncher.batchCreate5();

// Returning permissions
vm.prank(userA);
pairLauncher.returnPermissions();

// Validating that the permissions were returned
assertEq(buttonswapFactory.isCreationRestrictedSetter(), isCreationRestrictedSetter);

// Self-destructing
vm.prank(userA);
pairLauncher.destroy();
}
}