* Bancor Bridge contract * refactor Quote and FillData types * BancorService (wrapper for the Bancor SDK) * disable bancor while waiting for bancor SDK update * add bancor to test
		
			
				
	
	
		
			206 lines
		
	
	
		
			9.1 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			206 lines
		
	
	
		
			9.1 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| 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 { TestBancorBridgeContract } from './generated-wrappers/test_bancor_bridge';
 | |
| import {
 | |
|     TestBancorBridgeConvertByPathInputEventArgs as ConvertByPathArgs,
 | |
|     TestBancorBridgeEvents as ContractEvents,
 | |
|     TestBancorBridgeTokenApproveEventArgs as TokenApproveArgs,
 | |
|     TestBancorBridgeTokenTransferEventArgs as TokenTransferArgs,
 | |
| } from './wrappers';
 | |
| 
 | |
| blockchainTests.resets('Bancor 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: TestBancorBridgeContract;
 | |
| 
 | |
|     before(async () => {
 | |
|         testContract = await TestBancorBridgeContract.deployFrom0xArtifactAsync(
 | |
|             artifacts.TestBancorBridge,
 | |
|             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(3).fill(constants.NULL_ADDRESS),
 | |
|                 amount,
 | |
|                 toAddress: randomAddress(),
 | |
|                 fromTokenBalance: getRandomInteger(1, FROM_TOKEN_BASE.times(100)),
 | |
|                 routerRevertReason: '',
 | |
|                 ...opts,
 | |
|             };
 | |
|         }
 | |
| 
 | |
|         const bridgeDataEncoder = AbiEncoder.create('(address[], 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.setNetworkRevertReason(_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.
 | |
|                 bridgeDataEncoder.encode([
 | |
|                     _opts.tokenAddressesPath,
 | |
|                     await testContract.getNetworkAddress().callAsync(),
 | |
|                 ]),
 | |
|             );
 | |
|             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 BancorNetwork.convertByPath()', async () => {
 | |
|                 const { opts, result, logs } = await transferFromAsync();
 | |
|                 expect(result).to.eq(AssetProxyId.ERC20Bridge, 'asset proxy id');
 | |
|                 const transfers = filterLogsToArguments<ConvertByPathArgs>(logs, ContractEvents.ConvertByPathInput);
 | |
| 
 | |
|                 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].feeRecipient).to.eq(constants.NULL_ADDRESS);
 | |
|                 expect(transfers[0].feeAmount).to.bignumber.eq(new BigNumber(0));
 | |
|             });
 | |
| 
 | |
|             it('sets allowance for "from" token', async () => {
 | |
|                 const { logs } = await transferFromAsync();
 | |
|                 const approvals = filterLogsToArguments<TokenApproveArgs>(logs, ContractEvents.TokenApprove);
 | |
|                 const networkAddress = await testContract.getNetworkAddress().callAsync();
 | |
|                 expect(approvals.length).to.eq(1);
 | |
|                 expect(approvals[0].spender).to.eq(networkAddress);
 | |
|                 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 BancorNetwork.convertByPath()', async () => {
 | |
|                 const { opts, result, logs } = await transferFromAsync({
 | |
|                     tokenAddressesPath: Array(5).fill(constants.NULL_ADDRESS),
 | |
|                 });
 | |
|                 expect(result).to.eq(AssetProxyId.ERC20Bridge, 'asset proxy id');
 | |
|                 const transfers = filterLogsToArguments<ConvertByPathArgs>(logs, ContractEvents.ConvertByPathInput);
 | |
| 
 | |
|                 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].feeRecipient).to.eq(constants.NULL_ADDRESS);
 | |
|                 expect(transfers[0].feeAmount).to.bignumber.eq(new BigNumber(0));
 | |
|             });
 | |
|         });
 | |
|     });
 | |
| });
 |