Skip to content

Commit

Permalink
fix: oracle cross rate exchange calculations, tests, documentation (#434
Browse files Browse the repository at this point in the history
)

* fix: oracle cross rate exchange calculations, tests, documentation

CHAIN-711

* fix: copyright header for oracle.spec.ts
  • Loading branch information
sentientforest authored Nov 13, 2024
1 parent 32d93e7 commit dc9a281
Show file tree
Hide file tree
Showing 4 changed files with 156 additions and 5 deletions.
28 changes: 27 additions & 1 deletion chain-api/src/types/ChainId.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,32 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* @description
*
* Define identifiers of other blockchains external to GalaChain, or
* other channels within the GalaChain ecosystem.
*
* Primarily used for bridging operations.
*
*/
export enum ChainId {
Ethereum = 2
Ethereum = 2,
TON = 1001
}

/**
* @description
*
* Defines the list of external blockchains and/or GalaChain
* channels that can potentially be configured by channel operators
* to support bridge fees on `RequestTokenBridgeOut` actions.
*
* @remarks
*
* See also the `OracleBridgeFeeAssertion` within the `gala-chain/api` package,
* and the `requestTokenBridgeOutFeeGate` implementation in the
* `gala-chain/chaincode` package.
*/
export const ChainsWithBridgeFeeSupport: ChainId[] = [ChainId.Ethereum, ChainId.TON];
92 changes: 92 additions & 0 deletions chain-api/src/types/oracle.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* Copyright (c) Gala Games Inc. All rights reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import BigNumber from "bignumber.js";
import { plainToInstance } from "class-transformer";

import { ExternalToken } from "./OraclePriceAssertion";
import { TokenInstanceKey } from "./TokenInstance";
import { createValidDTO } from "./dtos";
import { OraclePriceAssertionDto, OraclePriceCrossRateAssertionDto } from "./oracle";

describe("oracle.ts", () => {
const mockOracle = "mock-oracle";
const mockOracleIdentity = "client|mock-oracle";

const galaTokenInstanceKey = plainToInstance(TokenInstanceKey, {
collection: "GALA",
category: "Unit",
type: "none",
additionalKey: "none"
});

const usdDetails = plainToInstance(ExternalToken, {
name: "USD",
symbol: "usd"
});

const tonDetails = plainToInstance(ExternalToken, {
name: "Toncoin",
symbol: "ton"
});

test("cross rate calculations", async () => {
const mockTonUsdQuote = {
"the-open-network": {
usd: 5.5
}
};

const mockGalaUsdQuote = {
gala: {
usd: 0.025
}
};

const tonUsdPriceAssertion = await createValidDTO(OraclePriceAssertionDto, {
oracle: mockOracle,
identity: mockOracleIdentity,
externalBaseToken: tonDetails,
externalQuoteToken: usdDetails,
exchangeRate: new BigNumber(mockTonUsdQuote["the-open-network"].usd),
timestamp: 0
});

const galaUsdPriceAssertion = await createValidDTO(OraclePriceAssertionDto, {
oracle: mockOracle,
identity: mockOracleIdentity,
baseToken: galaTokenInstanceKey,
externalQuoteToken: usdDetails,
exchangeRate: new BigNumber(mockGalaUsdQuote.gala.usd),
timestamp: 0
});

const mockAssertionDto = await createValidDTO(OraclePriceCrossRateAssertionDto, {
oracle: mockOracle,
identity: mockOracleIdentity,
baseTokenCrossRate: tonUsdPriceAssertion,
quoteTokenCrossRate: galaUsdPriceAssertion,
externalCrossRateToken: usdDetails,
crossRate: new BigNumber("220")
});

const voidResult = mockAssertionDto.validateCrossRate();

expect(voidResult).toBeUndefined();

mockAssertionDto.crossRate = new BigNumber("42");

expect(mockAssertionDto.validateCrossRate).toThrow();
});
});
35 changes: 33 additions & 2 deletions chain-api/src/types/oracle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -319,14 +319,44 @@ export class OraclePriceCrossRateAssertionDto extends ChainCallDTO {
}
}

/**
* @description
*
* Calculate the cross-rate using the instantiated instance's
* base and quote token exchange rates.
*
* Requires valid cross-rate token keys: For our purposes we expect the
* base exchange rate and the quote exchange rate to use a common
* quote token to faciliate the cross-rate calculation.
*
* @remarks
*
* The quote token exchange rate is inverted to yield a cross-rate
* exchange of "$n of quote token per 1 base token."
*
* @example
*
* Quote GALA in TON using hypothetical exchange rates:
*
* GALA is quoted in USD at $0.025 USD per GALA (quoteTokenCrossRate).
*
* TON is quoted in USD at $5.50 USD per TON. (baseTokenCrossRate).
*
* The GALA cross-rate is inverted, yielding 40 GALA per 1 USD.
*
* Multiply USD/TON x GALA/USD to yield a cross-rate of 220 GALA per 1 TON.
*
*/
@Exclude()
public calculateCrossRate() {
this.validateCrossRateTokenKeys();

const quoteTokenCrossRate = this.quoteTokenCrossRate.exchangeRate;
const invertedQuoteTokenCrossRate = new BigNumber("1").dividedBy(quoteTokenCrossRate);

const baseTokenCrossRate = this.baseTokenCrossRate.exchangeRate;

const calculatedCrossRate = quoteTokenCrossRate.dividedBy(baseTokenCrossRate);
const calculatedCrossRate = baseTokenCrossRate.times(invertedQuoteTokenCrossRate);

return calculatedCrossRate;
}
Expand All @@ -337,7 +367,8 @@ export class OraclePriceCrossRateAssertionDto extends ChainCallDTO {

if (!this.crossRate.isEqualTo(calculatedCrossRate)) {
throw new ValidationFailedError(
`Asserted cross rate (${this.crossRate} is not equal to calculated cross rate)`
`Asserted cross rate (${this.crossRate.toNumber()} is not equal to ` +
`the calculated cross rate: ${calculatedCrossRate.toNumber()})`
);
}
}
Expand Down
6 changes: 4 additions & 2 deletions chaincode/src/fees/feeGateImplementations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
ChainError,
ChainId,
ChainObject,
ChainsWithBridgeFeeSupport,
ErrorCode,
FeeAccelerationRateType,
FeeCodeDefinition,
Expand Down Expand Up @@ -215,8 +216,9 @@ export async function requestTokenBridgeOutFeeGate(ctx: GalaChainContext, dto: R
const { destinationChainId } = dto;

// Dynamic, gas based fees are intended for bridging outside of GalaChain
// In the future this list could include Solana, or other external Chains
if (destinationChainId !== ChainId.Ethereum) {
// Different external chain may have differing methods of calculating transaction fees
// Supported chains are currently defined in @gala-chain/api
if (!ChainsWithBridgeFeeSupport.includes(destinationChainId)) {
return;
}

Expand Down

0 comments on commit dc9a281

Please sign in to comment.