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
This commit is contained in:
Alex Kroeger 2020-12-03 14:31:45 -08:00 committed by GitHub
parent 23ee108089
commit 0a37a588e8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 163 additions and 4 deletions

View File

@ -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;
}
}

View File

@ -36,9 +36,9 @@
"publish:private": "yarn build && gitpkg publish" "publish:private": "yarn build && gitpkg publish"
}, },
"config": { "config": {
"publicInterfaceContracts": "ERC20BridgeSampler", "publicInterfaceContracts": "ERC20BridgeSampler,BalanceChecker",
"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.",
"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": { "postpublish": {
"assets": [] "assets": []
} }

View File

@ -5,5 +5,9 @@
*/ */
import { ContractArtifact } from 'ethereum-types'; import { ContractArtifact } from 'ethereum-types';
import * as BalanceChecker from '../generated-artifacts/BalanceChecker.json';
import * as ERC20BridgeSampler from '../generated-artifacts/ERC20BridgeSampler.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,
};

View File

@ -177,7 +177,7 @@ export {
} from './utils/quote_report_generator'; } from './utils/quote_report_generator';
export { QuoteRequestor } from './utils/quote_requestor'; export { QuoteRequestor } from './utils/quote_requestor';
export { rfqtMocker } from './utils/rfqt_mocker'; export { rfqtMocker } from './utils/rfqt_mocker';
export { ERC20BridgeSamplerContract } from './wrappers'; export { ERC20BridgeSamplerContract, BalanceCheckerContract } from './wrappers';
import { ERC20BridgeSource } from './utils/market_operation_utils/types'; import { ERC20BridgeSource } from './utils/market_operation_utils/types';
export type Native = ERC20BridgeSource.Native; export type Native = ERC20BridgeSource.Native;
export type MultiHop = ERC20BridgeSource.MultiHop; export type MultiHop = ERC20BridgeSource.MultiHop;

View File

@ -3,4 +3,5 @@
* Warning: This file is auto-generated by contracts-gen. Don't edit manually. * 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'; export * from '../generated-wrappers/erc20_bridge_sampler';

View File

@ -6,6 +6,7 @@
import { ContractArtifact } from 'ethereum-types'; import { ContractArtifact } from 'ethereum-types';
import * as ApproximateBuys from '../test/generated-artifacts/ApproximateBuys.json'; 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 BalancerSampler from '../test/generated-artifacts/BalancerSampler.json';
import * as CurveSampler from '../test/generated-artifacts/CurveSampler.json'; import * as CurveSampler from '../test/generated-artifacts/CurveSampler.json';
import * as DeploymentConstants from '../test/generated-artifacts/DeploymentConstants.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'; import * as UniswapV2Sampler from '../test/generated-artifacts/UniswapV2Sampler.json';
export const artifacts = { export const artifacts = {
ApproximateBuys: ApproximateBuys as ContractArtifact, ApproximateBuys: ApproximateBuys as ContractArtifact,
BalanceChecker: BalanceChecker as ContractArtifact,
BalancerSampler: BalancerSampler as ContractArtifact, BalancerSampler: BalancerSampler as ContractArtifact,
CurveSampler: CurveSampler as ContractArtifact, CurveSampler: CurveSampler as ContractArtifact,
DODOSampler: DODOSampler as ContractArtifact, DODOSampler: DODOSampler as ContractArtifact,

View File

@ -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');
}
});
});
});

View File

@ -4,6 +4,7 @@
* ----------------------------------------------------------------------------- * -----------------------------------------------------------------------------
*/ */
export * from '../test/generated-wrappers/approximate_buys'; 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/balancer_sampler';
export * from '../test/generated-wrappers/curve_sampler'; export * from '../test/generated-wrappers/curve_sampler';
export * from '../test/generated-wrappers/d_o_d_o_sampler'; export * from '../test/generated-wrappers/d_o_d_o_sampler';

View File

@ -3,8 +3,10 @@
"compilerOptions": { "outDir": "lib", "rootDir": ".", "resolveJsonModule": true }, "compilerOptions": { "outDir": "lib", "rootDir": ".", "resolveJsonModule": true },
"include": ["./src/**/*", "./test/**/*", "./generated-wrappers/**/*"], "include": ["./src/**/*", "./test/**/*", "./generated-wrappers/**/*"],
"files": [ "files": [
"generated-artifacts/BalanceChecker.json",
"generated-artifacts/ERC20BridgeSampler.json", "generated-artifacts/ERC20BridgeSampler.json",
"test/generated-artifacts/ApproximateBuys.json", "test/generated-artifacts/ApproximateBuys.json",
"test/generated-artifacts/BalanceChecker.json",
"test/generated-artifacts/BalancerSampler.json", "test/generated-artifacts/BalancerSampler.json",
"test/generated-artifacts/CurveSampler.json", "test/generated-artifacts/CurveSampler.json",
"test/generated-artifacts/DODOSampler.json", "test/generated-artifacts/DODOSampler.json",