mirror of
https://github.com/luxfi/uma.git
synced 2026-10-30 15:14:44 +00:00
* Only set non-default Request storage variables saves 10k gas for requestPrice * Save 1000 gas by exiting _getRequest early * Initial tests * Update OptimisticOracleLite.sol * Add request params to events * Remove customizeRequest, add bond and default liveness to requestPrice params * Add requestAndProposePriceFor * Add interface * Change name * Update OptimisticOracle.sol * Update packages/core/contracts/oracle/interfaces/SkinnyOptimisticOracleInterface.sol Co-authored-by: Chris Maree <[email protected]> * temp * Add callbacks * Update SkinnyOptimisticOracle.sol * Tests pass with Skinny OO * Add test cases about when a relay is both disputed and re-relayed, should not be able to settle twice * fix(insured-bridge): Instant relayer should only receive refund if they sped up valid relay * Update BridgePool.js * Fix clients * Update BridgePool.sol * Update BridgePool.sol * Update BridgePool.sol * Update BridgePool.sol * Update packages/core/contracts/insured-bridge/BridgePool.sol Co-authored-by: Chris Maree <[email protected]> * Update packages/insured-bridge-relayer/test/Relayer.ts Co-authored-by: Chris Maree <[email protected]> * Update packages/insured-bridge-relayer/src/Relayer.ts Co-authored-by: Chris Maree <[email protected]> * Update InsuredBridgeL1Client.ts * Merge master * Fix tests * Update FindContractVersion.ts * Update Arbitrum_CrossDomainEnabled.sol * Compiled contracts * WIP Signed-off-by: Nick Pai <[email protected]> * Clarify test cases * map relay ancillary data as bytes32 instead of bytes * Tests * Remove Disputed state * Include Relay state in hash * Store relay state in hash ``` ···························|·······························|·············|·············|·················|···············|·············· | BridgePool · relayDeposit · 332175 · 375995 · 362612 · 29 · - │ ···························|·······························|·············|·············|·················|···············|·············· | BridgePool · removeLiquidity · 70712 · 102702 · 85114 · 5 · - │ ···························|·······························|·············|·············|·················|···············|·············· | BridgePool · settleRelay · 174741 · 235642 · 212400 · 18 · - │ ···························|·······························|·············|·············|·················|···············|·············· | Bri ``` * Update BridgePool.sol * Update BridgePool.sol * Update BridgePool.sol * Add relay ancillary data hash to Relay event * Fix relay ancillary data hash * Clients done * Add priceRequestTime to relayData * L1Client stores priceRequestTime and relayId * Change Relayer to work with latest clients * Refactor tests to be more functional and have clearer default vars * Update BridgePool.sol * Update Relayer.ts * Update * Update packages/core/contracts/insured-bridge/BridgePool.sol Co-authored-by: Chris Maree <[email protected]> * Update BridgePool.sol * Update BridgePool.sol * Update packages/core/contracts/insured-bridge/BridgePool.sol Co-authored-by: Chris Maree <[email protected]> * lint * Update HardhatConfig.ts * feat(core): Bump compiler to 0.8.9 and silence trivial compile warnings - removed variable name shadowing by function params - removed names for unused function params - changed function state mutability levels * Update BalancerMock.sol * fix tests Co-authored-by: Chris Maree <[email protected]>
304 lines
16 KiB
Solidity
304 lines
16 KiB
Solidity
// SPDX-License-Identifier: AGPL-3.0-only
|
|
pragma solidity ^0.8.0;
|
|
|
|
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
|
|
import "@uniswap/lib/contracts/libraries/Babylonian.sol";
|
|
import "@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol";
|
|
|
|
import "@uniswap/lib/contracts/libraries/TransferHelper.sol";
|
|
import "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router01.sol";
|
|
|
|
import "../../common/implementation/FixedPoint.sol";
|
|
|
|
import "../../financial-templates/long-short-pair/LongShortPair.sol";
|
|
|
|
/**
|
|
* @title LspUniswapV2Broker
|
|
* @notice Helper contract to facilitate batched LSP and UniswapV2 transactions, including Mint+Sell and Mint+LP.
|
|
*/
|
|
contract LspUniswapV2Broker {
|
|
using FixedPoint for FixedPoint.Signed;
|
|
using FixedPoint for FixedPoint.Unsigned;
|
|
|
|
/**
|
|
* @notice Mint long and short tokens and deposit them all into a UniV2 Pool.
|
|
* @dev The caller of this method needs to approve `amountCollateral` collateral to be spent by this contract.
|
|
* @param callingAsEOA If True, caller has balance of collateral and expects to receive back all LP tokens + dust.
|
|
* @param longShortPair LSP contract address to mint position on.
|
|
* @param router Contract to call to exchange long and short tokens.
|
|
* @param amountCollateral Amount of collateral to deposit and mint long and short tokens against.
|
|
* @param minLpTokens Minimum number of LP tokens to mint
|
|
* @param deadline Unix timestamp that will force the transaction to revert if minded after this time.
|
|
*/
|
|
function atomicMintAddLiquidity(
|
|
bool callingAsEOA,
|
|
LongShortPair longShortPair,
|
|
IUniswapV2Router01 router,
|
|
uint256 amountCollateral,
|
|
uint256 minLpTokens,
|
|
uint256 deadline
|
|
) public {
|
|
require(address(longShortPair) != address(0), "Invalid long short pair");
|
|
require(address(router) != address(0), "Invalid router");
|
|
require(amountCollateral != 0, "Collateral to mint with");
|
|
|
|
IERC20 collateralToken = IERC20(longShortPair.collateralToken());
|
|
IERC20 longToken = IERC20(longShortPair.longToken());
|
|
IERC20 shortToken = IERC20(longShortPair.shortToken());
|
|
|
|
// 0) Pull collateral from caller if necessary and approve LSP to spend it.
|
|
if (callingAsEOA)
|
|
TransferHelper.safeTransferFrom(address(collateralToken), msg.sender, address(this), amountCollateral);
|
|
|
|
// 1) Approve collateral to be spent by the LSP from this contract at size of amountCollateral.
|
|
TransferHelper.safeApprove(address(collateralToken), address(longShortPair), amountCollateral);
|
|
|
|
// 2) Deposit collateral into LSP and mint long and short tokens.
|
|
uint256 tokensToCreate =
|
|
FixedPoint.fromUnscaledUint(amountCollateral).div(longShortPair.collateralPerPair()).rawValue;
|
|
longShortPair.create(tokensToCreate);
|
|
|
|
require(
|
|
longToken.balanceOf(address(this)) == shortToken.balanceOf(address(this)) &&
|
|
longToken.balanceOf(address(this)) == tokensToCreate,
|
|
"Create invariant failed"
|
|
);
|
|
|
|
{
|
|
// 3) Calculate if we need to sell the long token for the short or the short token for the long. Calculate
|
|
// the total trade size to bring the balance of long/short in this contract to equal the pool ratio after
|
|
// the trade. Note this computation must consider how the resultant pool ratios is impacted by the trade.
|
|
bool aToB;
|
|
uint256 tradeSize;
|
|
(uint256 reserveA, uint256 reserveB) =
|
|
getReserves(router.factory(), address(longToken), address(shortToken));
|
|
(aToB, tradeSize) = computeSwapToMintAtPoolRatio(
|
|
FixedPoint.Signed(int256(tokensToCreate)),
|
|
FixedPoint.Signed(int256(reserveA)),
|
|
FixedPoint.Signed(int256(reserveB))
|
|
);
|
|
address[] memory path = new address[](2);
|
|
|
|
// 3.a) If the trade is token a to b then we are selling long tokens for short tokens. In this case, we
|
|
// know the exact output number of tokens we want (short tokens) and use the router's
|
|
// swapTokensForExactTokens with the exact output number of tokens specified.
|
|
if (aToB && tradeSize > 0) {
|
|
path[0] = address(longToken);
|
|
path[1] = address(shortToken);
|
|
TransferHelper.safeApprove(address(longToken), address(router), tradeSize);
|
|
router.swapTokensForExactTokens(tradeSize, type(uint256).max, path, address(this), deadline);
|
|
}
|
|
if (!aToB && tradeSize > 0) {
|
|
// Else, if the trade is b to a then we are selling short tokens for long tokens. In this case, we
|
|
// know the exact input number of tokens that we need to sell (short tokens) and can use the router's
|
|
// swapExactTokensForTokens with the exact input specified.
|
|
path[0] = address(shortToken);
|
|
path[1] = address(longToken);
|
|
TransferHelper.safeApprove(address(shortToken), address(router), tradeSize);
|
|
router.swapExactTokensForTokens(tradeSize, 0, path, address(this), deadline);
|
|
}
|
|
}
|
|
|
|
// 4) Add liquidity to the pool via the router. Approve both long and short tokens with the full account balance
|
|
// in this contract (i.e the amount of tokens minted +- that traded in step 2.).
|
|
TransferHelper.safeApprove(address(longToken), address(router), longToken.balanceOf(address(this)));
|
|
TransferHelper.safeApprove(address(shortToken), address(router), shortToken.balanceOf(address(this)));
|
|
router.addLiquidity(
|
|
address(longToken),
|
|
address(shortToken),
|
|
longToken.balanceOf(address(this)),
|
|
shortToken.balanceOf(address(this)),
|
|
0,
|
|
0,
|
|
address(this),
|
|
deadline
|
|
);
|
|
|
|
// 5) Validate that at least the minimum number of tokens have been minted. If not, revert.
|
|
{
|
|
IERC20 lPToken = IERC20(address(pairFor(router.factory(), address(longToken), address(shortToken))));
|
|
require(lPToken.balanceOf(address(this)) > minLpTokens, "Failed to mint min LP tokens");
|
|
|
|
// 5) Finally, if the caller is an EOA, send back the LP tokens from minting + any dust that was left over.
|
|
// The dust is in the long and short tokens and will be left over as a result of small rounding errors in
|
|
// the calculation on how many long/short should be bought/sold to meet the pool ratio before minting.
|
|
if (callingAsEOA) {
|
|
// Send the LP tokens back to the minter.
|
|
TransferHelper.safeTransfer(address(lPToken), msg.sender, lPToken.balanceOf(address(this)));
|
|
|
|
// Send any dust left over back to the minter that may have happened due to small rounding errors in
|
|
// the computation methods within computeSwapToMintAtPoolRatio.
|
|
TransferHelper.safeTransfer(address(longToken), msg.sender, longToken.balanceOf(address(this)));
|
|
TransferHelper.safeTransfer(address(shortToken), msg.sender, shortToken.balanceOf(address(this)));
|
|
}
|
|
}
|
|
}
|
|
|
|
// For a given mint size (m) and pool balances (ra & rb), compute the trade size such that the resulting ratio
|
|
// of tokens, considering the mint and trade,is equal to the pool ratio after the trade. This is computed using the
|
|
// following logic: Assume m tokens are minted in equal proportion of long and short. This contract will therefore
|
|
// hold t_l=m+△l long tokens and t_s=m-△s after a trade of s for l tokens. The ratio t_l/t_s must equal the pool
|
|
// ratio after the trade. The pool ratio, considering the trade can be expressed as (R_a-△a)(R_b+△bλ)=k for a
|
|
// trade of △a for △b and a swap fee of λ=(1-swapFee). Using this, with a bit of algebra and simplification, we can
|
|
// solve for the resultant ratio to be (m+△a)/(m-△b)=(R_a-△a)/(R_b+△b). i.e the ratio of mint+trade must equal the
|
|
// ratio of token A and B in the pool after the trade has concluded. We also know that △a can be solved for as
|
|
// △a=(△bλR_a)/(R_b+△bλ) by manipulating a known uniswap equation. Using this equation and the expression of ratios
|
|
// we can solve simultaneously for △b. I.e how many short tokens do we need to sell such that the ratio of long and
|
|
// short after the trade equal the pool ratios. Numerically, this works out to this form:
|
|
// △b=(sqrt(R_b(R_a+m)(R_a(R_b(λ^2+2λ+1)+4λm)+R_b(λ^2-2λ+1)m))+R_b(-λ-1)(R_a+m))/(2λ(R_a+m)). For how this was solved
|
|
// from the algebraic solution above see https://bit.ly/3jn7AF6 on wolfram alpha showing the derivation.
|
|
function computeSwapToMintAtPoolRatio(
|
|
FixedPoint.Signed memory m,
|
|
FixedPoint.Signed memory ra,
|
|
FixedPoint.Signed memory rb
|
|
) private pure returns (bool, uint256) {
|
|
FixedPoint.Signed memory numerator1 =
|
|
sqrt(
|
|
rb.mul(ra.add(m)).mul(
|
|
ra
|
|
.mul(rb.mul(lambda2().add(num(2).mul(lambda())).add(num(1))).add(num(4).mul(lambda().mul(m))))
|
|
.add(rb.mul(lambda2().sub(num(2).mul(lambda())).add(num(1))).mul(m))
|
|
)
|
|
);
|
|
|
|
FixedPoint.Signed memory numerator2 = rb.mul(ra.add(m)).mul(num(-1).sub(lambda()));
|
|
|
|
FixedPoint.Signed memory numerator = numerator1.mul(1e9).add(numerator2);
|
|
|
|
FixedPoint.Signed memory denominator = num(2).mul(lambda()).mul(ra.add(m));
|
|
|
|
FixedPoint.Signed memory tradeSize = numerator.div(denominator);
|
|
|
|
// If the trade size is negative then we are traversing the equation backwards. In this case the contract must
|
|
// swap between swapTokensForExactTokens and swapExactTokensForTokens methods as we are always solving for △b.
|
|
// In other words, we are always solving for how many short tokens are needed to be bought/sold. If the number
|
|
// is positive then we are buying them. if it is negative then we are selling them. In the case we are selling
|
|
// them (negative) then we swap the polarity and apply the swap fee to the trade.
|
|
bool aToB = tradeSize.isLessThan(0);
|
|
return (aToB, uint256(aToB ? applySwapFee(tradeSize.mul(-1)).rawValue : tradeSize.rawValue));
|
|
}
|
|
|
|
// Syntactical sugar to calculate square root of a number.
|
|
function sqrt(FixedPoint.Signed memory _num) private pure returns (FixedPoint.Signed memory) {
|
|
return FixedPoint.Signed(int256(Babylonian.sqrt(uint256(_num.rawValue))));
|
|
}
|
|
|
|
// Syntactical sugar to convert a int256 to a Fixedpoint.Signed.
|
|
function num(int256 _num) private pure returns (FixedPoint.Signed memory) {
|
|
return FixedPoint.fromUnscaledInt(_num);
|
|
}
|
|
|
|
// Takes an input fixedPoint.Signed num and returns the num scaled by 0.997. 0.3% swap fee as in uniswap.
|
|
function applySwapFee(FixedPoint.Signed memory _num) private pure returns (FixedPoint.Signed memory) {
|
|
return _num.mul(997).div(1000);
|
|
}
|
|
|
|
// 1 with 0.3% fees applied.
|
|
function lambda() private pure returns (FixedPoint.Signed memory) {
|
|
return applySwapFee(FixedPoint.fromUnscaledInt(1));
|
|
}
|
|
|
|
// 1 with 0.3% fees applied twice.
|
|
function lambda2() private pure returns (FixedPoint.Signed memory) {
|
|
return applySwapFee(lambda());
|
|
}
|
|
|
|
/**
|
|
* @notice Mint long and short tokens and convert all of one side into the other.
|
|
* @dev The caller of this method needs to approve `amountCollateral` collateral to be spent by this contract.
|
|
* @param callingAsEOA If True, caller has balance of collateral and expects to receive back all long/short tokens.
|
|
* @param sellLong If True, converts all long tokens into short, else the opposite.
|
|
* @param longShortPair LSP contract address to mint position on.
|
|
* @param router Contract to call to exchange long and short tokens.
|
|
* @param amountCollateral Amount of collateral to deposit and mint long and short tokens against.
|
|
* @param swapPath `Router.swapExactTokensForTokens` param: path with which to swap token to sell for the other.
|
|
* @param deadline `Router.swapExactTokensForTokens` param: time before transaction must be mined.
|
|
*/
|
|
function atomicMintSellOneSide(
|
|
bool callingAsEOA,
|
|
bool sellLong,
|
|
LongShortPair longShortPair,
|
|
IUniswapV2Router01 router, /* TODO: Should we allow `router` to be any exchange, such as a Matcha multihop? */
|
|
uint256 amountCollateral,
|
|
address[] memory swapPath,
|
|
uint256 deadline
|
|
) public {
|
|
require(address(longShortPair) != address(0), "Invalid long short pair");
|
|
require(address(router) != address(0), "Invalid router");
|
|
require(amountCollateral != 0, "Collateral to mint with");
|
|
|
|
IERC20 collateralToken = IERC20(longShortPair.collateralToken());
|
|
|
|
// 0) Pull collateral from caller if necessary and approve LSP to spend it.
|
|
if (callingAsEOA)
|
|
TransferHelper.safeTransferFrom(address(collateralToken), msg.sender, address(this), amountCollateral);
|
|
|
|
TransferHelper.safeApprove(address(collateralToken), address(longShortPair), amountCollateral);
|
|
|
|
// 1) Deposit collateral into LSP and mint long and short tokens.
|
|
uint256 tokensToCreate =
|
|
FixedPoint.fromUnscaledUint(amountCollateral).div(longShortPair.collateralPerPair()).rawValue;
|
|
longShortPair.create(tokensToCreate);
|
|
|
|
// 2) Determine which token we are selling and convert it all into the other.
|
|
IERC20 soldToken = IERC20(sellLong ? longShortPair.shortToken() : longShortPair.longToken());
|
|
TransferHelper.safeApprove(address(soldToken), address(router), soldToken.balanceOf(address(this)));
|
|
require(swapPath[0] == address(soldToken), "Sold token != 0th swapPath");
|
|
router.swapExactTokensForTokens(
|
|
soldToken.balanceOf(address(this)), // sell all of the sold tokens held by the contract.
|
|
0,
|
|
swapPath,
|
|
address(this),
|
|
deadline
|
|
);
|
|
|
|
// 3) Send tokens back to caller if necessary.
|
|
if (callingAsEOA) {
|
|
IERC20 purchasedToken = IERC20(!sellLong ? longShortPair.shortToken() : longShortPair.longToken());
|
|
TransferHelper.safeTransfer(address(purchasedToken), msg.sender, purchasedToken.balanceOf(address(this)));
|
|
}
|
|
}
|
|
|
|
function getReserves(
|
|
address factory,
|
|
address tokenA,
|
|
address tokenB
|
|
) public view returns (uint256 reserveA, uint256 reserveB) {
|
|
(address token0, ) = sortTokens(tokenA, tokenB);
|
|
(uint256 reserve0, uint256 reserve1, ) = IUniswapV2Pair(pairFor(factory, tokenA, tokenB)).getReserves();
|
|
(reserveA, reserveB) = tokenA == token0 ? (reserve0, reserve1) : (reserve1, reserve0);
|
|
}
|
|
|
|
function sortTokens(address tokenA, address tokenB) internal pure returns (address token0, address token1) {
|
|
require(tokenA != tokenB, "UniswapV2Library: IDENTICAL_ADDRESSES");
|
|
(token0, token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
|
|
require(token0 != address(0), "UniswapV2Library: ZERO_ADDRESS");
|
|
}
|
|
|
|
// calculates the CREATE2 address for a pair without making any external calls
|
|
function pairFor(
|
|
address factory,
|
|
address tokenA,
|
|
address tokenB
|
|
) internal pure returns (IUniswapV2Pair pair) {
|
|
(address token0, address token1) = sortTokens(tokenA, tokenB);
|
|
pair = IUniswapV2Pair(
|
|
address(
|
|
uint160(
|
|
uint256(
|
|
keccak256(
|
|
abi.encodePacked(
|
|
hex"ff",
|
|
factory,
|
|
keccak256(abi.encodePacked(token0, token1)),
|
|
hex"96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f" // init code hash
|
|
)
|
|
)
|
|
)
|
|
)
|
|
)
|
|
);
|
|
}
|
|
}
|