@0x/contracts-asset-proxy
: Create DexForwarderBridge
bridge contract.
This commit is contained in:
parent
277dbacf68
commit
4bdaa48303
@ -17,6 +17,10 @@
|
|||||||
{
|
{
|
||||||
"note": "Added `MixinGasToken` allowing Gas Tokens to be freed",
|
"note": "Added `MixinGasToken` allowing Gas Tokens to be freed",
|
||||||
"pr": 2523
|
"pr": 2523
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"note": "Add `DexForwaderBridge` bridge contract.",
|
||||||
|
"pr": 2525
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -0,0 +1,210 @@
|
|||||||
|
/*
|
||||||
|
|
||||||
|
Copyright 2020 ZeroEx Intl.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
pragma solidity ^0.5.9;
|
||||||
|
pragma experimental ABIEncoderV2;
|
||||||
|
|
||||||
|
import "@0x/contracts-erc20/contracts/src/interfaces/IERC20Token.sol";
|
||||||
|
import "@0x/contracts-erc20/contracts/src/LibERC20Token.sol";
|
||||||
|
import "@0x/contracts-exchange-libs/contracts/src/IWallet.sol";
|
||||||
|
import "@0x/contracts-exchange-libs/contracts/src/LibMath.sol";
|
||||||
|
import "@0x/contracts-utils/contracts/src/LibBytes.sol";
|
||||||
|
import "@0x/contracts-utils/contracts/src/LibSafeMath.sol";
|
||||||
|
import "../interfaces/IERC20Bridge.sol";
|
||||||
|
|
||||||
|
|
||||||
|
// solhint-disable space-after-comma, indent
|
||||||
|
contract DexForwarderBridge is
|
||||||
|
IERC20Bridge,
|
||||||
|
IWallet
|
||||||
|
{
|
||||||
|
using LibSafeMath for uint256;
|
||||||
|
|
||||||
|
/// @dev Data needed to reconstruct a bridge call.
|
||||||
|
struct BridgeCall {
|
||||||
|
address target;
|
||||||
|
uint256 inputTokenAmount;
|
||||||
|
uint256 outputTokenAmount;
|
||||||
|
bytes bridgeData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Intermediate state variables used by `bridgeTransferFrom()`, in
|
||||||
|
/// struct form to get around stack limits.
|
||||||
|
struct TransferFromState {
|
||||||
|
address inputToken;
|
||||||
|
uint256 initialInputTokenBalance;
|
||||||
|
uint256 callInputTokenAmount;
|
||||||
|
uint256 callOutputTokenAmount;
|
||||||
|
uint256 totalInputTokenSold;
|
||||||
|
BridgeCall[] calls;
|
||||||
|
}
|
||||||
|
|
||||||
|
event DexForwarderBridgeCallFailed(
|
||||||
|
address indexed target,
|
||||||
|
address inputToken,
|
||||||
|
address outputToken,
|
||||||
|
uint256 inputTokenAmount,
|
||||||
|
uint256 outputTokenAmount
|
||||||
|
);
|
||||||
|
|
||||||
|
/// @dev Executes a series of calls, forwarding .
|
||||||
|
/// @param outputToken The token being bought.
|
||||||
|
/// @param to The recipient of the bought tokens.
|
||||||
|
/// @param bridgeData The abi-encoeded input token address.
|
||||||
|
/// @return success The magic bytes if successful.
|
||||||
|
function bridgeTransferFrom(
|
||||||
|
address outputToken,
|
||||||
|
address /* from */,
|
||||||
|
address to,
|
||||||
|
uint256 /* amount */,
|
||||||
|
bytes calldata bridgeData
|
||||||
|
)
|
||||||
|
external
|
||||||
|
returns (bytes4 success)
|
||||||
|
{
|
||||||
|
TransferFromState memory state;
|
||||||
|
(
|
||||||
|
state.inputToken,
|
||||||
|
state.calls
|
||||||
|
) = abi.decode(bridgeData, (address, BridgeCall[]));
|
||||||
|
|
||||||
|
state.initialInputTokenBalance = IERC20Token(state.inputToken).balanceOf(address(this));
|
||||||
|
|
||||||
|
for (uint256 i = 0; i < state.calls.length; ++i) {
|
||||||
|
// Stop if the we've sold all our input tokens.
|
||||||
|
if (state.totalInputTokenSold >= state.initialInputTokenBalance) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
BridgeCall memory call = state.calls[i];
|
||||||
|
// Compute token amounts.
|
||||||
|
state.callInputTokenAmount = LibSafeMath.min256(
|
||||||
|
call.inputTokenAmount,
|
||||||
|
state.initialInputTokenBalance.safeSub(state.totalInputTokenSold)
|
||||||
|
);
|
||||||
|
state.callOutputTokenAmount = LibMath.getPartialAmountFloor(
|
||||||
|
state.callInputTokenAmount,
|
||||||
|
call.inputTokenAmount,
|
||||||
|
call.outputTokenAmount
|
||||||
|
);
|
||||||
|
|
||||||
|
(bool didSucceed, ) = address(this)
|
||||||
|
.call(abi.encodeWithSelector(
|
||||||
|
this.executeBridgeCall.selector,
|
||||||
|
call.target,
|
||||||
|
to,
|
||||||
|
state.inputToken,
|
||||||
|
outputToken,
|
||||||
|
state.callInputTokenAmount,
|
||||||
|
state.callOutputTokenAmount,
|
||||||
|
call.bridgeData
|
||||||
|
));
|
||||||
|
|
||||||
|
if (!didSucceed) {
|
||||||
|
// Log errors.
|
||||||
|
emit DexForwarderBridgeCallFailed(
|
||||||
|
call.target,
|
||||||
|
state.inputToken,
|
||||||
|
outputToken,
|
||||||
|
state.callInputTokenAmount,
|
||||||
|
state.callOutputTokenAmount
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Increase the amount of tokens sold.
|
||||||
|
state.totalInputTokenSold = state.totalInputTokenSold.safeAdd(
|
||||||
|
state.callInputTokenAmount
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Revert if we were not able to sell our entire input token balance.
|
||||||
|
require(
|
||||||
|
state.totalInputTokenSold >= state.initialInputTokenBalance,
|
||||||
|
"DexForwaderBridge/INCOMPLETE_FILL"
|
||||||
|
);
|
||||||
|
// Always succeed.
|
||||||
|
return BRIDGE_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Transfers `inputToken` token to a bridge contract then calls
|
||||||
|
/// its `bridgeTransferFrom()`. This is executed in separate context
|
||||||
|
/// so we can revert the transfer on error. This can only be called
|
||||||
|
// by this contract itself.
|
||||||
|
/// @param bridge The bridge contract.
|
||||||
|
/// @param to The recipient of `outputToken` tokens.
|
||||||
|
/// @param inputToken The input token.
|
||||||
|
/// @param outputToken The output token.
|
||||||
|
/// @param inputTokenAmount The amount of input tokens to transfer to `bridge`.
|
||||||
|
/// @param outputTokenAmount The amount of expected output tokens to be sent
|
||||||
|
/// to `to` by `bridge`.
|
||||||
|
function executeBridgeCall(
|
||||||
|
address bridge,
|
||||||
|
address to,
|
||||||
|
address inputToken,
|
||||||
|
address outputToken,
|
||||||
|
uint256 inputTokenAmount,
|
||||||
|
uint256 outputTokenAmount,
|
||||||
|
bytes calldata bridgeData
|
||||||
|
)
|
||||||
|
external
|
||||||
|
{
|
||||||
|
// Must be called through `bridgeTransferFrom()`.
|
||||||
|
require(msg.sender == address(this), "DexForwaderBridge/ONLY_SELF");
|
||||||
|
// `bridge` must not be this contract.
|
||||||
|
require(bridge != address(this), "DexForwaderBridge/ILLEGAL_BRIDGE");
|
||||||
|
|
||||||
|
// Get the starting balance of output tokens for `to`.
|
||||||
|
uint256 initialRecipientBalance = IERC20Token(outputToken).balanceOf(to);
|
||||||
|
|
||||||
|
// Transfer input tokens to the bridge.
|
||||||
|
LibERC20Token.transfer(inputToken, bridge, inputTokenAmount);
|
||||||
|
|
||||||
|
// Call the bridge.
|
||||||
|
(bool didSucceed, bytes memory resultData) =
|
||||||
|
bridge.call(abi.encodeWithSelector(
|
||||||
|
IERC20Bridge(0).bridgeTransferFrom.selector,
|
||||||
|
outputToken,
|
||||||
|
bridge,
|
||||||
|
to,
|
||||||
|
outputTokenAmount,
|
||||||
|
bridgeData
|
||||||
|
));
|
||||||
|
|
||||||
|
// Revert if the call failed or not enough tokens were bought.
|
||||||
|
// This will also undo the token transfer.
|
||||||
|
require(
|
||||||
|
didSucceed
|
||||||
|
&& resultData.length == 32
|
||||||
|
&& LibBytes.readBytes32(resultData, 0) == bytes32(BRIDGE_SUCCESS)
|
||||||
|
&& IERC20Token(outputToken).balanceOf(to).safeSub(initialRecipientBalance) >= outputTokenAmount
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev `SignatureType.Wallet` callback, so that this bridge can be the maker
|
||||||
|
/// and sign for itself in orders. Always succeeds.
|
||||||
|
/// @return magicValue Magic success bytes, always.
|
||||||
|
function isValidSignature(
|
||||||
|
bytes32,
|
||||||
|
bytes calldata
|
||||||
|
)
|
||||||
|
external
|
||||||
|
view
|
||||||
|
returns (bytes4 magicValue)
|
||||||
|
{
|
||||||
|
return LEGACY_WALLET_MAGIC_VALUE;
|
||||||
|
}
|
||||||
|
}
|
220
contracts/asset-proxy/contracts/test/TestDexForwarderBridge.sol
Normal file
220
contracts/asset-proxy/contracts/test/TestDexForwarderBridge.sol
Normal file
@ -0,0 +1,220 @@
|
|||||||
|
/*
|
||||||
|
|
||||||
|
Copyright 2020 ZeroEx Intl.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
pragma solidity ^0.5.9;
|
||||||
|
pragma experimental ABIEncoderV2;
|
||||||
|
|
||||||
|
import "../src/bridges/DexForwarderBridge.sol";
|
||||||
|
import "@0x/contracts-utils/contracts/src/LibSafeMath.sol";
|
||||||
|
|
||||||
|
|
||||||
|
interface ITestDexForwarderBridge {
|
||||||
|
event BridgeTransferFromCalled(
|
||||||
|
address caller,
|
||||||
|
uint256 inputTokenBalance,
|
||||||
|
address inputToken,
|
||||||
|
address outputToken,
|
||||||
|
address from,
|
||||||
|
address to,
|
||||||
|
uint256 amount
|
||||||
|
);
|
||||||
|
|
||||||
|
event TokenTransferCalled(
|
||||||
|
address from,
|
||||||
|
address to,
|
||||||
|
uint256 amount
|
||||||
|
);
|
||||||
|
|
||||||
|
function emitBridgeTransferFromCalled(
|
||||||
|
address caller,
|
||||||
|
uint256 inputTokenBalance,
|
||||||
|
address inputToken,
|
||||||
|
address outputToken,
|
||||||
|
address from,
|
||||||
|
address to,
|
||||||
|
uint256 amount
|
||||||
|
) external;
|
||||||
|
|
||||||
|
function emitTokenTransferCalled(
|
||||||
|
address from,
|
||||||
|
address to,
|
||||||
|
uint256 amount
|
||||||
|
) external;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
interface ITestDexForwarderBridgeTestToken {
|
||||||
|
|
||||||
|
function transfer(address to, uint256 amount)
|
||||||
|
external
|
||||||
|
returns (bool);
|
||||||
|
|
||||||
|
function mint(address to, uint256 amount)
|
||||||
|
external;
|
||||||
|
|
||||||
|
function balanceOf(address owner) external view returns (uint256);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
contract TestDexForwarderBridgeTestBridge {
|
||||||
|
|
||||||
|
bytes4 private _returnCode;
|
||||||
|
string private _revertError;
|
||||||
|
uint256 private _transferAmount;
|
||||||
|
ITestDexForwarderBridge private _testContract;
|
||||||
|
|
||||||
|
constructor(bytes4 returnCode, string memory revertError) public {
|
||||||
|
_testContract = ITestDexForwarderBridge(msg.sender);
|
||||||
|
_returnCode = returnCode;
|
||||||
|
_revertError = revertError;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setTransferAmount(uint256 amount) external {
|
||||||
|
_transferAmount = amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
function bridgeTransferFrom(
|
||||||
|
address outputToken,
|
||||||
|
address from,
|
||||||
|
address to,
|
||||||
|
uint256 amount,
|
||||||
|
bytes memory bridgeData
|
||||||
|
)
|
||||||
|
public
|
||||||
|
returns (bytes4 success)
|
||||||
|
{
|
||||||
|
if (bytes(_revertError).length != 0) {
|
||||||
|
revert(_revertError);
|
||||||
|
}
|
||||||
|
address inputToken = abi.decode(bridgeData, (address));
|
||||||
|
_testContract.emitBridgeTransferFromCalled(
|
||||||
|
msg.sender,
|
||||||
|
ITestDexForwarderBridgeTestToken(inputToken).balanceOf(address(this)),
|
||||||
|
inputToken,
|
||||||
|
outputToken,
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
amount
|
||||||
|
);
|
||||||
|
ITestDexForwarderBridgeTestToken(outputToken).mint(to, _transferAmount);
|
||||||
|
return _returnCode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
contract TestDexForwarderBridgeTestToken {
|
||||||
|
|
||||||
|
using LibSafeMath for uint256;
|
||||||
|
|
||||||
|
mapping(address => uint256) public balanceOf;
|
||||||
|
ITestDexForwarderBridge private _testContract;
|
||||||
|
|
||||||
|
constructor() public {
|
||||||
|
_testContract = ITestDexForwarderBridge(msg.sender);
|
||||||
|
}
|
||||||
|
|
||||||
|
function transfer(address to, uint256 amount)
|
||||||
|
external
|
||||||
|
returns (bool)
|
||||||
|
{
|
||||||
|
balanceOf[msg.sender] = balanceOf[msg.sender].safeSub(amount);
|
||||||
|
balanceOf[to] = balanceOf[to].safeAdd(amount);
|
||||||
|
_testContract.emitTokenTransferCalled(msg.sender, to, amount);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function mint(address owner, uint256 amount)
|
||||||
|
external
|
||||||
|
{
|
||||||
|
balanceOf[owner] = balanceOf[owner].safeAdd(amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setBalance(address owner, uint256 amount)
|
||||||
|
external
|
||||||
|
{
|
||||||
|
balanceOf[owner] = amount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
contract TestDexForwarderBridge is
|
||||||
|
ITestDexForwarderBridge,
|
||||||
|
DexForwarderBridge
|
||||||
|
{
|
||||||
|
function createBridge(
|
||||||
|
bytes4 returnCode,
|
||||||
|
string memory revertError
|
||||||
|
)
|
||||||
|
public
|
||||||
|
returns (address bridge)
|
||||||
|
{
|
||||||
|
return address(new TestDexForwarderBridgeTestBridge(returnCode, revertError));
|
||||||
|
}
|
||||||
|
|
||||||
|
function createToken() public returns (address token) {
|
||||||
|
return address(new TestDexForwarderBridgeTestToken());
|
||||||
|
}
|
||||||
|
|
||||||
|
function setTokenBalance(address token, address owner, uint256 amount) public {
|
||||||
|
TestDexForwarderBridgeTestToken(token).setBalance(owner, amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setBridgeTransferAmount(address bridge, uint256 amount) public {
|
||||||
|
TestDexForwarderBridgeTestBridge(bridge).setTransferAmount(amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
function emitBridgeTransferFromCalled(
|
||||||
|
address caller,
|
||||||
|
uint256 inputTokenBalance,
|
||||||
|
address inputToken,
|
||||||
|
address outputToken,
|
||||||
|
address from,
|
||||||
|
address to,
|
||||||
|
uint256 amount
|
||||||
|
)
|
||||||
|
public
|
||||||
|
{
|
||||||
|
emit BridgeTransferFromCalled(
|
||||||
|
caller,
|
||||||
|
inputTokenBalance,
|
||||||
|
inputToken,
|
||||||
|
outputToken,
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
amount
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function emitTokenTransferCalled(
|
||||||
|
address from,
|
||||||
|
address to,
|
||||||
|
uint256 amount
|
||||||
|
)
|
||||||
|
public
|
||||||
|
{
|
||||||
|
emit TokenTransferCalled(
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
amount
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function balanceOf(address token, address owner) public view returns (uint256) {
|
||||||
|
return TestDexForwarderBridgeTestToken(token).balanceOf(owner);
|
||||||
|
}
|
||||||
|
}
|
@ -38,7 +38,7 @@
|
|||||||
"docs:json": "typedoc --excludePrivate --excludeExternals --excludeProtected --ignoreCompilerErrors --target ES5 --tsconfig typedoc-tsconfig.json --json $JSON_FILE_PATH $PROJECT_FILES"
|
"docs:json": "typedoc --excludePrivate --excludeExternals --excludeProtected --ignoreCompilerErrors --target ES5 --tsconfig typedoc-tsconfig.json --json $JSON_FILE_PATH $PROJECT_FILES"
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"abis": "./test/generated-artifacts/@(ChaiBridge|CurveBridge|DydxBridge|ERC1155Proxy|ERC20BridgeProxy|ERC20Proxy|ERC721Proxy|Eth2DaiBridge|IAssetData|IAssetProxy|IAssetProxyDispatcher|IAuthorizable|IChai|ICurve|IDydx|IDydxBridge|IERC20Bridge|IEth2Dai|IGasToken|IKyberNetworkProxy|IUniswapExchange|IUniswapExchangeFactory|KyberBridge|MixinAssetProxyDispatcher|MixinAuthorizable|MixinGasToken|MultiAssetProxy|Ownable|StaticCallProxy|TestChaiBridge|TestDydxBridge|TestERC20Bridge|TestEth2DaiBridge|TestKyberBridge|TestStaticCallTarget|TestUniswapBridge|UniswapBridge).json",
|
"abis": "./test/generated-artifacts/@(ChaiBridge|CurveBridge|DexForwarderBridge|DydxBridge|ERC1155Proxy|ERC20BridgeProxy|ERC20Proxy|ERC721Proxy|Eth2DaiBridge|IAssetData|IAssetProxy|IAssetProxyDispatcher|IAuthorizable|IChai|ICurve|IDydx|IDydxBridge|IERC20Bridge|IEth2Dai|IGasToken|IKyberNetworkProxy|IUniswapExchange|IUniswapExchangeFactory|KyberBridge|MixinAssetProxyDispatcher|MixinAuthorizable|MixinGasToken|MultiAssetProxy|Ownable|StaticCallProxy|TestChaiBridge|TestDexForwarderBridge|TestDydxBridge|TestERC20Bridge|TestEth2DaiBridge|TestKyberBridge|TestStaticCallTarget|TestUniswapBridge|UniswapBridge).json",
|
||||||
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually."
|
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually."
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
@ -52,6 +52,7 @@
|
|||||||
"homepage": "https://github.com/0xProject/0x-monorepo/contracts/protocol/README.md",
|
"homepage": "https://github.com/0xProject/0x-monorepo/contracts/protocol/README.md",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@0x/abi-gen": "^5.2.2",
|
"@0x/abi-gen": "^5.2.2",
|
||||||
|
"@0x/contract-wrappers": "^13.6.3",
|
||||||
"@0x/contracts-gen": "^2.0.8",
|
"@0x/contracts-gen": "^2.0.8",
|
||||||
"@0x/contracts-test-utils": "^5.3.2",
|
"@0x/contracts-test-utils": "^5.3.2",
|
||||||
"@0x/contracts-utils": "^4.4.3",
|
"@0x/contracts-utils": "^4.4.3",
|
||||||
|
@ -7,6 +7,7 @@ import { ContractArtifact } from 'ethereum-types';
|
|||||||
|
|
||||||
import * as ChaiBridge from '../generated-artifacts/ChaiBridge.json';
|
import * as ChaiBridge from '../generated-artifacts/ChaiBridge.json';
|
||||||
import * as CurveBridge from '../generated-artifacts/CurveBridge.json';
|
import * as CurveBridge from '../generated-artifacts/CurveBridge.json';
|
||||||
|
import * as DexForwarderBridge from '../generated-artifacts/DexForwarderBridge.json';
|
||||||
import * as DydxBridge from '../generated-artifacts/DydxBridge.json';
|
import * as DydxBridge from '../generated-artifacts/DydxBridge.json';
|
||||||
import * as ERC1155Proxy from '../generated-artifacts/ERC1155Proxy.json';
|
import * as ERC1155Proxy from '../generated-artifacts/ERC1155Proxy.json';
|
||||||
import * as ERC20BridgeProxy from '../generated-artifacts/ERC20BridgeProxy.json';
|
import * as ERC20BridgeProxy from '../generated-artifacts/ERC20BridgeProxy.json';
|
||||||
@ -35,6 +36,7 @@ import * as MultiAssetProxy from '../generated-artifacts/MultiAssetProxy.json';
|
|||||||
import * as Ownable from '../generated-artifacts/Ownable.json';
|
import * as Ownable from '../generated-artifacts/Ownable.json';
|
||||||
import * as StaticCallProxy from '../generated-artifacts/StaticCallProxy.json';
|
import * as StaticCallProxy from '../generated-artifacts/StaticCallProxy.json';
|
||||||
import * as TestChaiBridge from '../generated-artifacts/TestChaiBridge.json';
|
import * as TestChaiBridge from '../generated-artifacts/TestChaiBridge.json';
|
||||||
|
import * as TestDexForwarderBridge from '../generated-artifacts/TestDexForwarderBridge.json';
|
||||||
import * as TestDydxBridge from '../generated-artifacts/TestDydxBridge.json';
|
import * as TestDydxBridge from '../generated-artifacts/TestDydxBridge.json';
|
||||||
import * as TestERC20Bridge from '../generated-artifacts/TestERC20Bridge.json';
|
import * as TestERC20Bridge from '../generated-artifacts/TestERC20Bridge.json';
|
||||||
import * as TestEth2DaiBridge from '../generated-artifacts/TestEth2DaiBridge.json';
|
import * as TestEth2DaiBridge from '../generated-artifacts/TestEth2DaiBridge.json';
|
||||||
@ -54,6 +56,7 @@ export const artifacts = {
|
|||||||
StaticCallProxy: StaticCallProxy as ContractArtifact,
|
StaticCallProxy: StaticCallProxy as ContractArtifact,
|
||||||
ChaiBridge: ChaiBridge as ContractArtifact,
|
ChaiBridge: ChaiBridge as ContractArtifact,
|
||||||
CurveBridge: CurveBridge as ContractArtifact,
|
CurveBridge: CurveBridge as ContractArtifact,
|
||||||
|
DexForwarderBridge: DexForwarderBridge as ContractArtifact,
|
||||||
DydxBridge: DydxBridge as ContractArtifact,
|
DydxBridge: DydxBridge as ContractArtifact,
|
||||||
Eth2DaiBridge: Eth2DaiBridge as ContractArtifact,
|
Eth2DaiBridge: Eth2DaiBridge as ContractArtifact,
|
||||||
KyberBridge: KyberBridge as ContractArtifact,
|
KyberBridge: KyberBridge as ContractArtifact,
|
||||||
@ -74,6 +77,7 @@ export const artifacts = {
|
|||||||
IUniswapExchange: IUniswapExchange as ContractArtifact,
|
IUniswapExchange: IUniswapExchange as ContractArtifact,
|
||||||
IUniswapExchangeFactory: IUniswapExchangeFactory as ContractArtifact,
|
IUniswapExchangeFactory: IUniswapExchangeFactory as ContractArtifact,
|
||||||
TestChaiBridge: TestChaiBridge as ContractArtifact,
|
TestChaiBridge: TestChaiBridge as ContractArtifact,
|
||||||
|
TestDexForwarderBridge: TestDexForwarderBridge as ContractArtifact,
|
||||||
TestDydxBridge: TestDydxBridge as ContractArtifact,
|
TestDydxBridge: TestDydxBridge as ContractArtifact,
|
||||||
TestERC20Bridge: TestERC20Bridge as ContractArtifact,
|
TestERC20Bridge: TestERC20Bridge as ContractArtifact,
|
||||||
TestEth2DaiBridge: TestEth2DaiBridge as ContractArtifact,
|
TestEth2DaiBridge: TestEth2DaiBridge as ContractArtifact,
|
||||||
|
27
contracts/asset-proxy/src/dex_forwarder_bridge.ts
Normal file
27
contracts/asset-proxy/src/dex_forwarder_bridge.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { AbiEncoder, BigNumber } from '@0x/utils';
|
||||||
|
|
||||||
|
export interface DexForwaderBridgeCall {
|
||||||
|
target: string;
|
||||||
|
inputTokenAmount: BigNumber;
|
||||||
|
outputTokenAmount: BigNumber;
|
||||||
|
bridgeData: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DexForwaderBridgeData {
|
||||||
|
inputToken: string;
|
||||||
|
calls: DexForwaderBridgeCall[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const dexForwarderBridgeDataEncoder = AbiEncoder.create([
|
||||||
|
{ name: 'inputToken', type: 'address' },
|
||||||
|
{
|
||||||
|
name: 'calls',
|
||||||
|
type: 'tuple[]',
|
||||||
|
components: [
|
||||||
|
{ name: 'target', type: 'address' },
|
||||||
|
{ name: 'inputTokenAmount', type: 'uint256' },
|
||||||
|
{ name: 'outputTokenAmount', type: 'uint256' },
|
||||||
|
{ name: 'bridgeData', type: 'bytes' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
@ -88,3 +88,4 @@ export {
|
|||||||
} from './asset_data';
|
} from './asset_data';
|
||||||
|
|
||||||
export * from './dydx_bridge_encoder';
|
export * from './dydx_bridge_encoder';
|
||||||
|
export * from './dex_forwarder_bridge';
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
export * from '../generated-wrappers/chai_bridge';
|
export * from '../generated-wrappers/chai_bridge';
|
||||||
export * from '../generated-wrappers/curve_bridge';
|
export * from '../generated-wrappers/curve_bridge';
|
||||||
|
export * from '../generated-wrappers/dex_forwarder_bridge';
|
||||||
export * from '../generated-wrappers/dydx_bridge';
|
export * from '../generated-wrappers/dydx_bridge';
|
||||||
export * from '../generated-wrappers/erc1155_proxy';
|
export * from '../generated-wrappers/erc1155_proxy';
|
||||||
export * from '../generated-wrappers/erc20_bridge_proxy';
|
export * from '../generated-wrappers/erc20_bridge_proxy';
|
||||||
@ -33,6 +34,7 @@ export * from '../generated-wrappers/multi_asset_proxy';
|
|||||||
export * from '../generated-wrappers/ownable';
|
export * from '../generated-wrappers/ownable';
|
||||||
export * from '../generated-wrappers/static_call_proxy';
|
export * from '../generated-wrappers/static_call_proxy';
|
||||||
export * from '../generated-wrappers/test_chai_bridge';
|
export * from '../generated-wrappers/test_chai_bridge';
|
||||||
|
export * from '../generated-wrappers/test_dex_forwarder_bridge';
|
||||||
export * from '../generated-wrappers/test_dydx_bridge';
|
export * from '../generated-wrappers/test_dydx_bridge';
|
||||||
export * from '../generated-wrappers/test_erc20_bridge';
|
export * from '../generated-wrappers/test_erc20_bridge';
|
||||||
export * from '../generated-wrappers/test_eth2_dai_bridge';
|
export * from '../generated-wrappers/test_eth2_dai_bridge';
|
||||||
|
@ -7,6 +7,7 @@ import { ContractArtifact } from 'ethereum-types';
|
|||||||
|
|
||||||
import * as ChaiBridge from '../test/generated-artifacts/ChaiBridge.json';
|
import * as ChaiBridge from '../test/generated-artifacts/ChaiBridge.json';
|
||||||
import * as CurveBridge from '../test/generated-artifacts/CurveBridge.json';
|
import * as CurveBridge from '../test/generated-artifacts/CurveBridge.json';
|
||||||
|
import * as DexForwarderBridge from '../test/generated-artifacts/DexForwarderBridge.json';
|
||||||
import * as DydxBridge from '../test/generated-artifacts/DydxBridge.json';
|
import * as DydxBridge from '../test/generated-artifacts/DydxBridge.json';
|
||||||
import * as ERC1155Proxy from '../test/generated-artifacts/ERC1155Proxy.json';
|
import * as ERC1155Proxy from '../test/generated-artifacts/ERC1155Proxy.json';
|
||||||
import * as ERC20BridgeProxy from '../test/generated-artifacts/ERC20BridgeProxy.json';
|
import * as ERC20BridgeProxy from '../test/generated-artifacts/ERC20BridgeProxy.json';
|
||||||
@ -35,6 +36,7 @@ import * as MultiAssetProxy from '../test/generated-artifacts/MultiAssetProxy.js
|
|||||||
import * as Ownable from '../test/generated-artifacts/Ownable.json';
|
import * as Ownable from '../test/generated-artifacts/Ownable.json';
|
||||||
import * as StaticCallProxy from '../test/generated-artifacts/StaticCallProxy.json';
|
import * as StaticCallProxy from '../test/generated-artifacts/StaticCallProxy.json';
|
||||||
import * as TestChaiBridge from '../test/generated-artifacts/TestChaiBridge.json';
|
import * as TestChaiBridge from '../test/generated-artifacts/TestChaiBridge.json';
|
||||||
|
import * as TestDexForwarderBridge from '../test/generated-artifacts/TestDexForwarderBridge.json';
|
||||||
import * as TestDydxBridge from '../test/generated-artifacts/TestDydxBridge.json';
|
import * as TestDydxBridge from '../test/generated-artifacts/TestDydxBridge.json';
|
||||||
import * as TestERC20Bridge from '../test/generated-artifacts/TestERC20Bridge.json';
|
import * as TestERC20Bridge from '../test/generated-artifacts/TestERC20Bridge.json';
|
||||||
import * as TestEth2DaiBridge from '../test/generated-artifacts/TestEth2DaiBridge.json';
|
import * as TestEth2DaiBridge from '../test/generated-artifacts/TestEth2DaiBridge.json';
|
||||||
@ -54,6 +56,7 @@ export const artifacts = {
|
|||||||
StaticCallProxy: StaticCallProxy as ContractArtifact,
|
StaticCallProxy: StaticCallProxy as ContractArtifact,
|
||||||
ChaiBridge: ChaiBridge as ContractArtifact,
|
ChaiBridge: ChaiBridge as ContractArtifact,
|
||||||
CurveBridge: CurveBridge as ContractArtifact,
|
CurveBridge: CurveBridge as ContractArtifact,
|
||||||
|
DexForwarderBridge: DexForwarderBridge as ContractArtifact,
|
||||||
DydxBridge: DydxBridge as ContractArtifact,
|
DydxBridge: DydxBridge as ContractArtifact,
|
||||||
Eth2DaiBridge: Eth2DaiBridge as ContractArtifact,
|
Eth2DaiBridge: Eth2DaiBridge as ContractArtifact,
|
||||||
KyberBridge: KyberBridge as ContractArtifact,
|
KyberBridge: KyberBridge as ContractArtifact,
|
||||||
@ -74,6 +77,7 @@ export const artifacts = {
|
|||||||
IUniswapExchange: IUniswapExchange as ContractArtifact,
|
IUniswapExchange: IUniswapExchange as ContractArtifact,
|
||||||
IUniswapExchangeFactory: IUniswapExchangeFactory as ContractArtifact,
|
IUniswapExchangeFactory: IUniswapExchangeFactory as ContractArtifact,
|
||||||
TestChaiBridge: TestChaiBridge as ContractArtifact,
|
TestChaiBridge: TestChaiBridge as ContractArtifact,
|
||||||
|
TestDexForwarderBridge: TestDexForwarderBridge as ContractArtifact,
|
||||||
TestDydxBridge: TestDydxBridge as ContractArtifact,
|
TestDydxBridge: TestDydxBridge as ContractArtifact,
|
||||||
TestERC20Bridge: TestERC20Bridge as ContractArtifact,
|
TestERC20Bridge: TestERC20Bridge as ContractArtifact,
|
||||||
TestEth2DaiBridge: TestEth2DaiBridge as ContractArtifact,
|
TestEth2DaiBridge: TestEth2DaiBridge as ContractArtifact,
|
||||||
|
385
contracts/asset-proxy/test/dex_forwarder_bridge.ts
Normal file
385
contracts/asset-proxy/test/dex_forwarder_bridge.ts
Normal file
@ -0,0 +1,385 @@
|
|||||||
|
import { ContractTxFunctionObj } from '@0x/contract-wrappers';
|
||||||
|
import {
|
||||||
|
blockchainTests,
|
||||||
|
constants,
|
||||||
|
expect,
|
||||||
|
filterLogsToArguments,
|
||||||
|
getRandomInteger,
|
||||||
|
randomAddress,
|
||||||
|
shortZip,
|
||||||
|
verifyEventsFromLogs,
|
||||||
|
} from '@0x/contracts-test-utils';
|
||||||
|
import { BigNumber, hexUtils } from '@0x/utils';
|
||||||
|
import { DecodedLogs } from 'ethereum-types';
|
||||||
|
import * as _ from 'lodash';
|
||||||
|
|
||||||
|
import { DexForwaderBridgeCall, dexForwarderBridgeDataEncoder } from '../src/dex_forwarder_bridge';
|
||||||
|
|
||||||
|
import { artifacts } from './artifacts';
|
||||||
|
import {
|
||||||
|
DexForwarderBridgeEvents,
|
||||||
|
TestDexForwarderBridgeBridgeTransferFromCalledEventArgs as BtfCalledEventArgs,
|
||||||
|
TestDexForwarderBridgeContract,
|
||||||
|
TestDexForwarderBridgeEvents as TestEvents,
|
||||||
|
} from './wrappers';
|
||||||
|
|
||||||
|
const { ZERO_AMOUNT } = constants;
|
||||||
|
|
||||||
|
blockchainTests.resets('DexForwaderBridge unit tests', env => {
|
||||||
|
let testContract: TestDexForwarderBridgeContract;
|
||||||
|
let inputToken: string;
|
||||||
|
let outputToken: string;
|
||||||
|
const BRIDGE_SUCCESS = '0xdc1600f3';
|
||||||
|
const BRIDGE_FAILURE = '0xffffffff';
|
||||||
|
const BRIDGE_REVERT_ERROR = 'oopsie';
|
||||||
|
const INCOMPLETE_FILL_REVERT = 'DexForwaderBridge/INCOMPLETE_FILL';
|
||||||
|
const DEFAULTS = {
|
||||||
|
toAddress: randomAddress(),
|
||||||
|
};
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
testContract = await TestDexForwarderBridgeContract.deployFrom0xArtifactAsync(
|
||||||
|
artifacts.TestDexForwarderBridge,
|
||||||
|
env.provider,
|
||||||
|
env.txDefaults,
|
||||||
|
artifacts,
|
||||||
|
);
|
||||||
|
// Create test tokens.
|
||||||
|
[inputToken, outputToken] = [
|
||||||
|
await callAndTransactAsync(testContract.createToken()),
|
||||||
|
await callAndTransactAsync(testContract.createToken()),
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
async function callAndTransactAsync<TResult>(fnCall: ContractTxFunctionObj<TResult>): Promise<TResult> {
|
||||||
|
const result = await fnCall.callAsync();
|
||||||
|
await fnCall.awaitTransactionSuccessAsync({}, { shouldValidate: false });
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRandomBridgeCall(
|
||||||
|
bridgeAddress: string,
|
||||||
|
fields: Partial<DexForwaderBridgeCall> = {},
|
||||||
|
): DexForwaderBridgeCall {
|
||||||
|
return {
|
||||||
|
target: bridgeAddress,
|
||||||
|
inputTokenAmount: getRandomInteger(1, '100e18'),
|
||||||
|
outputTokenAmount: getRandomInteger(1, '100e18'),
|
||||||
|
bridgeData: hexUtils.leftPad(inputToken),
|
||||||
|
...fields,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('bridgeTransferFrom()', () => {
|
||||||
|
let goodBridgeCalls: DexForwaderBridgeCall[];
|
||||||
|
let revertingBridgeCall: DexForwaderBridgeCall;
|
||||||
|
let failingBridgeCall: DexForwaderBridgeCall;
|
||||||
|
let allBridgeCalls: DexForwaderBridgeCall[];
|
||||||
|
let totalFillableOutputAmount: BigNumber;
|
||||||
|
let totalFillableInputAmount: BigNumber;
|
||||||
|
let recipientOutputBalance: BigNumber;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
goodBridgeCalls = [];
|
||||||
|
for (let i = 0; i < 4; ++i) {
|
||||||
|
goodBridgeCalls.push(await createBridgeCallAsync({ returnCode: BRIDGE_SUCCESS }));
|
||||||
|
}
|
||||||
|
revertingBridgeCall = await createBridgeCallAsync({ revertError: BRIDGE_REVERT_ERROR });
|
||||||
|
failingBridgeCall = await createBridgeCallAsync({ returnCode: BRIDGE_FAILURE });
|
||||||
|
allBridgeCalls = _.shuffle([failingBridgeCall, revertingBridgeCall, ...goodBridgeCalls]);
|
||||||
|
|
||||||
|
totalFillableInputAmount = BigNumber.sum(...goodBridgeCalls.map(c => c.inputTokenAmount));
|
||||||
|
totalFillableOutputAmount = BigNumber.sum(...goodBridgeCalls.map(c => c.outputTokenAmount));
|
||||||
|
|
||||||
|
// Grant the taker some output tokens.
|
||||||
|
await testContract.setTokenBalance(
|
||||||
|
outputToken,
|
||||||
|
DEFAULTS.toAddress,
|
||||||
|
(recipientOutputBalance = getRandomInteger(1, '100e18')),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
async function setForwarderInputBalanceAsync(amount: BigNumber): Promise<void> {
|
||||||
|
await testContract
|
||||||
|
.setTokenBalance(inputToken, testContract.address, amount)
|
||||||
|
.awaitTransactionSuccessAsync({}, { shouldValidate: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createBridgeCallAsync(
|
||||||
|
opts: Partial<{
|
||||||
|
returnCode: string;
|
||||||
|
revertError: string;
|
||||||
|
callFields: Partial<DexForwaderBridgeCall>;
|
||||||
|
outputFillAmount: BigNumber;
|
||||||
|
}>,
|
||||||
|
): Promise<DexForwaderBridgeCall> {
|
||||||
|
const { returnCode, revertError, callFields, outputFillAmount } = {
|
||||||
|
returnCode: BRIDGE_SUCCESS,
|
||||||
|
revertError: '',
|
||||||
|
...opts,
|
||||||
|
};
|
||||||
|
const bridge = await callAndTransactAsync(testContract.createBridge(returnCode, revertError));
|
||||||
|
const call = getRandomBridgeCall(bridge, callFields);
|
||||||
|
await testContract
|
||||||
|
.setBridgeTransferAmount(call.target, outputFillAmount || call.outputTokenAmount)
|
||||||
|
.awaitTransactionSuccessAsync({}, { shouldValidate: false });
|
||||||
|
return call;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function callBridgeTransferFromAsync(opts: {
|
||||||
|
bridgeData: string;
|
||||||
|
sellAmount?: BigNumber;
|
||||||
|
buyAmount?: BigNumber;
|
||||||
|
}): Promise<DecodedLogs> {
|
||||||
|
// Fund the forwarder with input tokens to sell.
|
||||||
|
await setForwarderInputBalanceAsync(opts.sellAmount || totalFillableInputAmount);
|
||||||
|
const call = testContract.bridgeTransferFrom(
|
||||||
|
outputToken,
|
||||||
|
testContract.address,
|
||||||
|
DEFAULTS.toAddress,
|
||||||
|
opts.buyAmount || totalFillableOutputAmount,
|
||||||
|
opts.bridgeData,
|
||||||
|
);
|
||||||
|
const returnCode = await call.callAsync();
|
||||||
|
if (returnCode !== BRIDGE_SUCCESS) {
|
||||||
|
throw new Error('Expected BRIDGE_SUCCESS');
|
||||||
|
}
|
||||||
|
const receipt = await call.awaitTransactionSuccessAsync({}, { shouldValidate: false });
|
||||||
|
// tslint:disable-next-line: no-unnecessary-type-assertion
|
||||||
|
return receipt.logs as DecodedLogs;
|
||||||
|
}
|
||||||
|
|
||||||
|
it('succeeds with no bridge calls and no input balance', async () => {
|
||||||
|
const bridgeData = dexForwarderBridgeDataEncoder.encode({
|
||||||
|
inputToken,
|
||||||
|
calls: [],
|
||||||
|
});
|
||||||
|
await callBridgeTransferFromAsync({ bridgeData, sellAmount: ZERO_AMOUNT });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('succeeds with bridge calls and no input balance', async () => {
|
||||||
|
const bridgeData = dexForwarderBridgeDataEncoder.encode({
|
||||||
|
inputToken,
|
||||||
|
calls: allBridgeCalls,
|
||||||
|
});
|
||||||
|
await callBridgeTransferFromAsync({ bridgeData, sellAmount: ZERO_AMOUNT });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fails with no bridge calls and an input balance', async () => {
|
||||||
|
const bridgeData = dexForwarderBridgeDataEncoder.encode({
|
||||||
|
inputToken,
|
||||||
|
calls: [],
|
||||||
|
});
|
||||||
|
return expect(callBridgeTransferFromAsync({ bridgeData, sellAmount: new BigNumber(1) })).to.revertWith(
|
||||||
|
INCOMPLETE_FILL_REVERT,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fails if entire input token balance is not consumed', async () => {
|
||||||
|
const bridgeData = dexForwarderBridgeDataEncoder.encode({
|
||||||
|
inputToken,
|
||||||
|
calls: allBridgeCalls,
|
||||||
|
});
|
||||||
|
return expect(
|
||||||
|
callBridgeTransferFromAsync({
|
||||||
|
bridgeData,
|
||||||
|
sellAmount: totalFillableInputAmount.plus(1),
|
||||||
|
}),
|
||||||
|
).to.revertWith(INCOMPLETE_FILL_REVERT);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('succeeds with one bridge call', async () => {
|
||||||
|
const calls = goodBridgeCalls.slice(0, 1);
|
||||||
|
const bridgeData = dexForwarderBridgeDataEncoder.encode({
|
||||||
|
inputToken,
|
||||||
|
calls,
|
||||||
|
});
|
||||||
|
await callBridgeTransferFromAsync({ bridgeData, sellAmount: calls[0].inputTokenAmount });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('succeeds with many bridge calls', async () => {
|
||||||
|
const calls = goodBridgeCalls;
|
||||||
|
const bridgeData = dexForwarderBridgeDataEncoder.encode({
|
||||||
|
inputToken,
|
||||||
|
calls,
|
||||||
|
});
|
||||||
|
await callBridgeTransferFromAsync({ bridgeData });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('swallows a failing bridge call', async () => {
|
||||||
|
const calls = _.shuffle([...goodBridgeCalls, failingBridgeCall]);
|
||||||
|
const bridgeData = dexForwarderBridgeDataEncoder.encode({
|
||||||
|
inputToken,
|
||||||
|
calls,
|
||||||
|
});
|
||||||
|
await callBridgeTransferFromAsync({ bridgeData });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('consumes input tokens for output tokens', async () => {
|
||||||
|
const calls = allBridgeCalls;
|
||||||
|
const bridgeData = dexForwarderBridgeDataEncoder.encode({
|
||||||
|
inputToken,
|
||||||
|
calls,
|
||||||
|
});
|
||||||
|
await callBridgeTransferFromAsync({ bridgeData });
|
||||||
|
const currentBridgeInputBalance = await testContract
|
||||||
|
.balanceOf(inputToken, testContract.address)
|
||||||
|
.callAsync();
|
||||||
|
expect(currentBridgeInputBalance).to.bignumber.eq(0);
|
||||||
|
const currentRecipientOutputBalance = await testContract
|
||||||
|
.balanceOf(outputToken, DEFAULTS.toAddress)
|
||||||
|
.callAsync();
|
||||||
|
expect(currentRecipientOutputBalance).to.bignumber.eq(totalFillableOutputAmount);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('emits failure events for failing bridge calls', async () => {
|
||||||
|
const calls = [revertingBridgeCall, failingBridgeCall, ...goodBridgeCalls];
|
||||||
|
const bridgeData = dexForwarderBridgeDataEncoder.encode({
|
||||||
|
inputToken,
|
||||||
|
calls,
|
||||||
|
});
|
||||||
|
const logs = await callBridgeTransferFromAsync({ bridgeData });
|
||||||
|
verifyEventsFromLogs(
|
||||||
|
logs,
|
||||||
|
[revertingBridgeCall, failingBridgeCall].map(c => ({
|
||||||
|
inputToken,
|
||||||
|
outputToken,
|
||||||
|
target: c.target,
|
||||||
|
inputTokenAmount: c.inputTokenAmount,
|
||||||
|
outputTokenAmount: c.outputTokenAmount,
|
||||||
|
})),
|
||||||
|
DexForwarderBridgeEvents.DexForwarderBridgeCallFailed,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("transfers only up to each call's input amount to each bridge", async () => {
|
||||||
|
const calls = goodBridgeCalls;
|
||||||
|
const bridgeData = dexForwarderBridgeDataEncoder.encode({
|
||||||
|
inputToken,
|
||||||
|
calls,
|
||||||
|
});
|
||||||
|
const logs = await callBridgeTransferFromAsync({ bridgeData });
|
||||||
|
const btfs = filterLogsToArguments<BtfCalledEventArgs>(logs, TestEvents.BridgeTransferFromCalled);
|
||||||
|
for (const [call, btf] of shortZip(goodBridgeCalls, btfs)) {
|
||||||
|
expect(btf.inputTokenBalance).to.bignumber.eq(call.inputTokenAmount);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('transfers only up to outstanding sell amount to each bridge', async () => {
|
||||||
|
// Prepend an extra bridge call.
|
||||||
|
const calls = [
|
||||||
|
await createBridgeCallAsync({
|
||||||
|
callFields: {
|
||||||
|
inputTokenAmount: new BigNumber(1),
|
||||||
|
outputTokenAmount: new BigNumber(1),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
...goodBridgeCalls,
|
||||||
|
];
|
||||||
|
const bridgeData = dexForwarderBridgeDataEncoder.encode({
|
||||||
|
inputToken,
|
||||||
|
calls,
|
||||||
|
});
|
||||||
|
const logs = await callBridgeTransferFromAsync({ bridgeData });
|
||||||
|
const btfs = filterLogsToArguments<BtfCalledEventArgs>(logs, TestEvents.BridgeTransferFromCalled);
|
||||||
|
expect(btfs).to.be.length(goodBridgeCalls.length + 1);
|
||||||
|
// The last call will receive 1 less token.
|
||||||
|
const lastCall = calls.slice(-1)[0];
|
||||||
|
const lastBtf = btfs.slice(-1)[0];
|
||||||
|
expect(lastBtf.inputTokenBalance).to.bignumber.eq(lastCall.inputTokenAmount.minus(1));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('recoups funds from a bridge that fails', async () => {
|
||||||
|
// Prepend a call that will take the whole input amount but will
|
||||||
|
// fail.
|
||||||
|
const badCall = await createBridgeCallAsync({
|
||||||
|
callFields: { inputTokenAmount: totalFillableInputAmount },
|
||||||
|
returnCode: BRIDGE_FAILURE,
|
||||||
|
});
|
||||||
|
const calls = [badCall, ...goodBridgeCalls];
|
||||||
|
const bridgeData = dexForwarderBridgeDataEncoder.encode({
|
||||||
|
inputToken,
|
||||||
|
calls,
|
||||||
|
});
|
||||||
|
const logs = await callBridgeTransferFromAsync({ bridgeData });
|
||||||
|
verifyEventsFromLogs(
|
||||||
|
logs,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
inputToken,
|
||||||
|
outputToken,
|
||||||
|
target: badCall.target,
|
||||||
|
inputTokenAmount: badCall.inputTokenAmount,
|
||||||
|
outputTokenAmount: badCall.outputTokenAmount,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
TestEvents.DexForwarderBridgeCallFailed,
|
||||||
|
);
|
||||||
|
const btfs = filterLogsToArguments<BtfCalledEventArgs>(logs, TestEvents.BridgeTransferFromCalled);
|
||||||
|
expect(btfs).to.be.length(goodBridgeCalls.length);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('recoups funds from a bridge that reverts', async () => {
|
||||||
|
// Prepend a call that will take the whole input amount but will
|
||||||
|
// revert.
|
||||||
|
const badCall = await createBridgeCallAsync({
|
||||||
|
callFields: { inputTokenAmount: totalFillableInputAmount },
|
||||||
|
revertError: BRIDGE_REVERT_ERROR,
|
||||||
|
});
|
||||||
|
const calls = [badCall, ...goodBridgeCalls];
|
||||||
|
const bridgeData = dexForwarderBridgeDataEncoder.encode({
|
||||||
|
inputToken,
|
||||||
|
calls,
|
||||||
|
});
|
||||||
|
const logs = await callBridgeTransferFromAsync({ bridgeData });
|
||||||
|
verifyEventsFromLogs(
|
||||||
|
logs,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
inputToken,
|
||||||
|
outputToken,
|
||||||
|
target: badCall.target,
|
||||||
|
inputTokenAmount: badCall.inputTokenAmount,
|
||||||
|
outputTokenAmount: badCall.outputTokenAmount,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
TestEvents.DexForwarderBridgeCallFailed,
|
||||||
|
);
|
||||||
|
const btfs = filterLogsToArguments<BtfCalledEventArgs>(logs, TestEvents.BridgeTransferFromCalled);
|
||||||
|
expect(btfs).to.be.length(goodBridgeCalls.length);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('recoups funds from a bridge that under-pays', async () => {
|
||||||
|
// Prepend a call that will take the whole input amount but will
|
||||||
|
// underpay the output amount..
|
||||||
|
const badCall = await createBridgeCallAsync({
|
||||||
|
callFields: {
|
||||||
|
inputTokenAmount: totalFillableInputAmount,
|
||||||
|
outputTokenAmount: new BigNumber(2),
|
||||||
|
},
|
||||||
|
outputFillAmount: new BigNumber(1),
|
||||||
|
});
|
||||||
|
const calls = [badCall, ...goodBridgeCalls];
|
||||||
|
const bridgeData = dexForwarderBridgeDataEncoder.encode({
|
||||||
|
inputToken,
|
||||||
|
calls,
|
||||||
|
});
|
||||||
|
const logs = await callBridgeTransferFromAsync({ bridgeData });
|
||||||
|
verifyEventsFromLogs(
|
||||||
|
logs,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
inputToken,
|
||||||
|
outputToken,
|
||||||
|
target: badCall.target,
|
||||||
|
inputTokenAmount: badCall.inputTokenAmount,
|
||||||
|
outputTokenAmount: badCall.outputTokenAmount,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
TestEvents.DexForwarderBridgeCallFailed,
|
||||||
|
);
|
||||||
|
const btfs = filterLogsToArguments<BtfCalledEventArgs>(logs, TestEvents.BridgeTransferFromCalled);
|
||||||
|
expect(btfs).to.be.length(goodBridgeCalls.length);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -5,6 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
export * from '../test/generated-wrappers/chai_bridge';
|
export * from '../test/generated-wrappers/chai_bridge';
|
||||||
export * from '../test/generated-wrappers/curve_bridge';
|
export * from '../test/generated-wrappers/curve_bridge';
|
||||||
|
export * from '../test/generated-wrappers/dex_forwarder_bridge';
|
||||||
export * from '../test/generated-wrappers/dydx_bridge';
|
export * from '../test/generated-wrappers/dydx_bridge';
|
||||||
export * from '../test/generated-wrappers/erc1155_proxy';
|
export * from '../test/generated-wrappers/erc1155_proxy';
|
||||||
export * from '../test/generated-wrappers/erc20_bridge_proxy';
|
export * from '../test/generated-wrappers/erc20_bridge_proxy';
|
||||||
@ -33,6 +34,7 @@ export * from '../test/generated-wrappers/multi_asset_proxy';
|
|||||||
export * from '../test/generated-wrappers/ownable';
|
export * from '../test/generated-wrappers/ownable';
|
||||||
export * from '../test/generated-wrappers/static_call_proxy';
|
export * from '../test/generated-wrappers/static_call_proxy';
|
||||||
export * from '../test/generated-wrappers/test_chai_bridge';
|
export * from '../test/generated-wrappers/test_chai_bridge';
|
||||||
|
export * from '../test/generated-wrappers/test_dex_forwarder_bridge';
|
||||||
export * from '../test/generated-wrappers/test_dydx_bridge';
|
export * from '../test/generated-wrappers/test_dydx_bridge';
|
||||||
export * from '../test/generated-wrappers/test_erc20_bridge';
|
export * from '../test/generated-wrappers/test_erc20_bridge';
|
||||||
export * from '../test/generated-wrappers/test_eth2_dai_bridge';
|
export * from '../test/generated-wrappers/test_eth2_dai_bridge';
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
"files": [
|
"files": [
|
||||||
"generated-artifacts/ChaiBridge.json",
|
"generated-artifacts/ChaiBridge.json",
|
||||||
"generated-artifacts/CurveBridge.json",
|
"generated-artifacts/CurveBridge.json",
|
||||||
|
"generated-artifacts/DexForwarderBridge.json",
|
||||||
"generated-artifacts/DydxBridge.json",
|
"generated-artifacts/DydxBridge.json",
|
||||||
"generated-artifacts/ERC1155Proxy.json",
|
"generated-artifacts/ERC1155Proxy.json",
|
||||||
"generated-artifacts/ERC20BridgeProxy.json",
|
"generated-artifacts/ERC20BridgeProxy.json",
|
||||||
@ -33,6 +34,7 @@
|
|||||||
"generated-artifacts/Ownable.json",
|
"generated-artifacts/Ownable.json",
|
||||||
"generated-artifacts/StaticCallProxy.json",
|
"generated-artifacts/StaticCallProxy.json",
|
||||||
"generated-artifacts/TestChaiBridge.json",
|
"generated-artifacts/TestChaiBridge.json",
|
||||||
|
"generated-artifacts/TestDexForwarderBridge.json",
|
||||||
"generated-artifacts/TestDydxBridge.json",
|
"generated-artifacts/TestDydxBridge.json",
|
||||||
"generated-artifacts/TestERC20Bridge.json",
|
"generated-artifacts/TestERC20Bridge.json",
|
||||||
"generated-artifacts/TestEth2DaiBridge.json",
|
"generated-artifacts/TestEth2DaiBridge.json",
|
||||||
@ -42,6 +44,7 @@
|
|||||||
"generated-artifacts/UniswapBridge.json",
|
"generated-artifacts/UniswapBridge.json",
|
||||||
"test/generated-artifacts/ChaiBridge.json",
|
"test/generated-artifacts/ChaiBridge.json",
|
||||||
"test/generated-artifacts/CurveBridge.json",
|
"test/generated-artifacts/CurveBridge.json",
|
||||||
|
"test/generated-artifacts/DexForwarderBridge.json",
|
||||||
"test/generated-artifacts/DydxBridge.json",
|
"test/generated-artifacts/DydxBridge.json",
|
||||||
"test/generated-artifacts/ERC1155Proxy.json",
|
"test/generated-artifacts/ERC1155Proxy.json",
|
||||||
"test/generated-artifacts/ERC20BridgeProxy.json",
|
"test/generated-artifacts/ERC20BridgeProxy.json",
|
||||||
@ -70,6 +73,7 @@
|
|||||||
"test/generated-artifacts/Ownable.json",
|
"test/generated-artifacts/Ownable.json",
|
||||||
"test/generated-artifacts/StaticCallProxy.json",
|
"test/generated-artifacts/StaticCallProxy.json",
|
||||||
"test/generated-artifacts/TestChaiBridge.json",
|
"test/generated-artifacts/TestChaiBridge.json",
|
||||||
|
"test/generated-artifacts/TestDexForwarderBridge.json",
|
||||||
"test/generated-artifacts/TestDydxBridge.json",
|
"test/generated-artifacts/TestDydxBridge.json",
|
||||||
"test/generated-artifacts/TestERC20Bridge.json",
|
"test/generated-artifacts/TestERC20Bridge.json",
|
||||||
"test/generated-artifacts/TestEth2DaiBridge.json",
|
"test/generated-artifacts/TestEth2DaiBridge.json",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user