Skip to content

Commit

Permalink
feat: add convenience function to wallet for easy withdrawal to base …
Browse files Browse the repository at this point in the history
…chain (#564)

* feature: added new withdraw function to wallet

* docs: added new withdraw function to wallet

* docs: updated changeset

* feature: updated withdraw test

* chore: renamed withdraw to withdrawToBaseLayer

* refactor: integrated byte array coder
  • Loading branch information
pixelcircuits authored Nov 2, 2022
1 parent 7f3d8c2 commit 63aa038
Show file tree
Hide file tree
Showing 9 changed files with 138 additions and 18 deletions.
6 changes: 6 additions & 0 deletions .changeset/forty-icons-draw.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@fuel-ts/providers": minor
"@fuel-ts/wallet": minor
---

Added withdraw function to wallet
18 changes: 18 additions & 0 deletions docs/packages/fuel-ts-wallet/classes/Wallet.md
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,24 @@ ___

___

### withdrawToBaseLayer

**withdrawToBaseLayer**(`recipient`, `amount`, `txParams?`): `Promise`<[`TransactionResponse`](internal-TransactionResponse.md)\>

#### Parameters

| Name | Type | Default value |
| :------ | :------ | :------ |
| `recipient` | [`AbstractAddress`](internal-AbstractAddress.md) | `undefined` |
| `amount` | [`BigNumberish`](../namespaces/internal.md#bignumberish) | `undefined` |
| `txParams` | `Pick`<[`TransactionRequestLike`](../namespaces/internal.md#transactionrequestlike), ``"maturity"`` \| ``"gasPrice"`` \| ``"gasLimit"``\> | `{}` |

#### Returns

`Promise`<[`TransactionResponse`](internal-TransactionResponse.md)\>

___

### fromExtendedKey

`Static` **fromExtendedKey**(`extendedKey`): [`Wallet`](Wallet.md)
Expand Down
13 changes: 5 additions & 8 deletions packages/abi-coder/src/coders/byte.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,8 @@ import { bn, toBytes } from '@fuel-ts/math';
import Coder from './abstract-coder';

export default class ByteCoder extends Coder<number, number> {
length: number;

constructor(padding: boolean = true) {
super('byte', 'byte', padding ? 8 : 1);
this.length = padding ? 8 : 1;
constructor() {
super('byte', 'byte', 8);
}

encode(value: number): Uint8Array {
Expand All @@ -19,16 +16,16 @@ export default class ByteCoder extends Coder<number, number> {
this.throwError('Invalid Byte', value);
}

return toBytes(bytes, this.length);
return toBytes(bytes, 8);
}

decode(data: Uint8Array, offset: number): [number, number] {
const bytes = data.slice(offset, offset + this.length);
const bytes = data.slice(offset, offset + 8);
const value = bn(bytes);
if (value.gt(bn(255))) {
this.throwError('Invalid Byte', value);
}
const byte = Number(value);
return [byte, offset + this.length];
return [byte, offset + 8];
}
}
31 changes: 31 additions & 0 deletions packages/providers/src/transaction-request/transaction-request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,22 @@ export const returnZeroScript: AbstractScript<void> = {
encodeScriptData: () => new Uint8Array(0),
};

export const withdrawScript: AbstractScript<void> = {
/*
The following code loads some basic values into registers and calls SMO to create an output message
5040C010 - ADDI r16 $is i16 [r16 now points to memory 16 bytes from the start of this program (start of receiver data)]
5D44C006 - LW r17 $is i6 [r17 set to the 6th word in this program (6*8=48 bytes from the start of this program)]
4C400011 - SMO r16 r0 r0 r17 [send message out to address starting at memory position r16 with amount in r17]
24000000 - RET [return 0]
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 [recipient address]
00000000 00000000 [amount value]
*/
// TODO: Don't use hardcoded scripts: https://github.com/FuelLabs/fuels-ts/issues/281
bytes: arrayify('0x5040C0105D44C0064C40001124000000'),
encodeScriptData: () => new Uint8Array(0),
};

interface BaseTransactionRequestLike {
/** Gas price for transaction */
gasPrice?: BigNumberish;
Expand Down Expand Up @@ -447,6 +463,21 @@ export class ScriptTransactionRequest extends BaseTransactionRequest {
return this.outputs.length - 1;
}

addMessageOutputs(numberOfMessages: number = 1) {
let outputsNumber = numberOfMessages;

while (outputsNumber) {
this.pushOutput({
type: OutputType.Message,
recipient: '0x0000000000000000000000000000000000000000000000000000000000000000',
amount: 0,
});
outputsNumber -= 1;
}

return this.outputs.length - 1;
}

addContract(contract: ContractIdLike) {
const contractAddress = addressify(contract);

Expand Down
2 changes: 1 addition & 1 deletion packages/transactions/src/coders/receipt.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ describe('ReceiptCoder', () => {
const encoded = hexlify(new ReceiptCoder().encode(receipt));

expect(encoded).toEqual(
'0x000000000000000ad5579c46dfcc7f18207013e65b44e4cb4e2c2298f4ac457ba8f82743f31e930b750f560d912ec02d826af8ba3be90a9481fb6d3bc6b4e7f01a89f245cf0a705968b401b682ba0c9018150cca596358a6b98576337ea10b9cfb0d02441b3bc61a0000000000000fa0eb03488970d05ea240c788a0ea2e07176cc5317b7c7c89f26ac5282bbcd445bd000000000000000c2f6d40e3ac1a172fb9445f9843440a0fc383bea238a7a35a77a3c73d369029920102030405060708090a0b0c'
'0x000000000000000ad5579c46dfcc7f18207013e65b44e4cb4e2c2298f4ac457ba8f82743f31e930b750f560d912ec02d826af8ba3be90a9481fb6d3bc6b4e7f01a89f245cf0a705968b401b682ba0c9018150cca596358a6b98576337ea10b9cfb0d02441b3bc61a0000000000000fa0eb03488970d05ea240c788a0ea2e07176cc5317b7c7c89f26ac5282bbcd445bd000000000000000c2f6d40e3ac1a172fb9445f9843440a0fc383bea238a7a35a77a3c73d369029920102030405060708090a0b0c00000000'
);

const [decoded, offset] = new ReceiptCoder().decode(arrayify(encoded), 0);
Expand Down
14 changes: 7 additions & 7 deletions packages/transactions/src/coders/receipt.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
/* eslint-disable max-classes-per-file */

import { concat } from '@ethersproject/bytes';
import { Coder, ArrayCoder, ByteCoder, U64Coder, B256Coder, NumberCoder } from '@fuel-ts/abi-coder';
import { arrayify, concat } from '@ethersproject/bytes';
import { Coder, U64Coder, B256Coder, NumberCoder } from '@fuel-ts/abi-coder';
import type { BN } from '@fuel-ts/math';

import { ByteArrayCoder } from './byte-array';

export enum ReceiptType /* u8 */ {
Call = 0,
Return = 1,
Expand Down Expand Up @@ -678,8 +680,6 @@ export class ReceiptMessageOutCoder extends Coder<ReceiptMessageOut, ReceiptMess
}

encode(value: ReceiptMessageOut): Uint8Array {
const dataInNumbers: number[] = [];
value.data.forEach((d) => dataInNumbers.push(d));
const parts: Uint8Array[] = [];

parts.push(new B256Coder().encode(value.messageID));
Expand All @@ -689,7 +689,7 @@ export class ReceiptMessageOutCoder extends Coder<ReceiptMessageOut, ReceiptMess
parts.push(new B256Coder().encode(value.nonce));
parts.push(new NumberCoder('u16').encode(value.data.length));
parts.push(new B256Coder().encode(value.digest));
parts.push(new ArrayCoder(new ByteCoder(false), value.data.length).encode(dataInNumbers));
parts.push(new ByteArrayCoder(value.data.length).encode(value.data));

return concat(parts);
}
Expand All @@ -712,8 +712,8 @@ export class ReceiptMessageOutCoder extends Coder<ReceiptMessageOut, ReceiptMess
const len = decoded;
[decoded, o] = new B256Coder().decode(data, o);
const digest = decoded;
[decoded, o] = new ArrayCoder(new ByteCoder(false), len).decode(data, o);
const messageData = Uint8Array.from(decoded);
[decoded, o] = new ByteArrayCoder(len).decode(data, o);
const messageData = arrayify(decoded);

return [
{
Expand Down
1 change: 1 addition & 0 deletions packages/wallet/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"@fuel-ts/transactions": "workspace:*"
},
"devDependencies": {
"@fuel-ts/address": "workspace:*",
"@fuel-ts/keystore": "workspace:*",
"@fuel-ts/testcases": "workspace:*"
},
Expand Down
48 changes: 46 additions & 2 deletions packages/wallet/src/base-locked-wallet.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import type { BytesLike } from '@ethersproject/bytes';
import { hexlify } from '@ethersproject/bytes';
import { arrayify, hexlify } from '@ethersproject/bytes';
import type { InputValue } from '@fuel-ts/abi-coder';
import { Address, addressify } from '@fuel-ts/address';
import { NativeAssetId } from '@fuel-ts/constants';
import { AbstractWallet } from '@fuel-ts/interfaces';
import type { AbstractAddress, AbstractPredicate } from '@fuel-ts/interfaces';
import type { BigNumberish, BN } from '@fuel-ts/math';
import { bn } from '@fuel-ts/math';
import type {
TransactionResponse,
TransactionRequestLike,
Expand All @@ -18,7 +19,12 @@ import type {
TransactionResult,
Message,
} from '@fuel-ts/providers';
import { ScriptTransactionRequest, Provider, transactionRequestify } from '@fuel-ts/providers';
import {
withdrawScript,
ScriptTransactionRequest,
Provider,
transactionRequestify,
} from '@fuel-ts/providers';
import { MAX_GAS_PER_TX } from '@fuel-ts/transactions';

import { FUEL_NETWORK_URL } from './constants';
Expand Down Expand Up @@ -205,6 +211,44 @@ export class BaseWalletLocked extends AbstractWallet {
return this.sendTransaction(request);
}

/**
* Withdraws an amount of the base asset to the base chain.
*/
async withdrawToBaseLayer(
/** Address of the recipient on the base chain */
recipient: AbstractAddress,
/** Amount of base asset */
amount: BigNumberish,
/** Tx Params */
txParams: Pick<TransactionRequestLike, 'gasLimit' | 'gasPrice' | 'maturity'> = {}
): Promise<TransactionResponse> {
// add recipient and amount to the transaction script code
const recipientDataArray = arrayify(
'0x'.concat(recipient.toHexString().substring(2).padStart(64, '0'))
);
const amountDataArray = arrayify(
'0x'.concat(bn(amount).toHex().substring(2).padStart(16, '0'))
);
const script = new Uint8Array([
...arrayify(withdrawScript.bytes),
...recipientDataArray,
...amountDataArray,
]);

// build the transaction
const params = { script, gasLimit: MAX_GAS_PER_TX, ...txParams };
const request = new ScriptTransactionRequest(params);
request.addMessageOutputs();
const fee = request.calculateFee();
let quantities: CoinQuantityLike[] = [];
fee.amount.add(amount);
quantities = [fee];
const coins = await this.getCoinsToSpend(quantities);
request.addCoins(coins);

return this.sendTransaction(request);
}

/**
* Populates witnesses signature and send it to the network using `provider.sendTransaction`.
*
Expand Down
23 changes: 23 additions & 0 deletions packages/wallet/src/transfer.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { Address } from '@fuel-ts/address';
import { NativeAssetId } from '@fuel-ts/constants';
import { bn } from '@fuel-ts/math';
import type { TransactionResultMessageOutReceipt } from '@fuel-ts/providers';
import { Provider, ScriptTransactionRequest } from '@fuel-ts/providers';

import { generateTestWallet } from './test-utils';
Expand Down Expand Up @@ -114,4 +116,25 @@ describe('Wallet', () => {
])
);
});

it('can withdraw an amount of base asset', async () => {
const provider = new Provider('http://127.0.0.1:4000/graphql');

const sender = await generateTestWallet(provider, [[100, NativeAssetId]]);
const recipient = Address.fromB256(
'0x00000000000000000000000047ba61eec8e5e65247d717ff236f504cf3b0a263'
);
const amount = 10;

const tx = await sender.withdrawToBaseLayer(recipient, 10);
const result = await tx.wait();

const messageOutReceipt = <TransactionResultMessageOutReceipt>result.receipts[0];
expect(result.transactionId).toEqual(messageOutReceipt.sender);
expect(recipient.toHexString()).toEqual(messageOutReceipt.recipient);
expect(amount.toString()).toEqual(messageOutReceipt.amount.toString());

const senderBalances = await sender.getBalances();
expect(senderBalances).toEqual([{ assetId: NativeAssetId, amount: bn(90) }]);
});
});

1 comment on commit 63aa038

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coverage report

St.
Category Percentage Covered / Total
🟢 Statements 90.2% 3597/3988
🟡 Branches 72.77% 700/962
🟢 Functions 87.82% 721/821
🟢 Lines 90.21% 3447/3821

Test suite run success

543 tests passing in 49 suites.

Report generated by 🧪jest coverage report action from 63aa038

Please sign in to comment.