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
2 changes: 1 addition & 1 deletion raiden-dapp/src/components/dialogs/UdcWithdrawalDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ export default class UdcWithdrawalDialog extends Vue {
async planWithdraw() {
this.inProgress = true;
try {
await this.$raiden.planUdcWithdraw(this.withdrawAmount);
await this.$raiden.planUDCWithdraw(this.withdrawAmount);
this.done();
} catch (e) {
this.error = e;
Expand Down
12 changes: 6 additions & 6 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 Expand Up @@ -645,8 +645,8 @@ export default class RaidenService {
}

/* istanbul ignore next */
async planUdcWithdraw(amount: BigNumber): Promise<string> {
return this.raiden.planUdcWithdraw(amount);
async planUDCWithdraw(amount: BigNumber): Promise<string> {
return this.raiden.planUDCWithdraw(amount);
}

/* istanbul ignore next */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ describe('UdcWithdrawalDialog.vue', function () {
expect.assertions(1);
await (wrapper.vm as any).planWithdraw();
await flushPromises();
expect($raiden.planUdcWithdraw).toBeCalledTimes(1);
expect($raiden.planUDCWithdraw).toBeCalledTimes(1);
});

test('invalid withdraw amount is zero', async () => {
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
4 changes: 4 additions & 0 deletions raiden-ts/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@
### 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.withdrawFromUDC` to check and perform UDC withdraw when not in auto mode.

### Changed
- [#2536] Wait for global messages before resolving deposits and channel open request
- [#2566] Optimize initial sync and resume previous sync filters scans
- [#2570] Support multiple custom services in config.pfs
- [#2635] **BREAKING** Renamed `Raiden.planUdcWithdraw` to `Raiden.planUDCWithdraw` for consistency

### Removed
- [#2550] **BREAKING** Remove migration of legacy state at localStorage during creation
Expand All @@ -27,6 +29,8 @@
[#2581]: https://github.com/raiden-network/light-client/pull/2581
[#2596]: https://github.com/raiden-network/light-client/issues/2596
[#2600]: https://github.com/raiden-network/light-client/issues/2600
[#2629]: https://github.com/raiden-network/light-client/issues/2629
[#2635]: https://github.com/raiden-network/light-client/pull/2635

## [0.15.0] - 2021-01-26
### Added
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
1 change: 1 addition & 0 deletions raiden-ts/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,4 @@ export const CapsFallback = {

export const RAIDEN_DEVICE_ID = 'RAIDEN';
export const DEFAULT_CONFIRMATIONS = 5;
export const UDC_WITHDRAW_TIMEOUT = 100;
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 UDC withdraw 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: 62 additions & 3 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,72 @@ export class Raiden {
return receipt.transactionHash as Hash;
}

public async planUdcWithdraw(value: BigNumberish): Promise<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
* [[withdrawFromUDC]]; resolves to undefined if there's no current plan
*/
public async getUDCWithdrawPlan(): Promise<
{ 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
*
* 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'
*
* @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 withdrawFromUDC(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