Skip to content
This repository has been archived by the owner on Jul 9, 2021. It is now read-only.

ERC20BridgeSampler: Additional Buy support #2551

Merged
merged 11 commits into from
Apr 21, 2020
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ interface ICurve {
returns (uint256 dy);

/// @dev Get the amount of `fromToken` by buying `buyAmount` of `toToken`
/// This function exists on later versions of Curve (USDC/DAI/USDT)
/// @param i The token index being sold.
/// @param j The token index being bought.
/// @param buyAmount The amount of token being bought.
Expand Down
8 changes: 8 additions & 0 deletions contracts/erc20-bridge-sampler/CHANGELOG.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@
{
"note": "Pass in `DevUtils` address as a constructor parameter",
"pr": 2531
},
{
"note": "Sample `Curve` for buy amounts",
"pr": 2551
},
{
"note": "Added `sampleBuysFromKyberNetwork` ",
"pr": 2551
}
]
},
Expand Down
234 changes: 201 additions & 33 deletions contracts/erc20-bridge-sampler/contracts/src/ERC20BridgeSampler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,33 @@ contract ERC20BridgeSampler is
}
}

/// @dev Sample buy quotes from Kyber.
/// @param takerToken Address of the taker token (what to sell).
/// @param makerToken Address of the maker token (what to buy).
/// @param makerTokenAmounts Maker token buy amount for each sample.
/// @param opts `FakeBuyOptions` specifying target slippage and max iterations.
/// @return takerTokenAmounts Taker amounts sold at each maker token
/// amount.
function sampleBuysFromKyberNetwork(
address takerToken,
address makerToken,
uint256[] memory makerTokenAmounts,
FakeBuyOptions memory opts
)
public
view
returns (uint256[] memory takerTokenAmounts)
{
return _sampleApproximateBuysFromSource(
takerToken,
makerToken,
makerTokenAmounts,
opts,
this.sampleSellsFromKyberNetwork.selector,
address(0) // PLP registry address
);
}

/// @dev Sample sell quotes from Eth2Dai/Oasis.
/// @param takerToken Address of the taker token (what to sell).
/// @param makerToken Address of the maker token (what to buy).
Expand Down Expand Up @@ -443,6 +470,44 @@ contract ERC20BridgeSampler is
}
}

/// @dev Sample buy quotes from Curve.
/// @param curveAddress Address of the Curve contract.
/// @param fromTokenIdx Index of the taker token (what to sell).
/// @param toTokenIdx Index of the maker token (what to buy).
/// @param makerTokenAmounts Maker token buy amount for each sample.
/// @return takerTokenAmounts Taker amounts sold at each maker token
/// amount.
function sampleBuysFromCurve(
address curveAddress,
int128 fromTokenIdx,
int128 toTokenIdx,
uint256[] memory makerTokenAmounts
)
public
view
returns (uint256[] memory takerTokenAmounts)
{
uint256 numSamples = makerTokenAmounts.length;
takerTokenAmounts = new uint256[](numSamples);
for (uint256 i = 0; i < numSamples; i++) {
(bool didSucceed, bytes memory resultData) =
curveAddress.staticcall.gas(CURVE_CALL_GAS)(
abi.encodeWithSelector(
ICurve(0).get_dx_underlying.selector,
fromTokenIdx,
toTokenIdx,
makerTokenAmounts[i]
));
uint256 sellAmount = 0;
if (didSucceed) {
sellAmount = abi.decode(resultData, (uint256));
} else {
break;
}
takerTokenAmounts[i] = sellAmount;
}
}

/// @dev Sample sell quotes from an arbitrary on-chain liquidity provider.
/// @param registryAddress Address of the liquidity provider registry contract.
/// @param takerToken Address of the taker token (what to sell).
Expand Down Expand Up @@ -500,52 +565,28 @@ contract ERC20BridgeSampler is
/// @param takerToken Address of the taker token (what to sell).
/// @param makerToken Address of the maker token (what to buy).
/// @param makerTokenAmounts Maker token buy amount for each sample.
/// @param opts `FakeBuyOptions` specifying target slippage and max iterations.
/// @return takerTokenAmounts Taker amounts sold at each maker token
/// amount.
function sampleBuysFromLiquidityProviderRegistry(
address registryAddress,
address takerToken,
address makerToken,
uint256[] memory makerTokenAmounts
uint256[] memory makerTokenAmounts,
FakeBuyOptions memory opts
)
public
view
returns (uint256[] memory takerTokenAmounts)
{
// Initialize array of taker token amounts.
uint256 numSamples = makerTokenAmounts.length;
takerTokenAmounts = new uint256[](numSamples);

// Query registry for provider address.
address providerAddress = getLiquidityProviderFromRegistry(
registryAddress,
return _sampleApproximateBuysFromSource(
takerToken,
makerToken
makerToken,
makerTokenAmounts,
opts,
this.sampleSellsFromLiquidityProviderRegistry.selector,
registryAddress
);
// If provider doesn't exist, return all zeros.
if (providerAddress == address(0)) {
return takerTokenAmounts;
}

// Otherwise, query liquidity provider for quotes.
for (uint256 i = 0; i < numSamples; i++) {
(bool didSucceed, bytes memory resultData) =
providerAddress.staticcall.gas(DEFAULT_CALL_GAS)(
abi.encodeWithSelector(
ILiquidityProvider(0).getBuyQuote.selector,
takerToken,
makerToken,
makerTokenAmounts[i]
));
uint256 sellAmount = 0;
if (didSucceed) {
sellAmount = abi.decode(resultData, (uint256));
} else {
// Exit early if the amount is too high for the liquidity provider to serve
break;
}
takerTokenAmounts[i] = sellAmount;
}
}

/// @dev Returns the address of a liquidity provider for the given market
Expand Down Expand Up @@ -639,4 +680,131 @@ contract ERC20BridgeSampler is
{
require(makerToken != takerToken, "ERC20BridgeSampler/INVALID_TOKEN_PAIR");
}

function _sampleSellForApproximateBuy(
address takerToken,
address makerToken,
uint256 takerTokenAmount,
bytes4 selector,
address plpRegistryAddress
)
private
view
returns (uint256 makerTokenAmount)
{
bytes memory callData;
uint256[] memory tmpTakerAmounts = new uint256[](1);
tmpTakerAmounts[0] = takerTokenAmount;
if (selector == this.sampleSellsFromKyberNetwork.selector) {
callData = abi.encodeWithSelector(
this.sampleSellsFromKyberNetwork.selector,
takerToken,
makerToken,
tmpTakerAmounts
);
} else {
callData = abi.encodeWithSelector(
this.sampleSellsFromLiquidityProviderRegistry.selector,
plpRegistryAddress,
takerToken,
makerToken,
tmpTakerAmounts
);
}
(bool success, bytes memory resultData) = address(this).staticcall(callData);
if (!success) {
return 0;
}
// solhint-disable indent
makerTokenAmount = abi.decode(resultData, (uint256[]))[0];
}

function _sampleApproximateBuysFromSource(
address takerToken,
address makerToken,
uint256[] memory makerTokenAmounts,
FakeBuyOptions memory opts,
bytes4 selector,
address plpRegistryAddress
)
private
view
returns (uint256[] memory takerTokenAmounts)
{
_assertValidPair(makerToken, takerToken);
if (makerTokenAmounts.length == 0) {
return takerTokenAmounts;
}
uint256 sellAmount;
uint256 buyAmount;
uint256 slippageFromTarget;
takerTokenAmounts = new uint256[](makerTokenAmounts.length);
sellAmount = _sampleSellForApproximateBuy(
makerToken,
takerToken,
makerTokenAmounts[0],
dekz marked this conversation as resolved.
Show resolved Hide resolved
selector,
plpRegistryAddress
);

if (sellAmount == 0) {
return takerTokenAmounts;
}

buyAmount = _sampleSellForApproximateBuy(
takerToken,
makerToken,
sellAmount,
selector,
plpRegistryAddress
);
if (buyAmount == 0) {
return takerTokenAmounts;
}

for (uint256 i = 0; i < makerTokenAmounts.length; i++) {
for (uint256 iter = 0; iter < opts.maxIterations; iter++) {
// adjustedSellAmount = previousSellAmount * (target/actual) * JUMP_MULTIPLIER
sellAmount = LibMath.getPartialAmountCeil(
makerTokenAmounts[i],
buyAmount,
sellAmount
);
sellAmount = LibMath.getPartialAmountCeil(
(10000 + opts.targetSlippageBps),
10000,
sellAmount
);
uint256 _buyAmount = _sampleSellForApproximateBuy(
takerToken,
makerToken,
sellAmount,
selector,
plpRegistryAddress
);
if (_buyAmount == 0) {
break;
}
// We re-use buyAmount next iteration, only assign if it is
// non zero
buyAmount = _buyAmount;
// If we've reached our goal, exit early
if (buyAmount >= makerTokenAmounts[i]) {
uint256 slippageFromTarget = (buyAmount - makerTokenAmounts[i]) * 10000 /
makerTokenAmounts[i];
if (slippageFromTarget <= opts.targetSlippageBps) {
break;
}
}
dekz marked this conversation as resolved.
Show resolved Hide resolved
}
// We do our best to close in on the requested amount, but we can either over buy or under buy and exit
// if we hit a max iteration limit
// We scale the sell amount to get the approximate target
takerTokenAmounts[i] = LibMath.getPartialAmountCeil(
makerTokenAmounts[i],
buyAmount,
sellAmount
);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";

interface IERC20BridgeSampler {

struct FakeBuyOptions {
uint256 targetSlippageBps;
uint256 maxIterations;
}

/// @dev Call multiple public functions on this contract in a single transaction.
/// @param callDatas ABI-encoded call data for each function call.
/// @return callResults ABI-encoded results data for each call.
Expand Down Expand Up @@ -73,6 +78,23 @@ interface IERC20BridgeSampler {
view
returns (uint256[] memory makerTokenAmounts);

/// @dev Sample buy quotes from Kyber.
/// @param takerToken Address of the taker token (what to sell).
/// @param makerToken Address of the maker token (what to buy).
/// @param makerTokenAmounts Maker token buy amount for each sample.
/// @param opts `FakeBuyOptions` specifying target slippage and max iterations.
/// @return takerTokenAmounts Taker amounts sold at each maker token
/// amount.
function sampleBuysFromKyberNetwork(
address takerToken,
address makerToken,
uint256[] calldata makerTokenAmounts,
FakeBuyOptions calldata opts
)
external
view
returns (uint256[] memory takerTokenAmounts);

/// @dev Sample sell quotes from Eth2Dai/Oasis.
/// @param takerToken Address of the taker token (what to sell).
/// @param makerToken Address of the maker token (what to buy).
Expand Down Expand Up @@ -106,7 +128,7 @@ interface IERC20BridgeSampler {
/// @dev Sample buy quotes from Uniswap.
/// @param takerToken Address of the taker token (what to sell).
/// @param makerToken Address of the maker token (what to buy).
/// @param makerTokenAmounts Maker token sell amount for each sample.
/// @param makerTokenAmounts Maker token buy amount for each sample.
/// @return takerTokenAmounts Taker amounts sold at each maker token
/// amount.
function sampleBuysFromUniswap(
Expand All @@ -121,7 +143,7 @@ interface IERC20BridgeSampler {
/// @dev Sample buy quotes from Eth2Dai/Oasis.
/// @param takerToken Address of the taker token (what to sell).
/// @param makerToken Address of the maker token (what to buy).
/// @param takerTokenAmounts Maker token sell amount for each sample.
/// @param makerTokenAmounts Maker token buy amount for each sample.
/// @return takerTokenAmounts Taker amounts sold at each maker token
/// amount.
function sampleBuysFromEth2Dai(
Expand Down Expand Up @@ -150,6 +172,23 @@ interface IERC20BridgeSampler {
view
returns (uint256[] memory makerTokenAmounts);

/// @dev Sample buy quotes from Curve.
/// @param curveAddress Address of the Curve contract.
/// @param fromTokenIdx Index of the taker token (what to sell).
/// @param toTokenIdx Index of the maker token (what to buy).
/// @param makerTokenAmounts Maker token buy amount for each sample.
/// @return takerTokenAmounts Taker amounts sold at each maker token
/// amount.
function sampleBuysFromCurve(
address curveAddress,
int128 fromTokenIdx,
int128 toTokenIdx,
uint256[] calldata makerTokenAmounts
)
external
view
returns (uint256[] memory takerTokenAmounts);

/// @dev Sample sell quotes from an arbitrary on-chain liquidity provider.
/// @param registryAddress Address of the liquidity provider registry contract.
/// @param takerToken Address of the taker token (what to sell).
Expand All @@ -172,13 +211,16 @@ interface IERC20BridgeSampler {
/// @param takerToken Address of the taker token (what to sell).
/// @param makerToken Address of the maker token (what to buy).
/// @param makerTokenAmounts Maker token buy amount for each sample.
/// @param opts `FakeBuyOptions` specifying target slippage and max iterations.
/// @return takerTokenAmounts Taker amounts sold at each maker token
/// amount.
function sampleBuysFromLiquidityProviderRegistry(
address registryAddress,
address takerToken,
address makerToken,
uint256[] calldata makerTokenAmounts
uint256[] calldata makerTokenAmounts,
FakeBuyOptions calldata opts

)
external
view
Expand Down
Loading