UniswapV2Bridge (#2590)
* implement UniswapV2 Bridge * More tests for UniswapV2Bridge * cleanup and remove ERC20BridgeSampler changes * enable multihop; address review comments * solidity 0.6.9 doesnt allow devdoc for public storage vars * codestyle improvements
This commit is contained in:
@@ -28,6 +28,7 @@ import * as IGasToken from '../test/generated-artifacts/IGasToken.json';
|
||||
import * as IKyberNetworkProxy from '../test/generated-artifacts/IKyberNetworkProxy.json';
|
||||
import * as IUniswapExchange from '../test/generated-artifacts/IUniswapExchange.json';
|
||||
import * as IUniswapExchangeFactory from '../test/generated-artifacts/IUniswapExchangeFactory.json';
|
||||
import * as IUniswapV2Router01 from '../test/generated-artifacts/IUniswapV2Router01.json';
|
||||
import * as KyberBridge from '../test/generated-artifacts/KyberBridge.json';
|
||||
import * as MixinAssetProxyDispatcher from '../test/generated-artifacts/MixinAssetProxyDispatcher.json';
|
||||
import * as MixinAuthorizable from '../test/generated-artifacts/MixinAuthorizable.json';
|
||||
@@ -43,7 +44,9 @@ import * as TestEth2DaiBridge from '../test/generated-artifacts/TestEth2DaiBridg
|
||||
import * as TestKyberBridge from '../test/generated-artifacts/TestKyberBridge.json';
|
||||
import * as TestStaticCallTarget from '../test/generated-artifacts/TestStaticCallTarget.json';
|
||||
import * as TestUniswapBridge from '../test/generated-artifacts/TestUniswapBridge.json';
|
||||
import * as TestUniswapV2Bridge from '../test/generated-artifacts/TestUniswapV2Bridge.json';
|
||||
import * as UniswapBridge from '../test/generated-artifacts/UniswapBridge.json';
|
||||
import * as UniswapV2Bridge from '../test/generated-artifacts/UniswapV2Bridge.json';
|
||||
export const artifacts = {
|
||||
MixinAssetProxyDispatcher: MixinAssetProxyDispatcher as ContractArtifact,
|
||||
MixinAuthorizable: MixinAuthorizable as ContractArtifact,
|
||||
@@ -62,6 +65,7 @@ export const artifacts = {
|
||||
KyberBridge: KyberBridge as ContractArtifact,
|
||||
MixinGasToken: MixinGasToken as ContractArtifact,
|
||||
UniswapBridge: UniswapBridge as ContractArtifact,
|
||||
UniswapV2Bridge: UniswapV2Bridge as ContractArtifact,
|
||||
IAssetData: IAssetData as ContractArtifact,
|
||||
IAssetProxy: IAssetProxy as ContractArtifact,
|
||||
IAssetProxyDispatcher: IAssetProxyDispatcher as ContractArtifact,
|
||||
@@ -76,6 +80,7 @@ export const artifacts = {
|
||||
IKyberNetworkProxy: IKyberNetworkProxy as ContractArtifact,
|
||||
IUniswapExchange: IUniswapExchange as ContractArtifact,
|
||||
IUniswapExchangeFactory: IUniswapExchangeFactory as ContractArtifact,
|
||||
IUniswapV2Router01: IUniswapV2Router01 as ContractArtifact,
|
||||
TestChaiBridge: TestChaiBridge as ContractArtifact,
|
||||
TestDexForwarderBridge: TestDexForwarderBridge as ContractArtifact,
|
||||
TestDydxBridge: TestDydxBridge as ContractArtifact,
|
||||
@@ -84,4 +89,5 @@ export const artifacts = {
|
||||
TestKyberBridge: TestKyberBridge as ContractArtifact,
|
||||
TestStaticCallTarget: TestStaticCallTarget as ContractArtifact,
|
||||
TestUniswapBridge: TestUniswapBridge as ContractArtifact,
|
||||
TestUniswapV2Bridge: TestUniswapV2Bridge as ContractArtifact,
|
||||
};
|
||||
|
216
contracts/asset-proxy/test/uniswapv2_bridge.ts
Normal file
216
contracts/asset-proxy/test/uniswapv2_bridge.ts
Normal file
@@ -0,0 +1,216 @@
|
||||
import {
|
||||
blockchainTests,
|
||||
constants,
|
||||
expect,
|
||||
filterLogsToArguments,
|
||||
getRandomInteger,
|
||||
randomAddress,
|
||||
} from '@0x/contracts-test-utils';
|
||||
import { AssetProxyId } from '@0x/types';
|
||||
import { AbiEncoder, BigNumber, hexUtils } from '@0x/utils';
|
||||
import { DecodedLogs } from 'ethereum-types';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { artifacts } from './artifacts';
|
||||
|
||||
import {
|
||||
TestUniswapV2BridgeContract,
|
||||
TestUniswapV2BridgeEvents as ContractEvents,
|
||||
TestUniswapV2BridgeSwapExactTokensForTokensInputEventArgs as SwapExactTokensForTokensArgs,
|
||||
TestUniswapV2BridgeTokenApproveEventArgs as TokenApproveArgs,
|
||||
TestUniswapV2BridgeTokenTransferEventArgs as TokenTransferArgs,
|
||||
} from './wrappers';
|
||||
|
||||
blockchainTests.resets('UniswapV2 unit tests', env => {
|
||||
const FROM_TOKEN_DECIMALS = 6;
|
||||
const TO_TOKEN_DECIMALS = 18;
|
||||
const FROM_TOKEN_BASE = new BigNumber(10).pow(FROM_TOKEN_DECIMALS);
|
||||
const TO_TOKEN_BASE = new BigNumber(10).pow(TO_TOKEN_DECIMALS);
|
||||
let testContract: TestUniswapV2BridgeContract;
|
||||
|
||||
before(async () => {
|
||||
testContract = await TestUniswapV2BridgeContract.deployFrom0xArtifactAsync(
|
||||
artifacts.TestUniswapV2Bridge,
|
||||
env.provider,
|
||||
env.txDefaults,
|
||||
artifacts,
|
||||
);
|
||||
});
|
||||
|
||||
describe('isValidSignature()', () => {
|
||||
it('returns success bytes', async () => {
|
||||
const LEGACY_WALLET_MAGIC_VALUE = '0xb0671381';
|
||||
const result = await testContract
|
||||
.isValidSignature(hexUtils.random(), hexUtils.random(_.random(0, 32)))
|
||||
.callAsync();
|
||||
expect(result).to.eq(LEGACY_WALLET_MAGIC_VALUE);
|
||||
});
|
||||
});
|
||||
|
||||
describe('bridgeTransferFrom()', () => {
|
||||
interface TransferFromOpts {
|
||||
tokenAddressesPath: string[];
|
||||
toAddress: string;
|
||||
// Amount to pass into `bridgeTransferFrom()`
|
||||
amount: BigNumber;
|
||||
// Token balance of the bridge.
|
||||
fromTokenBalance: BigNumber;
|
||||
// Router reverts with this reason
|
||||
routerRevertReason: string;
|
||||
}
|
||||
|
||||
interface TransferFromResult {
|
||||
opts: TransferFromOpts;
|
||||
result: string;
|
||||
logs: DecodedLogs;
|
||||
blocktime: number;
|
||||
}
|
||||
|
||||
function createTransferFromOpts(opts?: Partial<TransferFromOpts>): TransferFromOpts {
|
||||
const amount = getRandomInteger(1, TO_TOKEN_BASE.times(100));
|
||||
return {
|
||||
tokenAddressesPath: Array(2).fill(constants.NULL_ADDRESS),
|
||||
amount,
|
||||
toAddress: randomAddress(),
|
||||
fromTokenBalance: getRandomInteger(1, FROM_TOKEN_BASE.times(100)),
|
||||
routerRevertReason: '',
|
||||
...opts,
|
||||
};
|
||||
}
|
||||
|
||||
const bridgeDataEncoder = AbiEncoder.create('(address[])');
|
||||
|
||||
async function transferFromAsync(opts?: Partial<TransferFromOpts>): Promise<TransferFromResult> {
|
||||
const _opts = createTransferFromOpts(opts);
|
||||
|
||||
for (let i = 0; i < _opts.tokenAddressesPath.length; i++) {
|
||||
const createFromTokenFn = testContract.createToken(_opts.tokenAddressesPath[i]);
|
||||
_opts.tokenAddressesPath[i] = await createFromTokenFn.callAsync();
|
||||
await createFromTokenFn.awaitTransactionSuccessAsync();
|
||||
}
|
||||
|
||||
// Set the token balance for the token we're converting from.
|
||||
await testContract
|
||||
.setTokenBalance(_opts.tokenAddressesPath[0], _opts.fromTokenBalance)
|
||||
.awaitTransactionSuccessAsync();
|
||||
|
||||
// Set revert reason for the router.
|
||||
await testContract.setRouterRevertReason(_opts.routerRevertReason).awaitTransactionSuccessAsync();
|
||||
|
||||
// Call bridgeTransferFrom().
|
||||
const bridgeTransferFromFn = testContract.bridgeTransferFrom(
|
||||
// Output token
|
||||
_opts.tokenAddressesPath[_opts.tokenAddressesPath.length - 1],
|
||||
// Random maker address.
|
||||
randomAddress(),
|
||||
// Recipient address.
|
||||
_opts.toAddress,
|
||||
// Transfer amount.
|
||||
_opts.amount,
|
||||
// ABI-encode the input token address as the bridge data. // FIXME
|
||||
bridgeDataEncoder.encode([_opts.tokenAddressesPath]),
|
||||
);
|
||||
const result = await bridgeTransferFromFn.callAsync();
|
||||
const receipt = await bridgeTransferFromFn.awaitTransactionSuccessAsync();
|
||||
return {
|
||||
opts: _opts,
|
||||
result,
|
||||
logs: (receipt.logs as any) as DecodedLogs,
|
||||
blocktime: await env.web3Wrapper.getBlockTimestampAsync(receipt.blockNumber),
|
||||
};
|
||||
}
|
||||
|
||||
it('returns magic bytes on success', async () => {
|
||||
const { result } = await transferFromAsync();
|
||||
expect(result).to.eq(AssetProxyId.ERC20Bridge);
|
||||
});
|
||||
|
||||
it('performs transfer when both tokens are the same', async () => {
|
||||
const createTokenFn = testContract.createToken(constants.NULL_ADDRESS);
|
||||
const tokenAddress = await createTokenFn.callAsync();
|
||||
await createTokenFn.awaitTransactionSuccessAsync();
|
||||
|
||||
const { opts, result, logs } = await transferFromAsync({
|
||||
tokenAddressesPath: [tokenAddress, tokenAddress],
|
||||
});
|
||||
expect(result).to.eq(AssetProxyId.ERC20Bridge, 'asset proxy id');
|
||||
const transfers = filterLogsToArguments<TokenTransferArgs>(logs, ContractEvents.TokenTransfer);
|
||||
|
||||
expect(transfers.length).to.eq(1);
|
||||
expect(transfers[0].token).to.eq(tokenAddress, 'input token address');
|
||||
expect(transfers[0].from).to.eq(testContract.address);
|
||||
expect(transfers[0].to).to.eq(opts.toAddress, 'recipient address');
|
||||
expect(transfers[0].amount).to.bignumber.eq(opts.amount, 'amount');
|
||||
});
|
||||
|
||||
describe('token -> token', async () => {
|
||||
it('calls UniswapV2Router01.swapExactTokensForTokens()', async () => {
|
||||
const { opts, result, logs, blocktime } = await transferFromAsync();
|
||||
expect(result).to.eq(AssetProxyId.ERC20Bridge, 'asset proxy id');
|
||||
const transfers = filterLogsToArguments<SwapExactTokensForTokensArgs>(
|
||||
logs,
|
||||
ContractEvents.SwapExactTokensForTokensInput,
|
||||
);
|
||||
|
||||
expect(transfers.length).to.eq(1);
|
||||
expect(transfers[0].toTokenAddress).to.eq(
|
||||
opts.tokenAddressesPath[opts.tokenAddressesPath.length - 1],
|
||||
'output token address',
|
||||
);
|
||||
expect(transfers[0].to).to.eq(opts.toAddress, 'recipient address');
|
||||
expect(transfers[0].amountIn).to.bignumber.eq(opts.fromTokenBalance, 'input token amount');
|
||||
expect(transfers[0].amountOutMin).to.bignumber.eq(opts.amount, 'output token amount');
|
||||
expect(transfers[0].deadline).to.bignumber.eq(blocktime, 'deadline');
|
||||
});
|
||||
|
||||
it('sets allowance for "from" token', async () => {
|
||||
const { logs } = await transferFromAsync();
|
||||
const approvals = filterLogsToArguments<TokenApproveArgs>(logs, ContractEvents.TokenApprove);
|
||||
const routerAddress = await testContract.getRouterAddress().callAsync();
|
||||
expect(approvals.length).to.eq(1);
|
||||
expect(approvals[0].spender).to.eq(routerAddress);
|
||||
expect(approvals[0].allowance).to.bignumber.eq(constants.MAX_UINT256);
|
||||
});
|
||||
|
||||
it('sets allowance for "from" token on subsequent calls', async () => {
|
||||
const { opts } = await transferFromAsync();
|
||||
const { logs } = await transferFromAsync(opts);
|
||||
const approvals = filterLogsToArguments<TokenApproveArgs>(logs, ContractEvents.TokenApprove);
|
||||
const routerAddress = await testContract.getRouterAddress().callAsync();
|
||||
expect(approvals.length).to.eq(1);
|
||||
expect(approvals[0].spender).to.eq(routerAddress);
|
||||
expect(approvals[0].allowance).to.bignumber.eq(constants.MAX_UINT256);
|
||||
});
|
||||
|
||||
it('fails if the router fails', async () => {
|
||||
const revertReason = 'FOOBAR';
|
||||
const tx = transferFromAsync({
|
||||
routerRevertReason: revertReason,
|
||||
});
|
||||
return expect(tx).to.eventually.be.rejectedWith(revertReason);
|
||||
});
|
||||
});
|
||||
describe('token -> token -> token', async () => {
|
||||
it('calls UniswapV2Router01.swapExactTokensForTokens()', async () => {
|
||||
const { opts, result, logs, blocktime } = await transferFromAsync({
|
||||
tokenAddressesPath: Array(3).fill(constants.NULL_ADDRESS),
|
||||
});
|
||||
expect(result).to.eq(AssetProxyId.ERC20Bridge, 'asset proxy id');
|
||||
const transfers = filterLogsToArguments<SwapExactTokensForTokensArgs>(
|
||||
logs,
|
||||
ContractEvents.SwapExactTokensForTokensInput,
|
||||
);
|
||||
|
||||
expect(transfers.length).to.eq(1);
|
||||
expect(transfers[0].toTokenAddress).to.eq(
|
||||
opts.tokenAddressesPath[opts.tokenAddressesPath.length - 1],
|
||||
'output token address',
|
||||
);
|
||||
expect(transfers[0].to).to.eq(opts.toAddress, 'recipient address');
|
||||
expect(transfers[0].amountIn).to.bignumber.eq(opts.fromTokenBalance, 'input token amount');
|
||||
expect(transfers[0].amountOutMin).to.bignumber.eq(opts.amount, 'output token amount');
|
||||
expect(transfers[0].deadline).to.bignumber.eq(blocktime, 'deadline');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@@ -26,6 +26,7 @@ export * from '../test/generated-wrappers/i_gas_token';
|
||||
export * from '../test/generated-wrappers/i_kyber_network_proxy';
|
||||
export * from '../test/generated-wrappers/i_uniswap_exchange';
|
||||
export * from '../test/generated-wrappers/i_uniswap_exchange_factory';
|
||||
export * from '../test/generated-wrappers/i_uniswap_v2_router01';
|
||||
export * from '../test/generated-wrappers/kyber_bridge';
|
||||
export * from '../test/generated-wrappers/mixin_asset_proxy_dispatcher';
|
||||
export * from '../test/generated-wrappers/mixin_authorizable';
|
||||
@@ -41,4 +42,6 @@ export * from '../test/generated-wrappers/test_eth2_dai_bridge';
|
||||
export * from '../test/generated-wrappers/test_kyber_bridge';
|
||||
export * from '../test/generated-wrappers/test_static_call_target';
|
||||
export * from '../test/generated-wrappers/test_uniswap_bridge';
|
||||
export * from '../test/generated-wrappers/test_uniswap_v2_bridge';
|
||||
export * from '../test/generated-wrappers/uniswap_bridge';
|
||||
export * from '../test/generated-wrappers/uniswap_v2_bridge';
|
||||
|
Reference in New Issue
Block a user