diff options
Diffstat (limited to 'src/utils/exchange_transfer_simulator.ts')
-rw-r--r-- | src/utils/exchange_transfer_simulator.ts | 136 |
1 files changed, 136 insertions, 0 deletions
diff --git a/src/utils/exchange_transfer_simulator.ts b/src/utils/exchange_transfer_simulator.ts new file mode 100644 index 000000000..f79dce242 --- /dev/null +++ b/src/utils/exchange_transfer_simulator.ts @@ -0,0 +1,136 @@ +import * as _ from 'lodash'; +import {ExchangeContractErrs, TradeSide, TransferType} from '../types'; +import {TokenWrapper} from '../contract_wrappers/token_wrapper'; + +enum FailureReason { + Balance = 'balance', + ProxyAllowance = 'proxy allowance', +} + +const ERR_MSG_MAPPING = { + [FailureReason.Balance]: { + [TradeSide.Maker]: { + [TransferType.Trade]: ExchangeContractErrs.InsufficientMakerBalance, + [TransferType.Fee]: ExchangeContractErrs.InsufficientMakerFeeBalance, + }, + [TradeSide.Taker]: { + [TransferType.Trade]: ExchangeContractErrs.InsufficientTakerBalance, + [TransferType.Fee]: ExchangeContractErrs.InsufficientTakerFeeBalance, + }, + }, + [FailureReason.ProxyAllowance]: { + [TradeSide.Maker]: { + [TransferType.Trade]: ExchangeContractErrs.InsufficientMakerAllowance, + [TransferType.Fee]: ExchangeContractErrs.InsufficientMakerFeeAllowance, + }, + [TradeSide.Taker]: { + [TransferType.Trade]: ExchangeContractErrs.InsufficientTakerAllowance, + [TransferType.Fee]: ExchangeContractErrs.InsufficientTakerFeeAllowance, + }, + }, +}; + +/** + * Copy on read store for balances/proxyAllowances of tokens/accounts touched in trades + * @param {TokenWrapper} token [description] + * @return {[type]} [description] + */ +export class BalanceAndProxyAllowanceLazyStore { + protected _token: TokenWrapper; + private _balance: { + [tokenAddress: string]: { + [userAddress: string]: BigNumber.BigNumber, + }, + }; + private _proxyAllowance: { + [tokenAddress: string]: { + [userAddress: string]: BigNumber.BigNumber, + }, + }; + constructor(token: TokenWrapper) { + this._token = token; + this._balance = {}; + this._proxyAllowance = {}; + } + protected async getBalanceAsync(tokenAddress: string, userAddress: string): Promise<BigNumber.BigNumber> { + if (_.isUndefined(this._balance[tokenAddress]) || _.isUndefined(this._balance[tokenAddress][userAddress])) { + const balance = await this._token.getBalanceAsync(tokenAddress, userAddress); + this.setBalance(tokenAddress, userAddress, balance); + } + return this._balance[tokenAddress][userAddress]; + } + protected setBalance(tokenAddress: string, userAddress: string, balance: BigNumber.BigNumber): void { + if (_.isUndefined(this._balance[tokenAddress])) { + this._balance[tokenAddress] = {}; + } + this._balance[tokenAddress][userAddress] = balance; + } + protected async getProxyAllowanceAsync(tokenAddress: string, userAddress: string): Promise<BigNumber.BigNumber> { + if (_.isUndefined(this._proxyAllowance[tokenAddress]) || + _.isUndefined(this._proxyAllowance[tokenAddress][userAddress])) { + const proxyAllowance = await this._token.getProxyAllowanceAsync(tokenAddress, userAddress); + this.setProxyAllowance(tokenAddress, userAddress, proxyAllowance); + } + return this._proxyAllowance[tokenAddress][userAddress]; + } + protected setProxyAllowance(tokenAddress: string, userAddress: string, proxyAllowance: BigNumber.BigNumber): void { + if (_.isUndefined(this._proxyAllowance[tokenAddress])) { + this._proxyAllowance[tokenAddress] = {}; + } + this._proxyAllowance[tokenAddress][userAddress] = proxyAllowance; + } +} + +export class ExchangeTransferSimulator extends BalanceAndProxyAllowanceLazyStore { + constructor(token: TokenWrapper) { + super(token); + } + /** + * Simulates transferFrom call performed by a proxy + * @param tokenAddress Address of a token to be transfered + * @param from Owner of the transfered tokens + * @param to Recipient of the transfered tokens + * @param amountInBaseUnits The amount of tokens being transfered + * @param tradeSide Is Maker/Taker transferring + * @param transferType Is it a fee payment or a value transfer + */ + public async transferFromAsync(tokenAddress: string, from: string, to: string, + amountInBaseUnits: BigNumber.BigNumber, tradeSide: TradeSide, + transferType: TransferType): Promise<void> { + const balance = await this.getBalanceAsync(tokenAddress, from); + const proxyAllowance = await this.getProxyAllowanceAsync(tokenAddress, from); + if (balance.lessThan(amountInBaseUnits)) { + this.throwValidationError(FailureReason.Balance, tradeSide, transferType); + } + if (proxyAllowance.lessThan(amountInBaseUnits)) { + this.throwValidationError(FailureReason.ProxyAllowance, tradeSide, transferType); + } + await this.decreaseProxyAllowanceAsync(tokenAddress, from, amountInBaseUnits); + await this.decreaseBalanceAsync(tokenAddress, from, amountInBaseUnits); + if (!_.isUndefined(to)) { + await this.increaseBalanceAsync(tokenAddress, to, amountInBaseUnits); + } + } + private async decreaseProxyAllowanceAsync(tokenAddress: string, userAddress: string, + amountInBaseUnits: BigNumber.BigNumber): Promise<void> { + const proxyAllowance = await this.getProxyAllowanceAsync(tokenAddress, userAddress); + if (!proxyAllowance.eq(this._token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS)) { + this.setProxyAllowance(tokenAddress, userAddress, proxyAllowance.minus(amountInBaseUnits)); + } + } + private async increaseBalanceAsync(tokenAddress: string, userAddress: string, + amountInBaseUnits: BigNumber.BigNumber): Promise<void> { + const balance = await this.getBalanceAsync(tokenAddress, userAddress); + this.setBalance(tokenAddress, userAddress, balance.plus(amountInBaseUnits)); + } + private async decreaseBalanceAsync(tokenAddress: string, userAddress: string, + amountInBaseUnits: BigNumber.BigNumber): Promise<void> { + const balance = await this.getBalanceAsync(tokenAddress, userAddress); + this.setBalance(tokenAddress, userAddress, balance.minus(amountInBaseUnits)); + } + private throwValidationError(failureReason: FailureReason, tradeSide: TradeSide, + transferType: TransferType): Promise<never> { + const errMsg = ERR_MSG_MAPPING[failureReason][tradeSide][transferType]; + throw new Error(errMsg); + } +} |