Skip to content

Commit

Permalink
feat: exchange sudt for ckb (#109)
Browse files Browse the repository at this point in the history
* feat: exchange sudt for ckb

* chore: refactor

* chore: modify annotation

* chore: refactor
  • Loading branch information
felicityin authored Aug 15, 2022
1 parent 2acabef commit 5fecc65
Show file tree
Hide file tree
Showing 7 changed files with 419 additions and 6 deletions.
110 changes: 109 additions & 1 deletion packages/ckit/src/__tests__/secp256k1-sudt.spec.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
// because of hot cell problem
// we need to ensure that different genesisSigner is used in different cases

import { utils } from '@ckb-lumos/base';
import { Cell, utils } from '@ckb-lumos/base';
import { key } from '@ckb-lumos/hd';
import { Amount, CkbAmount } from '../helpers';
import { Pw } from '../helpers/pw';
import {
AcpTransferSudtBuilder,
MintOptions,
Expand All @@ -13,6 +14,8 @@ import {
ChequeDepositBuilder,
ChequeClaimBuilder,
ChequeWithdrawBuilder,
ExchangeSudtForCkbBuilder,
ExchangeSudtForCkbOptions,
} from '../tx-builders';
import { randomHexString, asyncSleep, nonNullable } from '../utils';
import { InternalNonAcpPwLockSigner } from '../wallets/PwWallet';
Expand Down Expand Up @@ -819,6 +822,111 @@ test('deposit sudt cheque and claim it', async () => {
eqAmount(await provider.getUdtBalance(receiver.getAddress(), sudt), 500);
});

test('exchange sudt for ckb', async () => {
const provider = new TestProvider();
await provider.init();

const { debug } = provider;
debug('exchange sudt for ckb');

const exchangeProviderSigner = provider.generateAcpSigner('SECP256K1_BLAKE160');
const exchangeProviderAddr = exchangeProviderSigner.getAddress();

const sudtSenderSigner = provider.generateAcpSigner('SECP256K1_BLAKE160');
const sudtSenderAddr = sudtSenderSigner.getAddress();

const recipientSigner = provider.generateAcpSigner('SECP256K1_BLAKE160');
const recipientAddr = recipientSigner.getAddress();

const issuerPrivateKey = provider.testPrivateKeys[testPrivateKeyIndex]!;
const issuerLock = provider.newScript('SECP256K1_BLAKE160', key.privateKeyToBlake160(issuerPrivateKey));
const issuerLockHash = utils.computeScriptHash(issuerLock);

const testUdt = provider.newScript('SUDT', issuerLockHash);

const exchangeProviderCells = await prepareForExchange(provider, exchangeProviderAddr, sudtSenderAddr);

// assume: 1 SUDT = 380 CKB
// transfer 1 SUDT and exchange 1 SUDT for 380 CKB
const builderOptions: ExchangeSudtForCkbOptions = {
sudt: testUdt,
sudtSender: sudtSenderAddr,
sudtAmountForExchange: Amount.from(1, 8).toHex(),
sudtAmountForRecipient: Amount.from(1, 8).toHex(),
exchangeProvider: exchangeProviderCells as Cell[],
// exchangeProvider: exchangeProviderAddr,
ckbAmountForRecipient: CkbAmount.fromCkb(380).toHex(),
exchangeRecipient: recipientAddr,
};

const unsigned = await new ExchangeSudtForCkbBuilder(builderOptions, provider).build();
const partialSignedTx = await exchangeProviderSigner.partialSeal(unsigned);

expect(partialSignedTx != null).toBe(true);
eqAmount(await provider.getUdtBalance(recipientAddr, testUdt), '0x0');
eqAmount(await provider.getUdtBalance(sudtSenderAddr, testUdt), '0xbebc200');
eqAmount(await provider.getUdtBalance(exchangeProviderAddr, testUdt), '0x0');

const fullySignedTx = await sudtSenderSigner.seal(partialSignedTx);
const fullySignedTxHash = await provider.sendTransaction(fullySignedTx);
const exchangeTx = await provider.waitForTransactionCommitted(fullySignedTxHash);

expect(exchangeTx != null).toBe(true);
eqAmount(await provider.getUdtBalance(recipientAddr, testUdt), '0x5f5e100');
eqAmount(await provider.getUdtBalance(sudtSenderAddr, testUdt), '0x0');
eqAmount(await provider.getUdtBalance(exchangeProviderAddr, testUdt), '0x5f5e100');
});

async function prepareForExchange(
provider: TestProvider,
exchangeProviderAddr: string,
sudtSenderAddr: string,
): Promise<Cell[]> {
const { debug } = provider;

// mint sudt for exchangeProvider
const { sudt, txHash } = await provider.mintSudtFromGenesis(
{
recipients: [
{
recipient: exchangeProviderAddr,
amount: Amount.from(0).toHex(),
additionalCapacity: CkbAmount.fromCkb(600).toHex(),
capacityPolicy: 'createCell',
},
],
},
{ testPrivateKeysIndex: testPrivateKeyIndex },
);
debug('[prepare] mint ckb for exchangeProvider: 600 ckb, txHash: %s', txHash);

const exchangProviderPoint = await provider.collectUdtCells(exchangeProviderAddr, sudt, '0');
expect(exchangProviderPoint).toHaveLength(1);

// mint sudt for sudtSender
const res = await provider.mintSudtFromGenesis(
{
recipients: [
{
recipient: sudtSenderAddr,
amount: Amount.from(2, 8).toHex(),
additionalCapacity: CkbAmount.fromCkb(200).toHex(),
capacityPolicy: 'createCell',
},
],
},
{ testPrivateKeysIndex: testPrivateKeyIndex },
);
debug('[prepare] mint sudt for sudtSender: 2 unit, tx hash: %s', res.txHash);

const senderReceived = await provider.collectUdtCells(sudtSenderAddr, sudt, '0');
expect(senderReceived).toHaveLength(1);

const exchangProviderPwCells = exchangProviderPoint.map(Pw.toPwCell);
const exchangProviderCells = exchangProviderPwCells.map(Pw.fromPwCell);
return exchangProviderCells;
}

/**
* Using Tippy to test this case.
* Before testing, modify the value in the [miner.workers] section of the ckb-miner.toml file
Expand Down
2 changes: 1 addition & 1 deletion packages/ckit/src/helpers/pw.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ function fromPwCell(x: PwCell): LumosCell {
lock: fromPwScript(x.lock),
},
out_point: x.outPoint ? { tx_hash: x.outPoint.txHash, index: x.outPoint.index } : undefined,
data: x.getData(),
data: x.getHexData(),
};
}

Expand Down
13 changes: 11 additions & 2 deletions packages/ckit/src/providers/mercury/MercuryProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,13 +152,22 @@ export class MercuryProvider extends AbstractProvider {
}));
}

collectCells({ searchKey }: { searchKey: SearchKey }, takeWhile_: (cell: Cell[]) => boolean): Promise<Cell[]> {
collectCells(
{ searchKey }: { searchKey: SearchKey },
takeWhile_: (cell: Cell[]) => boolean,
options?: { inclusive?: boolean },
): Promise<Cell[]> {
let inclusive = false;
if (options && options.inclusive) {
inclusive = options.inclusive;
}

const cells$ = from(this.mercury.get_cells({ search_key: searchKey })).pipe(
expand((res) => this.mercury.get_cells({ search_key: searchKey, after_cursor: res.last_cursor }), 1),
takeWhile((res) => res.objects.length > 0),
concatMap((res) => res.objects.map(toCell)),
scan((acc, next) => acc.concat(next), [] as Cell[]),
takeWhile((acc) => takeWhile_(acc)),
takeWhile((acc) => takeWhile_(acc), inclusive),
);

return lastValueFrom(cells$, { defaultValue: [] });
Expand Down
Loading

0 comments on commit 5fecc65

Please sign in to comment.