Skip to content

Commit

Permalink
Merge branch '102-decimals-drop-cardano' into 'dev'
Browse files Browse the repository at this point in the history
consider decimals drop in CardanoChain

Closes #102

See merge request ergo/rosen-bridge/rosen-chains!119
  • Loading branch information
vorujack committed Jul 24, 2024
2 parents b2df4ec + 4fe286d commit be5c5a9
Show file tree
Hide file tree
Showing 5 changed files with 463 additions and 103 deletions.
5 changes: 5 additions & 0 deletions .changeset/unlucky-socks-occur.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@rosen-chains/cardano': major
---

consider decimals drop
76 changes: 60 additions & 16 deletions packages/chains/cardano/lib/CardanoChain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,16 @@ class CardanoChain extends AbstractUtxoChain<CardanoTx, CardanoUtxo> {
)
continue;

const assetBalance = CardanoUtils.getBoxAssets(output);
const wrappedBalance = ChainUtils.wrapAssetBalance(
assetBalance,
this.tokenMap,
this.NATIVE_TOKEN_ID,
this.CHAIN
);
const payment: SinglePayment = {
address: output.address().to_bech32(),
assets: CardanoUtils.getBoxAssets(output),
assets: wrappedBalance,
};
order.push(payment);
}
Expand All @@ -109,19 +116,29 @@ class CardanoChain extends AbstractUtxoChain<CardanoTx, CardanoUtxo> {
`Generating Cardano transaction for Order: ${JsonBigInt.stringify(order)}`
);
// calculate required assets
const fee = this.configs.fee;
const requiredAssets = order
.map((order) => order.assets)
.reduce(ChainUtils.sumAssetBalance, {
nativeToken: this.getMinimumNativeToken() + this.configs.fee,
nativeToken:
this.getMinimumNativeToken() +
this.tokenMap.wrapAmount(this.NATIVE_TOKEN_ID, fee, this.CHAIN)
.amount,
tokens: [],
});
this.logger.debug(
`Required assets: ${JsonBigInt.stringify(requiredAssets)}`
);
const unwrappedRequiredAssets = ChainUtils.unwrapAssetBalance(
requiredAssets,
this.tokenMap,
this.NATIVE_TOKEN_ID,
this.CHAIN
);

if (!(await this.hasLockAddressEnoughAssets(requiredAssets))) {
const neededADA = requiredAssets.nativeToken.toString();
const neededTokens = JSONBigInt.stringify(requiredAssets.tokens);
const neededADA = unwrappedRequiredAssets.nativeToken.toString();
const neededTokens = JSONBigInt.stringify(unwrappedRequiredAssets.tokens);
throw new NotEnoughAssetsError(
`Locked assets cannot cover required assets. ADA: ${neededADA}, Tokens: ${neededTokens}`
);
Expand All @@ -144,13 +161,13 @@ class CardanoChain extends AbstractUtxoChain<CardanoTx, CardanoUtxo> {

const coveredBoxes = await this.getCoveringBoxes(
this.configs.addresses.lock,
requiredAssets,
unwrappedRequiredAssets,
forbiddenBoxIds,
trackMap
);
if (!coveredBoxes.covered) {
const neededAdas = requiredAssets.nativeToken.toString();
const neededTokens = JSONBigInt.stringify(requiredAssets.tokens);
const neededAdas = unwrappedRequiredAssets.nativeToken.toString();
const neededTokens = JSONBigInt.stringify(unwrappedRequiredAssets.tokens);
throw new NotEnoughValidBoxesError(
`Available boxes didn't cover required assets. ADA: ${neededAdas}, Tokens: ${neededTokens}`
);
Expand All @@ -171,20 +188,30 @@ class CardanoChain extends AbstractUtxoChain<CardanoTx, CardanoUtxo> {
throw Error('Cardano does not support extra data in payment order');
}
// accumulate value
const orderLovelace = this.tokenMap.unwrapAmount(
this.NATIVE_TOKEN_ID,
order.assets.nativeToken,
this.CHAIN
).amount;
orderValue = orderValue.checked_add(
CardanoUtils.bigIntToBigNum(order.assets.nativeToken)
CardanoUtils.bigIntToBigNum(orderLovelace)
);

// reduce order value from remaining assets
remainingAssets = ChainUtils.subtractAssetBalance(
remainingAssets,
order.assets
ChainUtils.unwrapAssetBalance(
order.assets,
this.tokenMap,
this.NATIVE_TOKEN_ID,
this.CHAIN
)
);

// create order output
const address = CardanoWasm.Address.from_bech32(order.address);
const value = CardanoWasm.Value.new(
CardanoUtils.bigIntToBigNum(order.assets.nativeToken)
CardanoUtils.bigIntToBigNum(orderLovelace)
);
// inserting assets
const orderMultiAsset = CardanoWasm.MultiAsset.new();
Expand All @@ -198,7 +225,9 @@ class CardanoChain extends AbstractUtxoChain<CardanoTx, CardanoUtxo> {
orderMultiAsset.set_asset(
policyId,
assetName,
CardanoUtils.bigIntToBigNum(asset.value)
CardanoUtils.bigIntToBigNum(
this.tokenMap.unwrapAmount(asset.id, asset.value, this.CHAIN).amount
)
);
});
value.set_multiasset(orderMultiAsset);
Expand All @@ -223,7 +252,7 @@ class CardanoChain extends AbstractUtxoChain<CardanoTx, CardanoUtxo> {
);

// create change output
remainingAssets.nativeToken -= this.configs.fee;
remainingAssets.nativeToken -= fee;
const changeBox = CardanoUtils.createTransactionOutput(
remainingAssets,
this.configs.addresses.lock
Expand All @@ -232,7 +261,7 @@ class CardanoChain extends AbstractUtxoChain<CardanoTx, CardanoUtxo> {

// set ttl and fee
txBuilder.set_ttl((await this.network.currentSlot()) + this.configs.txTtl);
txBuilder.set_fee(CardanoUtils.bigIntToBigNum(this.configs.fee));
txBuilder.set_fee(CardanoUtils.bigIntToBigNum(fee));

// create the transaction
const txBody = txBuilder.build();
Expand Down Expand Up @@ -262,6 +291,7 @@ class CardanoChain extends AbstractUtxoChain<CardanoTx, CardanoUtxo> {

/**
* extracts box id and assets of a box
* Note: it returns the actual value
* @param box the box
* @returns an object containing the box id and assets
*/
Expand Down Expand Up @@ -366,8 +396,18 @@ class CardanoChain extends AbstractUtxoChain<CardanoTx, CardanoUtxo> {
}

return {
inputAssets,
outputAssets,
inputAssets: ChainUtils.wrapAssetBalance(
inputAssets,
this.tokenMap,
this.NATIVE_TOKEN_ID,
this.CHAIN
),
outputAssets: ChainUtils.wrapAssetBalance(
outputAssets,
this.tokenMap,
this.NATIVE_TOKEN_ID,
this.CHAIN
),
};
};

Expand Down Expand Up @@ -544,7 +584,11 @@ class CardanoChain extends AbstractUtxoChain<CardanoTx, CardanoUtxo> {
* @returns the minimum amount
*/
getMinimumNativeToken = () => {
return this.configs.minBoxValue;
return this.tokenMap.wrapAmount(
this.NATIVE_TOKEN_ID,
this.configs.minBoxValue,
this.CHAIN
).amount;
};

/**
Expand Down
135 changes: 133 additions & 2 deletions packages/chains/cardano/tests/CardanoChain.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,81 @@ describe('CardanoChain', () => {
);
}).rejects.toThrow(NotEnoughValidBoxesError);
});

/**
* @target CardanoChain.generateTransaction should generate payment
* transaction with wrapped order successfully
* @dependencies
* @scenario
* - mock transaction order, currentSlot
* - mock getCoveringBoxes, hasLockAddressEnoughAssets
* - call the function
* - check returned value
* @expected
* - PaymentTransaction txType, eventId, network and inputUtxos should be as
* expected
* - extracted order of generated transaction should be the same as input
* order
* - transaction fee and ttl should be the same as config fee
* - tokens with same policyId and should put correctly
*/
it('should generate payment transaction with wrapped order successfully', async () => {
// mock transaction order, currentSlot
const order = TestData.transaction4WrappedOrder;
const payment = CardanoTransaction.fromJson(
TestData.transaction4PaymentTransaction
);
const getSlotSpy = spyOn(network, 'currentSlot');
getSlotSpy.mockResolvedValue(200);

// mock getCoveringBoxes, hasLockAddressEnoughAssets
const cardanoChain =
TestUtils.generateChainObjectWithMultiDecimalTokenMap(network);
const getCovBoxesSpy = spyOn(cardanoChain as any, 'getCoveringBoxes');
getCovBoxesSpy.mockResolvedValue({
covered: true,
boxes: bankBoxes.slice(2),
});
const hasLockAddressEnoughAssetsSpy = spyOn(
cardanoChain,
'hasLockAddressEnoughAssets'
);
hasLockAddressEnoughAssetsSpy.mockResolvedValue(true);

// call the function
const result = await cardanoChain.generateTransaction(
'2bedc6e54ede7748e5efc7df689a0a89b281ac1d92d09054650d5f27a25d5b85',
TransactionType.payment,
order,
[],
[]
);
const cardanoTx = result as CardanoTransaction;

// check returned value
expect(cardanoTx.txType).toEqual(payment.txType);
expect(cardanoTx.eventId).toEqual(payment.eventId);
expect(cardanoTx.network).toEqual(payment.network);
expect(cardanoTx.inputUtxos).toEqual(
bankBoxes.slice(2).map((utxo) => JsonBI.stringify(utxo))
);

// extracted order of generated transaction should be the same as input order
const extractedOrder = cardanoChain.extractTransactionOrder(cardanoTx);
expect(extractedOrder).toEqual(order);

// transaction fee and ttl should be the same as input configs
const tx = Transaction.from_bytes(cardanoTx.txBytes);
expect(tx.body().fee().to_str()).toEqual(
TestUtils.configs.fee.toString()
);
expect(tx.body().ttl()).toEqual(264);

// tokens with same policyId and should put correctly
expect(
tx.body().outputs().get(1).amount().multiasset()!.to_json()
).toEqual(TestData.transaction4ChangeBoxMultiAssets.trim());
});
});

describe('extractTransactionOrder', () => {
Expand Down Expand Up @@ -275,6 +350,33 @@ describe('CardanoChain', () => {
// check returned value
expect(result).toEqual(expectedOrder);
});

/**
* @target CardanoChain.extractTransactionOrder should wrap transaction
* order successfully
* @dependencies
* @scenario
* - mock PaymentTransaction
* - call the function
* - check returned value
* @expected
* - it should return mocked transaction order
*/
it('should wrap transaction order successfully', () => {
// mock PaymentTransaction
const paymentTx = CardanoTransaction.fromJson(
TestData.transaction1PaymentTransaction
);
const expectedOrder = TestData.transaction1WrappedOrder;

// call the function
const cardanoChain =
TestUtils.generateChainObjectWithMultiDecimalTokenMap(network);
const result = cardanoChain.extractTransactionOrder(paymentTx);

// check returned value
expect(result).toEqual(expectedOrder);
});
});

describe('getBoxInfo', () => {
Expand Down Expand Up @@ -426,6 +528,35 @@ describe('CardanoChain', () => {
const result = await cardanoChain.getTransactionAssets(paymentTx);
expect(result.inputAssets).toEqual(TestData.transaction6InputAssets);
});

/**
* @target CardanoChain.getTransactionAssets should wrap transaction assets
* successfully
* @dependencies
* @scenario
* - mock PaymentTransaction
* - call the function
* - check returned value
* @expected
* - it should return mocked transaction assets (both input and output assets)
*/
it('should wrap transaction assets successfully', async () => {
// mock PaymentTransaction
const paymentTx = CardanoTransaction.fromJson(
TestData.transaction1PaymentTransaction
);

// call the function
const cardanoChain =
TestUtils.generateChainObjectWithMultiDecimalTokenMap(network);

// check returned value
const result = await cardanoChain.getTransactionAssets(paymentTx);
expect(result.inputAssets).toEqual(
TestData.transaction1WrappedInputAssets
);
expect(result.outputAssets).toEqual(TestData.transaction1WrappedAssets);
});
});

describe('getMempoolBoxMapping', () => {
Expand Down Expand Up @@ -562,7 +693,7 @@ describe('CardanoChain', () => {
const testInstance = new TestCardanoChain(
network,
TestUtils.configs,
TestUtils.rosenTokens,
TestData.testTokenMap,
null as any
);

Expand Down Expand Up @@ -1028,7 +1159,7 @@ describe('CardanoChain', () => {
const cardanoChain = new CardanoChain(
network,
newConfigs,
TestUtils.rosenTokens,
TestData.testTokenMap,
TestUtils.mockedSignFn
);

Expand Down
Loading

0 comments on commit be5c5a9

Please sign in to comment.