[FEATURE] Add Deposit field

This commit is contained in:
Daniel Kmak 2018-03-31 10:56:45 +02:00 committed by Bagaric
parent 02ba3ea0c3
commit 353eeb5384
21 changed files with 183 additions and 28 deletions

View File

@ -18,7 +18,8 @@ import {
SetScheduleTypeAction,
SetSchedulingToggleAction,
SetScheduleGasPriceFieldAction,
SetScheduleGasLimitFieldAction
SetScheduleGasLimitFieldAction,
SetScheduleDepositFieldAction
} from '../actionTypes';
import { TypeKeys } from 'actions/transaction/constants';
@ -146,6 +147,12 @@ const setScheduleGasLimitField = (payload: SetScheduleGasLimitFieldAction['paylo
payload
});
type TSetScheduleDepositField = typeof setScheduleDepositField;
const setScheduleDepositField = (payload: SetScheduleDepositFieldAction['payload']) => ({
type: TypeKeys.SCHEDULE_DEPOSIT_FIELD_SET,
payload
});
type TReset = typeof reset;
const reset = (payload: ResetAction['payload'] = { include: {}, exclude: {} }): ResetAction => ({
type: TypeKeys.RESET,
@ -172,6 +179,7 @@ export {
TSetSchedulingToggle,
TSetScheduleGasPriceField,
TSetScheduleGasLimitField,
TSetScheduleDepositField,
TReset,
inputGasLimit,
inputGasPrice,
@ -192,5 +200,6 @@ export {
setSchedulingToggle,
setScheduleGasPriceField,
setScheduleGasLimitField,
setScheduleDepositField,
reset
};

View File

@ -147,6 +147,14 @@ interface SetScheduleGasLimitFieldAction {
};
}
interface SetScheduleDepositFieldAction {
type: TypeKeys.SCHEDULE_DEPOSIT_FIELD_SET;
payload: {
raw: string;
value: Wei | null;
};
}
type InputFieldAction = InputNonceAction | InputGasLimitAction | InputDataAction;
type FieldAction =
@ -163,7 +171,8 @@ type FieldAction =
| SetScheduleTypeAction
| SetSchedulingToggleAction
| SetScheduleGasPriceFieldAction
| SetScheduleGasLimitFieldAction;
| SetScheduleGasLimitFieldAction
| SetScheduleDepositFieldAction;
export {
InputGasLimitAction,
@ -188,5 +197,6 @@ export {
SetScheduleTypeAction,
SetSchedulingToggleAction,
SetScheduleGasPriceFieldAction,
SetScheduleGasLimitFieldAction
SetScheduleGasLimitFieldAction,
SetScheduleDepositFieldAction
};

View File

@ -55,6 +55,7 @@ export enum TypeKeys {
SCHEDULE_TIMEZONE_SET = 'SCHEDULE_TIMEZONE_SET',
SCHEDULE_TYPE_SET = 'SCHEDULE_TYPE_SET',
SCHEDULING_TOGGLE_SET = 'SCHEDULING_TOGGLE_SET',
SCHEDULE_DEPOSIT_FIELD_SET = 'SCHEDULE_DEPOSIT_FIELD_SET',
TOKEN_TO_META_SET = 'TOKEN_TO_META_SET',
UNIT_META_SET = 'UNIT_META_SET',

View File

@ -5,6 +5,7 @@ import { inputGasLimit, TInputGasLimit } from 'actions/transaction';
import { connect } from 'react-redux';
import { AppState } from 'reducers';
import { sanitizeNumericalInput } from 'libs/values';
import { getSchedulingToggle } from 'selectors/transaction';
const defaultGasLimit = '21000';
@ -18,16 +19,24 @@ export interface CallBackProps {
interface DispatchProps {
inputGasLimit: TInputGasLimit;
}
interface OwnProps {
gasLimit: string | null;
scheduling: boolean;
withProps(props: CallBackProps): React.ReactElement<any> | null;
}
type Props = DispatchProps & OwnProps;
class GasLimitFieldClass extends Component<Props, {}> {
class GasLimitFieldClass extends Component<Props> {
public componentDidMount() {
const { gasLimit } = this.props;
const { gasLimit, scheduling } = this.props;
if (scheduling) {
return;
}
if (gasLimit) {
this.props.inputGasLimit(gasLimit);
} else {
@ -45,7 +54,12 @@ class GasLimitFieldClass extends Component<Props, {}> {
};
}
const GasLimitField = connect(null, { inputGasLimit })(GasLimitFieldClass);
const GasLimitField = connect(
(state: AppState) => ({
scheduling: getSchedulingToggle(state).value
}),
{ inputGasLimit }
)(GasLimitFieldClass);
interface DefaultGasLimitFieldProps {
withProps(props: CallBackProps): React.ReactElement<any> | null;

View File

@ -11,7 +11,7 @@ export const ScheduleTimestampField: React.SFC<Props> = ({ isReadOnly }) => (
withProps={({ currentScheduleTimestamp, isValid, onChange, readOnly }) => (
<div className="input-group-wrapper">
<label className="input-group">
<div className="input-group-header">{translate('SCHEDULE_timestamp')}</div>
<div className="input-group-header">{translate('SCHEDULE_TIMESTAMP')}</div>
<input
className={`input-group-input ${isValid ? '' : 'invalid'}`}
type="text"

View File

@ -0,0 +1,62 @@
import { connect } from 'react-redux';
import React, { Component } from 'react';
import { AppState } from 'reducers';
import { setScheduleDepositField, TSetScheduleDepositField } from 'actions/transaction';
import { translateRaw } from 'translations';
import { Input } from 'components/ui';
import { getScheduleDeposit, isValidScheduleDeposit, getDecimal } from 'selectors/transaction';
import { toWei } from 'libs/units';
interface OwnProps {
decimal: number;
scheduleDeposit: any;
validScheduleDeposit: boolean;
}
interface DispatchProps {
setScheduleDepositField: TSetScheduleDepositField;
}
type Props = OwnProps & DispatchProps;
class ScheduleDepositFieldClass extends Component<Props> {
public render() {
const { scheduleDeposit, validScheduleDeposit } = this.props;
return (
<div className="input-group-wrapper">
<label className="input-group">
<div className="input-group-header">{translateRaw('SCHEDULE_DEPOSIT')}</div>
<Input
className={!!scheduleDeposit.raw && !validScheduleDeposit ? 'invalid' : ''}
type="number"
placeholder={translateRaw('SCHEDULE_DEPOSIT_PLACEHOLDER')}
value={scheduleDeposit.raw}
onChange={this.handleDepositChange}
/>
</label>
</div>
);
}
private handleDepositChange = (ev: React.FormEvent<HTMLInputElement>) => {
const { decimal } = this.props;
const { value } = ev.currentTarget;
this.props.setScheduleDepositField({
raw: value,
value: value ? toWei(value, decimal) : null
});
};
}
export const ScheduleDepositField = connect(
(state: AppState) => ({
decimal: getDecimal(state),
scheduleDeposit: getScheduleDeposit(state),
validScheduleDeposit: isValidScheduleDeposit(state)
}),
{
setScheduleDepositField
}
)(ScheduleDepositFieldClass);

View File

@ -43,7 +43,7 @@ class ScheduleGasPriceFieldClass extends React.Component<Props> {
this.props.setScheduleGasPriceField({
raw: value,
value: gasPriceToBase(parseInt(value, 10))
value: value ? gasPriceToBase(parseInt(value, 10)) : null
});
};
}

View File

@ -4,6 +4,8 @@ import { WindowStartInputFactory } from './WindowStartInputFactory';
import React from 'react';
import { connect } from 'react-redux';
import { ICurrentWindowStart } from 'selectors/transaction';
import { getLatestBlock } from 'selectors/config';
import { AppState } from 'reducers';
interface DispatchProps {
setCurrentWindowStart: TSetCurrentWindowStart;
@ -11,6 +13,7 @@ interface DispatchProps {
interface OwnProps {
windowStart: string | null;
latestBlock: string;
withProps(props: CallbackProps): React.ReactElement<any> | null;
}
@ -25,9 +28,12 @@ type Props = DispatchProps & OwnProps;
class WindowStartFieldFactoryClass extends React.Component<Props> {
public componentDidMount() {
const { windowStart } = this.props;
const { latestBlock, windowStart } = this.props;
if (windowStart) {
this.props.setCurrentWindowStart(windowStart);
} else {
this.props.setCurrentWindowStart(latestBlock);
}
}
@ -43,9 +49,12 @@ class WindowStartFieldFactoryClass extends React.Component<Props> {
};
}
const WindowStartFieldFactory = connect(null, { setCurrentWindowStart })(
WindowStartFieldFactoryClass
);
const WindowStartFieldFactory = connect(
(state: AppState) => ({
latestBlock: getLatestBlock(state)
}),
{ setCurrentWindowStart }
)(WindowStartFieldFactoryClass);
interface DefaultWindowStartFieldProps {
withProps(props: CallbackProps): React.ReactElement<any> | null;

View File

@ -1,3 +1,5 @@
@import 'common/sass/variables';
.scheduled-tx-settings {
padding: 0 20px 20px 20px;
border: 1px solid #e5ecf3;
@ -11,7 +13,9 @@
}
}
.vcenter {
display: flex;
align-items: center;
.vcenter-sm {
@media (min-width: $screen-sm) {
display: flex;
align-items: center;
}
}

View File

@ -7,7 +7,8 @@ import {
TimeBountyField,
WindowStartField,
ScheduleGasPriceField,
ScheduleGasLimitField
ScheduleGasLimitField,
ScheduleDepositField
} from '.';
import { ScheduleTimezoneDropDown, ScheduleTimestampField, ScheduleType } from 'components';
import './ScheduleFields.scss';
@ -25,7 +26,7 @@ class ScheduleFieldsClass extends React.Component<Props> {
<div className="scheduled-tx-settings_title">Scheduled Transaction Settings</div>
<br />
<div className="row form-group vcenter">
<div className="row form-group vcenter-sm">
<div className="col-xs-12 col-sm-6 col-md-3 col-md-push-9">
<ScheduleType />
</div>
@ -58,6 +59,9 @@ class ScheduleFieldsClass extends React.Component<Props> {
<div className="col-xs-6">
<TimeBountyField />
</div>
<div className="col-xs-6">
<ScheduleDepositField />
</div>
</div>
<div className="row form-group">

View File

@ -4,4 +4,5 @@ export * from './Fields/WindowSize/WindowSizeField';
export * from './Fields/SchedulingToggle/SchedulingToggle';
export * from './Fields/ScheduleGasPriceField';
export * from './Fields/ScheduleGasLimitField';
export * from './Fields/ScheduleDepositField';
export * from './ScheduleFields';

View File

@ -11,7 +11,6 @@ export const EAC_SCHEDULING_CONFIG = {
FEE: new BN('2242000000000000'), // $2
FEE_MULTIPLIER: new BN('2'),
FUTURE_EXECUTION_COST: new BN('180000'),
REQUIRED_DEPOSIT: 0,
SCHEDULING_GAS_LIMIT: new BN('1500000'),
TIME_BOUNTY_MIN,
TIME_BOUNTY_DEFAULT: TIME_BOUNTY_MIN,
@ -68,8 +67,12 @@ export const getScheduleData = (
windowStart: any,
callGasPrice: BN | null,
timeBounty: BN | null,
requiredDeposit: any
requiredDeposit: BN | null
) => {
if (!requiredDeposit || requiredDeposit.lt(new BN(0))) {
requiredDeposit = new BN(0);
}
if (
!callValue ||
!callGas ||

View File

@ -40,7 +40,8 @@ const INITIAL_STATE: State = {
scheduleGasPrice: {
raw: EAC_SCHEDULING_CONFIG.SCHEDULE_GAS_PRICE_FALLBACK.toString(),
value: gasPriceToBase(EAC_SCHEDULING_CONFIG.SCHEDULE_GAS_PRICE_FALLBACK)
}
},
scheduleDeposit: { raw: '', value: null }
};
const updateField = (key: keyof State): Reducer<State> => (state: State, action: FieldAction) => ({
@ -106,6 +107,8 @@ export const fields = (
return updateField('scheduleGasLimit')(state, action);
case TK.SCHEDULE_GAS_PRICE_FIELD_SET:
return updateField('scheduleGasPrice')(state, action);
case TK.SCHEDULE_DEPOSIT_FIELD_SET:
return updateField('scheduleDeposit')(state, action);
case TK.TOKEN_TO_ETHER_SWAP:
return tokenToEther(state, action);
case TK.ETHER_TO_TOKEN_SWAP:

View File

@ -9,7 +9,8 @@ import {
SetScheduleTypeAction,
SetSchedulingToggleAction,
SetScheduleGasPriceFieldAction,
SetScheduleGasLimitFieldAction
SetScheduleGasLimitFieldAction,
SetScheduleDepositFieldAction
} from 'actions/transaction';
import { Wei } from 'libs/units';
@ -28,4 +29,5 @@ export interface State {
scheduleType: SetScheduleTypeAction['payload'];
scheduleGasLimit: SetScheduleGasLimitFieldAction['payload'];
scheduleGasPrice: SetScheduleGasPriceFieldAction['payload'];
scheduleDeposit: SetScheduleDepositFieldAction['payload'];
}

View File

@ -7,10 +7,12 @@ import {
getDataExists,
getGasPrice,
getGasLimit,
getScheduleGasLimit
getScheduleGasLimit,
getScheduleDeposit
} from 'selectors/transaction';
import { isNetworkUnit } from 'selectors/config';
import { getAddressMessage, AddressMessage } from 'config';
import BN from 'bn.js';
interface ICurrentValue {
raw: string;
@ -55,6 +57,16 @@ const isValidScheduleGasPrice = (state: AppState): boolean =>
const isValidScheduleGasLimit = (state: AppState): boolean =>
gasLimitValidator(getScheduleGasLimit(state).raw);
const isValidScheduleDeposit = (state: AppState): boolean => {
const depositValue = getScheduleDeposit(state).value;
if (!depositValue) {
return true;
}
return depositValue.gte(new BN('0'));
};
function getCurrentToAddressMessage(state: AppState): AddressMessage | undefined {
const to = getCurrentTo(state);
return getAddressMessage(to.raw);
@ -71,5 +83,6 @@ export {
isValidGasLimit,
isValidScheduleGasLimit,
isValidScheduleGasPrice,
isValidScheduleDeposit,
getCurrentToAddressMessage
};

View File

@ -18,6 +18,7 @@ const getScheduleType = (state: AppState) => getFields(state).scheduleType;
const getSchedulingToggle = (state: AppState) => getFields(state).schedulingToggle;
const getScheduleGasLimit = (state: AppState) => getFields(state).scheduleGasLimit;
const getScheduleGasPrice = (state: AppState) => getFields(state).scheduleGasPrice;
const getScheduleDeposit = (state: AppState) => getFields(state).scheduleDeposit;
const getDataExists = (state: AppState) => {
const { value } = getData(state);
@ -51,5 +52,6 @@ export {
getScheduleType,
getSchedulingToggle,
getScheduleGasLimit,
getScheduleGasPrice
getScheduleGasPrice,
getScheduleDeposit
};

View File

@ -33,6 +33,9 @@ export const isFullTx = (
delete partialParamsToCheck.windowSize;
delete partialParamsToCheck.scheduleTimestamp;
delete partialParamsToCheck.schedulingToggle;
delete partialParamsToCheck.scheduleDeposit;
delete partialParamsToCheck.scheduleGasLimit;
delete partialParamsToCheck.scheduleGasPrice;
const validPartialParams = Object.values(partialParamsToCheck).reduce<boolean>(
(isValid, v: AppState['transaction']['fields'] & ICurrentTo & ICurrentValue) =>

View File

@ -29,7 +29,9 @@ import {
getScheduleGasPrice,
isValidScheduleGasPrice,
isValidScheduleGasLimit,
getScheduleGasLimit
getScheduleGasLimit,
isValidScheduleDeposit,
getScheduleDeposit
} from 'selectors/transaction';
import { Wei, Address, gasPriceToBase } from 'libs/units';
import { getTransactionFields } from 'libs/transaction/utils/ether';
@ -92,13 +94,16 @@ const getSchedulingTransaction = (state: AppState): IGetTransaction => {
const scheduleGasPriceValid = isValidScheduleGasPrice(state);
const scheduleGasLimit = getScheduleGasLimit(state);
const scheduleGasLimitValid = isValidScheduleGasLimit(state);
const depositValid = isValidScheduleDeposit(state);
const deposit = getScheduleDeposit(state);
const isFullTransaction =
isFullTx(state, transactionFields, currentTo, currentValue, dataExists, validGasCost, unit) &&
(windowStartValid || scheduleTimestampValid) &&
windowSizeValid &&
scheduleGasPriceValid &&
scheduleGasLimitValid;
scheduleGasLimitValid &&
depositValid;
const transactionData = getScheduleData(
currentTo.raw,
@ -109,7 +114,7 @@ const getSchedulingTransaction = (state: AppState): IGetTransaction => {
windowStart.value,
scheduleGasPrice.value,
timeBounty.value,
EAC_SCHEDULING_CONFIG.REQUIRED_DEPOSIT
deposit.value
);
const endowment = calcEACEndowment(

View File

@ -29,7 +29,8 @@ describe('fields reducer', () => {
raw: EAC_SCHEDULING_CONFIG.SCHEDULE_GAS_PRICE_FALLBACK.toString(),
value: gasPriceToBase(EAC_SCHEDULING_CONFIG.SCHEDULE_GAS_PRICE_FALLBACK)
},
scheduleGasLimit: { raw: '21000', value: new BN(21000) }
scheduleGasLimit: { raw: '21000', value: new BN(21000) },
scheduleDeposit: { raw: '', value: null }
};
const testPayload = { raw: 'test', value: null };

View File

@ -71,6 +71,10 @@ describe('fields selector', () => {
scheduleGasLimit: {
raw: '21000',
value: Wei('21000')
},
scheduleDeposit: {
raw: '1000000000',
value: Wei('1000000000')
}
};

View File

@ -75,6 +75,10 @@ describe('helpers selector', () => {
scheduleGasLimit: {
raw: '21000',
value: Wei('21000')
},
scheduleDeposit: {
raw: '1000000000',
value: Wei('1000000000')
}
}
};
@ -94,7 +98,8 @@ describe('helpers selector', () => {
scheduleTimestamp: null,
scheduleType: 'time',
scheduleGasPrice: Wei('1500'),
scheduleGasLimit: Wei('21000')
scheduleGasLimit: Wei('21000'),
scheduleDeposit: Wei('1000000000')
};
expect(reduceToValues(state.transaction.fields)).toEqual(values);
});