Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Interactive UDC withdraw #2635

Merged
merged 10 commits into from
Mar 25, 2021
8 changes: 4 additions & 4 deletions raiden-dapp/src/services/raiden-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,12 +217,12 @@ export default class RaidenService {
value.payload.reward,
value.payload.txHash,
);
} else if (value.type === 'udc/withdrawn') {
} else if (value.type === 'udc/withdraw/success') {
if (!value.payload.confirmed) {
return;
}
await this.notifyWithdrawal(value.meta.amount, value.payload.withdrawal);
} else if (value.type === 'udc/withdraw/failure') {
} else if (value.type === 'udc/withdraw/plan/failure') {
await this.notifyWithdrawalFailure(
value.payload?.code,
value.meta.amount,
Expand Down Expand Up @@ -481,7 +481,7 @@ export default class RaidenService {
}

private async updatePlannedUserDepositWithdrawals(event: ObservedValueOf<Raiden['events$']>) {
if (event.type === 'udc/withdraw/success') {
if (event.type === 'udc/withdraw/plan/success') {
if (event.payload.confirmed === false) {
this.store.commit('userDepositContract/clearPlannedWithdrawal');
} else {
Expand All @@ -493,7 +493,7 @@ export default class RaidenService {
confirmed: event.payload.confirmed,
});
}
} else if (event.type === 'udc/withdrawn' && event.payload.confirmed) {
} else if (event.type === 'udc/withdraw/success' && event.payload.confirmed) {
this.store.commit('userDepositContract/clearPlannedWithdrawal');
}
}
Expand Down
14 changes: 7 additions & 7 deletions raiden-dapp/tests/unit/raiden-service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -731,7 +731,7 @@ describe('RaidenService', () => {
await setupSDK();

subject.next({
type: 'udc/withdrawn',
type: 'udc/withdraw/success',
payload: {
withdrawal: utils.parseEther('5'),
confirmed: true,
Expand Down Expand Up @@ -760,7 +760,7 @@ describe('RaidenService', () => {
await setupSDK({ subkey: true });

subject.next({
type: 'udc/withdrawn',
type: 'udc/withdraw/success',
payload: {
withdrawal: utils.parseEther('5'),
confirmed: true,
Expand All @@ -787,7 +787,7 @@ describe('RaidenService', () => {
(raiden as any).events$ = subject;
await setupSDK();
subject.next({
type: 'udc/withdraw/failure',
type: 'udc/withdraw/plan/failure',
payload: {
code: 'UDC_PLAN_WITHDRAW_EXCEEDS_AVAILABLE',
},
Expand All @@ -808,7 +808,7 @@ describe('RaidenService', () => {
(raiden as any).events$ = subject;
await setupSDK();
subject.next({
type: 'udc/withdraw/failure',
type: 'udc/withdraw/plan/failure',
payload: {
code: -3200,
message: 'gas',
Expand Down Expand Up @@ -1060,7 +1060,7 @@ describe('RaidenService', () => {
(raiden as any).events$ = subject;
await setupSDK();
subject.next({
type: 'udc/withdraw/success',
type: 'udc/withdraw/plan/success',
payload: {
block: 5,
txHash: '0xTxHash',
Expand All @@ -1084,7 +1084,7 @@ describe('RaidenService', () => {
(raiden as any).events$ = subject;
await setupSDK();
subject.next({
type: 'udc/withdraw/success',
type: 'udc/withdraw/plan/success',
payload: {
block: 5,
txHash: '0xTxHash',
Expand All @@ -1105,7 +1105,7 @@ describe('RaidenService', () => {
(raiden as any).events$ = subject;
await setupSDK();
subject.next({
type: 'udc/withdrawn',
type: 'udc/withdraw/success',
payload: {
confirmed: true,
withdrawal: constants.One,
Expand Down
1 change: 1 addition & 0 deletions raiden-ts/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
### Added
- [#1342] Flat (fixed) mediation fees for mediator nodes
- [#2581] `config.pfsSafetyMargin` now also accepts a `[f, a]` pair, which will add `f*fee + a*amount` on top of PFS's estimated fee, if one wants finer-grain control on safety margin which is added on the transfer to be initiated.
- [#2629] `config.autoUdcWithdraw` (default=true) to allow disabling automatically completing a planned UDC withdraw, and new `Raiden.getUdcWithdrawPlan` and `Raiden.udcWithdraw` to check and perform UDC withdraw when not in auto mode.

### Changed
- [#2536] Wait for global messages before resolving deposits and channel open request
Expand Down
3 changes: 2 additions & 1 deletion raiden-ts/src/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,10 @@ export const RaidenEvents = [
RaidenActions.newBlock,
RaidenActions.matrixPresence.success,
RaidenActions.tokenMonitored,
RaidenActions.udcWithdrawPlan.success,
RaidenActions.udcWithdrawPlan.failure,
RaidenActions.udcWithdraw.success,
RaidenActions.udcWithdraw.failure,
RaidenActions.udcWithdrawn,
RaidenActions.msBalanceProofSent,
RaidenActions.channelSettle.success,
RaidenActions.channelSettle.failure,
Expand Down
7 changes: 5 additions & 2 deletions raiden-ts/src/channels/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,10 @@ export function assertTx(
{ log, provider }: Pick<RaidenEpicDeps, 'log' | 'provider'>,
): OperatorFunction<
ContractTransaction,
[ContractTransaction, ContractReceipt & { transactionHash: Hash; blockNumber: number }]
[
ContractTransaction & { hash: Hash },
ContractReceipt & { transactionHash: Hash; blockNumber: number },
]
> {
return (tx$) =>
tx$.pipe(
Expand All @@ -167,7 +170,7 @@ export function assertTx(
});
log.debug(`${method} tx "${receipt.transactionHash}" successfuly mined!`);
return [tx, receipt] as [
ContractTransaction,
ContractTransaction & { hash: Hash },
ContractReceipt & { transactionHash: Hash; blockNumber: number },
];
}),
Expand Down
3 changes: 3 additions & 0 deletions raiden-ts/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ const RTCIceServer = t.type({ urls: t.union([t.string, t.array(t.string)]) });
* approving tokens should be needed only once, trusting TokenNetwork's & UDC contracts;
* Set to Zero to fallback to approving the strictly needed deposit amounts
* - autoSettle - Whether to channelSettle.request settleable channels automatically
* - autoUdcWithdraw - Whether to udcWithdraw.request planned withdraws automatically
* - mediationFees - deps.mediationFeeCalculator config. It's typed as unknown because it'll be
* validated and decoded by [[FeeModel.decodeConfig]].
* - matrixServer? - Specify a matrix server to use.
Expand Down Expand Up @@ -97,6 +98,7 @@ export const RaidenConfig = t.readonly(
pollingInterval: t.number,
minimumAllowance: UInt(32),
autoSettle: t.boolean,
autoUdcWithdraw: t.boolean,
mediationFees: t.unknown,
}),
t.partial({
Expand Down Expand Up @@ -166,6 +168,7 @@ export function makeDefaultConfig(
pollingInterval: 5000,
minimumAllowance: MaxUint256 as UInt<32>,
autoSettle: false,
autoUdcWithdraw: true,
mediationFees: {},
...overwrites,
caps, // merged caps overwrites 'overwrites.caps'
Expand Down
6 changes: 5 additions & 1 deletion raiden-ts/src/errors.json
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@
"UDC_PLAN_WITHDRAW_GT_ZERO" : "The planned withdraw amount has to be greater than zero.",
"UDC_PLAN_WITHDRAW_EXCEEDS_AVAILABLE" : "The planned withdraw amount exceeds the total amount available for withdrawing.",
"UDC_WITHDRAW_NO_BALANCE" : "There is no balances left to withdraw from UDC.",
"UDC_WITHDRAW_FAILED" : "An error occurred while withdrawing from the UDC."
"UDC_WITHDRAW_FAILED" : "An error occurred while withdrawing from the UDC.",
"UDC_WITHDRAW_AUTO_ENABLED": "Interactive udcWithdraw disabled when config.autoUdcWithdraw is enabled; request will be made automatically",
"UDC_WITHDRAW_NO_PLAN": "No plan currently registered; you must call Raiden.planUdcWithdraw first",
"UDC_WITHDRAW_TOO_EARLY": "The current UDC withdraw plan is not yet ready to be withdrawn",
"UDC_WITHDRAW_TOO_LARGE": "Your current plan is not enough to withdraw the requested amount"
}

65 changes: 63 additions & 2 deletions raiden-ts/src/raiden.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ import {
} from './helpers';
import { createPersisterMiddleware } from './persister';
import { raidenReducer } from './reducer';
import { pathFind, udcDeposit, udcWithdraw } from './services/actions';
import { pathFind, udcDeposit, udcWithdraw, udcWithdrawPlan } from './services/actions';
import type { IOU, RaidenPaths, RaidenPFS, SuggestedPartner } from './services/types';
import { Paths, PFS, PfsMode, SuggestedPartners } from './services/types';
import { pfsListInfo } from './services/utils';
Expand Down Expand Up @@ -1388,13 +1388,74 @@ export class Raiden {
return receipt.transactionHash as Hash;
}

/**
* Fetches our current UDC withdraw plan
*
* @returns Promise to object containing maximum 'amount' planned for withdraw and 'block' at
* which withdraw will become available, and 'ready' after it can be withdrawn with
* [[udcWithdraw]]; resolves to undefined if there's no current plan
*/
public async getUdcWithdrawPlan(): Promise<
andrevmatos marked this conversation as resolved.
Show resolved Hide resolved
{ amount: UInt<32>; block: number; ready: boolean } | undefined
> {
const plan = await this.deps.userDepositContract.withdraw_plans(this.address);
if (plan.withdraw_block.isZero()) return;
return {
amount: plan.amount as UInt<32>,
block: plan.withdraw_block.toNumber(),
ready: plan.withdraw_block.lte(this.state.blockNumber),
};
}

/**
* Records a UDC withdraw plan for our UDC deposit
*
* The plan will be ready for withdraw after 100 blocks.
weilbith marked this conversation as resolved.
Show resolved Hide resolved
* Maximum 'value' which can be planned is current [[getUDCCapacity]] plus current
* [[getUdcWithdrawPlan]].amount, since new plan overwrites previous.
*
* @param value - Maximum value which we may try to withdraw. An error will be thrown if this
* value is larger than [[getUDCCapacity]]+[[getUdcWithdrawPlan]].amount
* @returns Promise to hash of plan transaction, if it succeeds.
*/
public async planUdcWithdraw(value: BigNumberish): Promise<Hash> {
const meta = {
amount: decode(UInt(32), value, ErrorCodes.DTA_INVALID_AMOUNT, this.log.error),
};
const promise = asyncActionToPromise(udcWithdraw, meta, this.action$, true).then(
const promise = asyncActionToPromise(udcWithdrawPlan, meta, this.action$, true).then(
({ txHash }) => txHash!,
);
this.store.dispatch(udcWithdrawPlan.request(undefined, meta));
return promise;
}

/**
* Complete a planned UDC withdraw and get the deposit to account.
*
* Maximum 'value' is the one from current plan, attempting to withdraw a larger value will throw
weilbith marked this conversation as resolved.
Show resolved Hide resolved
* an error, but a smaller value is valid. This method may only be called after plan is 'ready',
* i.e. at least 100 blocks have passed after it was planned.
*
* @param value - Maximum value which we may try to withdraw. An error will be thrown if this
* value is larger than [[getUDCCapacity]]+[[getUdcWithdrawPlan]].amount
* @returns Promise to hash of plan transaction, if it succeeds.
*/
public async udcWithdraw(value?: BigNumberish): Promise<Hash> {
assert(!this.config.autoUdcWithdraw, ErrorCodes.UDC_WITHDRAW_AUTO_ENABLED, this.log.warn);
const plan = await this.getUdcWithdrawPlan();
assert(plan, ErrorCodes.UDC_WITHDRAW_NO_PLAN, this.log.warn);
assert(plan.ready, ErrorCodes.UDC_WITHDRAW_TOO_EARLY, this.log.warn);
if (!value) {
value = plan.amount;
} else {
assert(plan.amount.gte(value), ErrorCodes.UDC_WITHDRAW_TOO_LARGE, this.log.warn);
}
const meta = {
amount: decode(UInt(32), value, ErrorCodes.DTA_INVALID_AMOUNT, this.log.error),
};
const promise = asyncActionToPromise(udcWithdraw, meta, this.action$, true).then(
({ txHash }) => txHash,
);
this.store.dispatch(udcWithdraw.request(undefined, meta));
return promise;
}
Expand Down
33 changes: 19 additions & 14 deletions raiden-ts/src/services/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,36 +66,41 @@ const UdcWithdrawId = t.type({
amount: UInt(32),
});

export const udcWithdraw = createAsyncAction(
export const udcWithdrawPlan = createAsyncAction(
UdcWithdrawId,
'udc/withdraw/request',
'udc/withdraw/success',
'udc/withdraw/failure',
'udc/withdraw/plan/request',
'udc/withdraw/plan/success',
'udc/withdraw/plan/failure',
t.undefined,
t.intersection([
t.type({ block: t.number }),
t.partial({ txHash: Hash, txBlock: t.number, confirmed: t.union([t.undefined, t.boolean]) }),
]),
);

export namespace udcWithdraw {
export interface request extends ActionType<typeof udcWithdraw.request> {}
export interface success extends ActionType<typeof udcWithdraw.success> {}
export interface failure extends ActionType<typeof udcWithdraw.failure> {}
export namespace udcWithdrawPlan {
export interface request extends ActionType<typeof udcWithdrawPlan.request> {}
export interface success extends ActionType<typeof udcWithdrawPlan.success> {}
export interface failure extends ActionType<typeof udcWithdrawPlan.failure> {}
}

export const udcWithdrawn = createAction(
'udc/withdrawn',
export const udcWithdraw = createAsyncAction(
UdcWithdrawId,
'udc/withdraw/request',
'udc/withdraw/success',
'udc/withdraw/failure',
t.undefined,
t.type({
withdrawal: UInt(32),
txHash: Hash,
txBlock: t.number,
confirmed: t.union([t.undefined, t.boolean]),
}),
UdcWithdrawId,
);

export interface udcWithdrawn extends ActionType<typeof udcWithdrawn> {}
export namespace udcWithdraw {
export interface request extends ActionType<typeof udcWithdraw.request> {}
export interface success extends ActionType<typeof udcWithdraw.success> {}
export interface failure extends ActionType<typeof udcWithdraw.failure> {}
}

export const msBalanceProofSent = createAction(
'ms/balanceProof/sent',
Expand Down
Loading