aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md6
-rw-r--r--src/contract_wrappers/token_wrapper.ts31
-rw-r--r--src/utils/constants.ts3
-rw-r--r--test/token_wrapper_test.ts52
4 files changed, 91 insertions, 1 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index df161b3a2..ad72b3d04 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,11 @@
# CHANGELOG
+v0.11.0 - _TBD_
+------------------------
+ * Added `zeroEx.token.setUnlimitedProxyAllowanceAsync` (#137)
+ * Added `zeroEx.token.setUnlimitedAllowanceAsync` (#137)
+ * Added `zeroEx.token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS` (#137)
+
v0.10.4 - _Aug 24, 2017_
------------------------
* Fixed a bug where checksummed addresses were being pulled from artifacts and not lower-cased. (#135)
diff --git a/src/contract_wrappers/token_wrapper.ts b/src/contract_wrappers/token_wrapper.ts
index 8c1bf6b52..a2812ccdb 100644
--- a/src/contract_wrappers/token_wrapper.ts
+++ b/src/contract_wrappers/token_wrapper.ts
@@ -21,7 +21,7 @@ import {
ContractEventObj,
} from '../types';
-const ALLOWANCE_TO_ZERO_GAS_AMOUNT = 45730;
+const ALLOWANCE_TO_ZERO_GAS_AMOUNT = 47155;
/**
* This class includes all the functionality related to interacting with ERC20 token contracts.
@@ -29,6 +29,7 @@ const ALLOWANCE_TO_ZERO_GAS_AMOUNT = 45730;
* to the 0x Proxy smart contract.
*/
export class TokenWrapper extends ContractWrapper {
+ public UNLIMITED_ALLOWANCE_IN_BASE_UNITS = constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS;
private _tokenContractsByAddress: {[address: string]: TokenContract};
private _tokenLogEventEmitters: ContractEventEmitter[];
constructor(web3Wrapper: Web3Wrapper) {
@@ -80,6 +81,22 @@ export class TokenWrapper extends ContractWrapper {
});
}
/**
+ * Sets the spender's allowance to an unlimited number of baseUnits on behalf of the owner address.
+ * Equivalent to the ERC20 spec method `approve`.
+ * Setting an unlimited allowance will lower the gas cost for filling orders involving tokens that forego updating
+ * allowances set to the max amount (e.g ZRX, WETH)
+ * @param tokenAddress The hex encoded contract Ethereum address where the ERC20 token is deployed.
+ * @param ownerAddress The hex encoded user Ethereum address who would like to set an allowance
+ * for spenderAddress.
+ * @param spenderAddress The hex encoded user Ethereum address who will be able to spend the set allowance.
+ */
+ public async setUnlimitedAllowanceAsync(tokenAddress: string, ownerAddress: string,
+ spenderAddress: string): Promise<void> {
+ await this.setAllowanceAsync(
+ tokenAddress, ownerAddress, spenderAddress, this.UNLIMITED_ALLOWANCE_IN_BASE_UNITS,
+ );
+ }
+ /**
* Retrieves the owners allowance in baseUnits set to the spender's address.
* @param tokenAddress The hex encoded contract Ethereum address where the ERC20 token is deployed.
* @param ownerAddress The hex encoded user Ethereum address whose allowance to spenderAddress
@@ -127,6 +144,18 @@ export class TokenWrapper extends ContractWrapper {
await this.setAllowanceAsync(tokenAddress, ownerAddress, proxyAddress, amountInBaseUnits);
}
/**
+ * Sets the 0x proxy contract's allowance to a unlimited number of a tokens' baseUnits on behalf
+ * of an owner address.
+ * Setting an unlimited allowance will lower the gas cost for filling orders involving tokens that forego updating
+ * allowances set to the max amount (e.g ZRX, WETH)
+ * @param tokenAddress The hex encoded contract Ethereum address where the ERC20 token is deployed.
+ * @param ownerAddress The hex encoded user Ethereum address who is setting an allowance
+ * for the Proxy contract.
+ */
+ public async setUnlimitedProxyAllowanceAsync(tokenAddress: string, ownerAddress: string): Promise<void> {
+ await this.setProxyAllowanceAsync(tokenAddress, ownerAddress, this.UNLIMITED_ALLOWANCE_IN_BASE_UNITS);
+ }
+ /**
* Transfers `amountInBaseUnits` ERC20 tokens from `fromAddress` to `toAddress`.
* @param tokenAddress The hex encoded contract Ethereum address where the ERC20 token is deployed.
* @param fromAddress The hex encoded user Ethereum address that will send the funds.
diff --git a/src/utils/constants.ts b/src/utils/constants.ts
index d56ee16c5..1b11d7055 100644
--- a/src/utils/constants.ts
+++ b/src/utils/constants.ts
@@ -1,7 +1,10 @@
+import * as BigNumber from 'bignumber.js';
+
export const constants = {
NULL_ADDRESS: '0x0000000000000000000000000000000000000000',
TESTRPC_NETWORK_ID: 50,
MAX_DIGITS_IN_UNSIGNED_256_INT: 78,
INVALID_JUMP_PATTERN: 'invalid JUMP at',
OUT_OF_GAS_PATTERN: 'out of gas',
+ UNLIMITED_ALLOWANCE_IN_BASE_UNITS: new BigNumber(2).pow(256).minus(1),
};
diff --git a/test/token_wrapper_test.ts b/test/token_wrapper_test.ts
index 8adaa6351..f4653e432 100644
--- a/test/token_wrapper_test.ts
+++ b/test/token_wrapper_test.ts
@@ -16,6 +16,7 @@ import {
ApprovalContractEventArgs,
} from '../src';
import {BlockchainLifecycle} from './utils/blockchain_lifecycle';
+import {TokenUtils} from './utils/token_utils';
import {DoneCallback} from '../src/types';
chaiSetup.configure();
@@ -27,6 +28,7 @@ describe('TokenWrapper', () => {
let zeroEx: ZeroEx;
let userAddresses: string[];
let tokens: Token[];
+ let tokenUtils: TokenUtils;
let coinbase: string;
let addressWithoutFunds: string;
before(async () => {
@@ -34,6 +36,7 @@ describe('TokenWrapper', () => {
zeroEx = new ZeroEx(web3.currentProvider);
userAddresses = await promisify(web3.eth.getAccounts)();
tokens = await zeroEx.tokenRegistry.getTokensAsync();
+ tokenUtils = new TokenUtils(tokens);
coinbase = userAddresses[0];
addressWithoutFunds = userAddresses[1];
});
@@ -206,6 +209,45 @@ describe('TokenWrapper', () => {
return expect(allowanceAfterSet).to.be.bignumber.equal(expectedAllowanceAfterAllowanceSet);
});
});
+ describe('#setUnlimitedAllowanceAsync', () => {
+ it('should set the unlimited spender\'s allowance', async () => {
+ const token = tokens[0];
+ const ownerAddress = coinbase;
+ const spenderAddress = addressWithoutFunds;
+
+ await zeroEx.token.setUnlimitedAllowanceAsync(token.address, ownerAddress, spenderAddress);
+ const allowance = await zeroEx.token.getAllowanceAsync(token.address, ownerAddress, spenderAddress);
+ return expect(allowance).to.be.bignumber.equal(zeroEx.token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS);
+ });
+ it('should reduce the gas cost for transfers including tokens with unlimited allowance support', async () => {
+ const transferAmount = new BigNumber(5);
+ const zrx = tokenUtils.getProtocolTokenOrThrow();
+ const [, userWithNormalAllowance, userWithUnlimitedAllowance] = userAddresses;
+ await zeroEx.token.setAllowanceAsync(zrx.address, coinbase, userWithNormalAllowance, transferAmount);
+ await zeroEx.token.setUnlimitedAllowanceAsync(zrx.address, coinbase, userWithUnlimitedAllowance);
+
+ const initBalanceWithNormalAllowance = await promisify(web3.eth.getBalance)(userWithNormalAllowance);
+ const initBalanceWithUnlimitedAllowance = await promisify(web3.eth.getBalance)(userWithUnlimitedAllowance);
+
+ await zeroEx.token.transferFromAsync(
+ zrx.address, coinbase, userWithNormalAllowance, userWithNormalAllowance, transferAmount,
+ );
+ await zeroEx.token.transferFromAsync(
+ zrx.address, coinbase, userWithUnlimitedAllowance, userWithUnlimitedAllowance, transferAmount,
+ );
+
+ const finalBalanceWithNormalAllowance = await promisify(web3.eth.getBalance)(userWithNormalAllowance);
+ const finalBalanceWithUnlimitedAllowance = await promisify(web3.eth.getBalance)(userWithUnlimitedAllowance);
+
+ const normalGasCost = initBalanceWithNormalAllowance.minus(finalBalanceWithNormalAllowance);
+ const unlimitedGasCost = initBalanceWithUnlimitedAllowance.minus(finalBalanceWithUnlimitedAllowance);
+
+ // In theory the gas cost with unlimited allowance should be smaller, but with testrpc it's actually bigger.
+ // This needs to be investigated in ethereumjs-vm. This test is essentially a repro.
+ // TODO: Make this test pass with inverted assertion.
+ expect(unlimitedGasCost.toNumber()).to.be.gt(normalGasCost.toNumber());
+ });
+ });
describe('#getAllowanceAsync', () => {
describe('With web3 provider with accounts', () => {
it('should get the proxy allowance', async () => {
@@ -282,6 +324,16 @@ describe('TokenWrapper', () => {
return expect(allowanceAfterSet).to.be.bignumber.equal(expectedAllowanceAfterAllowanceSet);
});
});
+ describe('#setUnlimitedProxyAllowanceAsync', () => {
+ it('should set the unlimited proxy allowance', async () => {
+ const token = tokens[0];
+ const ownerAddress = coinbase;
+
+ await zeroEx.token.setUnlimitedProxyAllowanceAsync(token.address, ownerAddress);
+ const allowance = await zeroEx.token.getProxyAllowanceAsync(token.address, ownerAddress);
+ return expect(allowance).to.be.bignumber.equal(zeroEx.token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS);
+ });
+ });
describe('#subscribeAsync', () => {
const indexFilterValues = {};
const shouldThrowOnInsufficientBalanceOrAllowance = true;