From 0a37a588e85209747b1dc367756266941acaeba2 Mon Sep 17 00:00:00 2001 From: Alex Kroeger Date: Thu, 3 Dec 2020 14:31:45 -0800 Subject: [PATCH] Added BalanceChecker contract (#60) * Added BalanceChecker contract * Upgraded to solidity 0.6, simplified contract, added tests * uint -> uint256 * export BalanceChecker contract wrapper * prettier * removed superfluous test code * prettier --- .../contracts/src/BalanceChecker.sol | 89 +++++++++++++++++++ packages/asset-swapper/package.json | 4 +- packages/asset-swapper/src/artifacts.ts | 6 +- packages/asset-swapper/src/index.ts | 2 +- packages/asset-swapper/src/wrappers.ts | 1 + packages/asset-swapper/test/artifacts.ts | 2 + .../test/contracts/balance_checker_test.ts | 60 +++++++++++++ packages/asset-swapper/test/wrappers.ts | 1 + packages/asset-swapper/tsconfig.json | 2 + 9 files changed, 163 insertions(+), 4 deletions(-) create mode 100644 packages/asset-swapper/contracts/src/BalanceChecker.sol create mode 100644 packages/asset-swapper/test/contracts/balance_checker_test.ts diff --git a/packages/asset-swapper/contracts/src/BalanceChecker.sol b/packages/asset-swapper/contracts/src/BalanceChecker.sol new file mode 100644 index 0000000000..7a062061fd --- /dev/null +++ b/packages/asset-swapper/contracts/src/BalanceChecker.sol @@ -0,0 +1,89 @@ +/* + + 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.6; + +// ERC20 contract interface +abstract contract IToken { + /// @dev Query the balance of owner + /// @param _owner The address from which the balance will be retrieved + /// @return Balance of owner + function balanceOf(address _owner) public virtual view returns (uint256); + + /// @param _owner The address of the account owning tokens + /// @param _spender The address of the account able to transfer the tokens + /// @return Amount of remaining tokens allowed to spent + function allowance(address _owner, address _spender) public virtual view returns (uint256); +} + +contract BalanceChecker { + /* + Check the token balances of wallet-token pairs. + Pass 0xeee... as a "token" address to get ETH balance. + Possible error throws: + - extremely large arrays for user and or tokens (gas cost too high) + + Returns a one-dimensional that's user.length long. + */ + function balances(address[] calldata users, address[] calldata tokens) external view returns (uint256[] memory) { + // make sure the users array and tokens array are of equal length + require(users.length == tokens.length, "users array is a different length than the tokens array"); + + uint256[] memory addrBalances = new uint256[](users.length); + + for(uint i = 0; i < users.length; i++) { + if (tokens[i] != address(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE)) { + addrBalances[i] = IToken(tokens[i]).balanceOf(users[i]); + } else { + addrBalances[i] = users[i].balance; // ETH balance + } + } + + return addrBalances; + } + + /* + Check the allowances of an array of owner-spender-tokens + + Returns 0 for 0xeee... (ETH) + Possible error throws: + - extremely large arrays for user and or tokens (gas cost too high) + + Returns a one-dimensional array that's owners.length long. + */ + function allowances(address[] calldata owners, address[] calldata spenders, address[] calldata tokens) external view returns (uint256[] memory) { + // make sure the arrays are all of equal length + require(owners.length == spenders.length, "all arrays must be of equal length"); + require(owners.length == tokens.length, "all arrays must be of equal length"); + + uint256[] memory addrAllowances = new uint256[](owners.length); + + for(uint i = 0; i < owners.length; i++) { + if (tokens[i] != address(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE)) { + addrAllowances[i] = IToken(tokens[i]).allowance(owners[i], spenders[i]); + } else { + // ETH + addrAllowances[i] = 0; + } + } + + return addrAllowances; + } + + +} diff --git a/packages/asset-swapper/package.json b/packages/asset-swapper/package.json index 7006faf55b..aeb9216807 100644 --- a/packages/asset-swapper/package.json +++ b/packages/asset-swapper/package.json @@ -36,9 +36,9 @@ "publish:private": "yarn build && gitpkg publish" }, "config": { - "publicInterfaceContracts": "ERC20BridgeSampler", + "publicInterfaceContracts": "ERC20BridgeSampler,BalanceChecker", "abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.", - "abis": "./test/generated-artifacts/@(ApproximateBuys|BalancerSampler|CurveSampler|DODOSampler|DeploymentConstants|DummyLiquidityProvider|ERC20BridgeSampler|Eth2DaiSampler|IBalancer|ICurve|IEth2Dai|IKyberNetwork|IMStable|IMooniswap|IMultiBridge|IShell|IUniswapExchangeQuotes|IUniswapV2Router01|KyberSampler|LiquidityProviderSampler|MStableSampler|MooniswapSampler|MultiBridgeSampler|NativeOrderSampler|SamplerUtils|ShellSampler|SushiSwapSampler|TestERC20BridgeSampler|TestNativeOrderSampler|TwoHopSampler|UniswapSampler|UniswapV2Sampler).json", + "abis": "./test/generated-artifacts/@(ApproximateBuys|BalanceChecker|BalancerSampler|CurveSampler|DODOSampler|DeploymentConstants|DummyLiquidityProvider|ERC20BridgeSampler|Eth2DaiSampler|IBalancer|ICurve|IEth2Dai|IKyberNetwork|IMStable|IMooniswap|IMultiBridge|IShell|IUniswapExchangeQuotes|IUniswapV2Router01|KyberSampler|LiquidityProviderSampler|MStableSampler|MooniswapSampler|MultiBridgeSampler|NativeOrderSampler|SamplerUtils|ShellSampler|SushiSwapSampler|TestERC20BridgeSampler|TestNativeOrderSampler|TwoHopSampler|UniswapSampler|UniswapV2Sampler).json", "postpublish": { "assets": [] } diff --git a/packages/asset-swapper/src/artifacts.ts b/packages/asset-swapper/src/artifacts.ts index 7cb876ed6c..da0b6ac0cd 100644 --- a/packages/asset-swapper/src/artifacts.ts +++ b/packages/asset-swapper/src/artifacts.ts @@ -5,5 +5,9 @@ */ import { ContractArtifact } from 'ethereum-types'; +import * as BalanceChecker from '../generated-artifacts/BalanceChecker.json'; import * as ERC20BridgeSampler from '../generated-artifacts/ERC20BridgeSampler.json'; -export const artifacts = { ERC20BridgeSampler: ERC20BridgeSampler as ContractArtifact }; +export const artifacts = { + ERC20BridgeSampler: ERC20BridgeSampler as ContractArtifact, + BalanceChecker: BalanceChecker as ContractArtifact, +}; diff --git a/packages/asset-swapper/src/index.ts b/packages/asset-swapper/src/index.ts index db5bef6e7f..1fbaa783f4 100644 --- a/packages/asset-swapper/src/index.ts +++ b/packages/asset-swapper/src/index.ts @@ -177,7 +177,7 @@ export { } from './utils/quote_report_generator'; export { QuoteRequestor } from './utils/quote_requestor'; export { rfqtMocker } from './utils/rfqt_mocker'; -export { ERC20BridgeSamplerContract } from './wrappers'; +export { ERC20BridgeSamplerContract, BalanceCheckerContract } from './wrappers'; import { ERC20BridgeSource } from './utils/market_operation_utils/types'; export type Native = ERC20BridgeSource.Native; export type MultiHop = ERC20BridgeSource.MultiHop; diff --git a/packages/asset-swapper/src/wrappers.ts b/packages/asset-swapper/src/wrappers.ts index 3ae69ad238..5d8c796e4c 100644 --- a/packages/asset-swapper/src/wrappers.ts +++ b/packages/asset-swapper/src/wrappers.ts @@ -3,4 +3,5 @@ * Warning: This file is auto-generated by contracts-gen. Don't edit manually. * ----------------------------------------------------------------------------- */ +export * from '../generated-wrappers/balance_checker'; export * from '../generated-wrappers/erc20_bridge_sampler'; diff --git a/packages/asset-swapper/test/artifacts.ts b/packages/asset-swapper/test/artifacts.ts index fd5ae276e0..fa45737121 100644 --- a/packages/asset-swapper/test/artifacts.ts +++ b/packages/asset-swapper/test/artifacts.ts @@ -6,6 +6,7 @@ import { ContractArtifact } from 'ethereum-types'; import * as ApproximateBuys from '../test/generated-artifacts/ApproximateBuys.json'; +import * as BalanceChecker from '../test/generated-artifacts/BalanceChecker.json'; import * as BalancerSampler from '../test/generated-artifacts/BalancerSampler.json'; import * as CurveSampler from '../test/generated-artifacts/CurveSampler.json'; import * as DeploymentConstants from '../test/generated-artifacts/DeploymentConstants.json'; @@ -39,6 +40,7 @@ import * as UniswapSampler from '../test/generated-artifacts/UniswapSampler.json import * as UniswapV2Sampler from '../test/generated-artifacts/UniswapV2Sampler.json'; export const artifacts = { ApproximateBuys: ApproximateBuys as ContractArtifact, + BalanceChecker: BalanceChecker as ContractArtifact, BalancerSampler: BalancerSampler as ContractArtifact, CurveSampler: CurveSampler as ContractArtifact, DODOSampler: DODOSampler as ContractArtifact, diff --git a/packages/asset-swapper/test/contracts/balance_checker_test.ts b/packages/asset-swapper/test/contracts/balance_checker_test.ts new file mode 100644 index 0000000000..0ea3bb5e94 --- /dev/null +++ b/packages/asset-swapper/test/contracts/balance_checker_test.ts @@ -0,0 +1,60 @@ +import { artifacts as erc20Artifacts, DummyERC20TokenContract } from '@0x/contracts-erc20'; +import { blockchainTests, constants, expect, web3Wrapper } from '@0x/contracts-test-utils'; +import { BigNumber } from '@0x/utils'; +import * as _ from 'lodash'; + +import { artifacts } from '../artifacts'; +import { BalanceCheckerContract } from '../wrappers'; + +// tslint:disable: custom-no-magic-numbers + +const ETH_ADDRESS = '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee'; + +blockchainTests.resets('BalanceChecker contract', env => { + let contract: BalanceCheckerContract; + + before(async () => { + contract = await BalanceCheckerContract.deployFrom0xArtifactAsync( + artifacts.BalanceChecker, + env.provider, + env.txDefaults, + {}, + ); + }); + + describe('getBalances', () => { + it('returns the correct array for a successful call', async () => { + const makerToken = await DummyERC20TokenContract.deployFrom0xArtifactAsync( + erc20Artifacts.DummyERC20Token, + env.provider, + env.txDefaults, + artifacts, + constants.DUMMY_TOKEN_NAME, + constants.DUMMY_TOKEN_SYMBOL, + new BigNumber(18), + constants.DUMMY_TOKEN_TOTAL_SUPPLY, + ); + + const accounts = await web3Wrapper.getAvailableAddressesAsync(); + const owner = accounts[0]; + const owner2 = accounts[1]; + + await makerToken.mint(new BigNumber(100)).awaitTransactionSuccessAsync({ from: owner }); + + const testResults = await contract.balances([owner, owner2], [makerToken.address, ETH_ADDRESS]).callAsync(); + + expect(testResults).to.eql([new BigNumber(100), new BigNumber(100000000000000000000)]); + }); + it('it throws an error if the input arrays of different lengths', async () => { + const accounts = await web3Wrapper.getAvailableAddressesAsync(); + const owner = accounts[0]; + + try { + await contract.balances([owner], [ETH_ADDRESS, ETH_ADDRESS]).callAsync(); + expect.fail(); + } catch (error) { + expect(error.message).to.eql('users array is a different length than the tokens array'); + } + }); + }); +}); diff --git a/packages/asset-swapper/test/wrappers.ts b/packages/asset-swapper/test/wrappers.ts index 9a49445ce8..ebb2482d6d 100644 --- a/packages/asset-swapper/test/wrappers.ts +++ b/packages/asset-swapper/test/wrappers.ts @@ -4,6 +4,7 @@ * ----------------------------------------------------------------------------- */ export * from '../test/generated-wrappers/approximate_buys'; +export * from '../test/generated-wrappers/balance_checker'; export * from '../test/generated-wrappers/balancer_sampler'; export * from '../test/generated-wrappers/curve_sampler'; export * from '../test/generated-wrappers/d_o_d_o_sampler'; diff --git a/packages/asset-swapper/tsconfig.json b/packages/asset-swapper/tsconfig.json index e17ffb37a8..8f51a33066 100644 --- a/packages/asset-swapper/tsconfig.json +++ b/packages/asset-swapper/tsconfig.json @@ -3,8 +3,10 @@ "compilerOptions": { "outDir": "lib", "rootDir": ".", "resolveJsonModule": true }, "include": ["./src/**/*", "./test/**/*", "./generated-wrappers/**/*"], "files": [ + "generated-artifacts/BalanceChecker.json", "generated-artifacts/ERC20BridgeSampler.json", "test/generated-artifacts/ApproximateBuys.json", + "test/generated-artifacts/BalanceChecker.json", "test/generated-artifacts/BalancerSampler.json", "test/generated-artifacts/CurveSampler.json", "test/generated-artifacts/DODOSampler.json",