Skip to content

Commit

Permalink
Expose past redemption requested events getter (#677)
Browse files Browse the repository at this point in the history
Refs: keep-network/keep-core#3664

Here we expose the `Bridge.getRedemptionRequestedEvents` method that
allows fetching past redemption requested events from the `Bridge`
contract.
  • Loading branch information
r-czajkowski committed Aug 2, 2023
2 parents 48e8c05 + 1b42493 commit 9ca1370
Show file tree
Hide file tree
Showing 6 changed files with 152 additions and 4 deletions.
40 changes: 40 additions & 0 deletions typescript/src/bitcoin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -639,3 +639,43 @@ export function createAddressFromOutputScript(
.getAddress()
?.toString(toBcoinNetwork(network))
}

/**
* Reads the leading compact size uint from the provided variable length data.
*
* WARNING: CURRENTLY, THIS FUNCTION SUPPORTS ONLY 1-BYTE COMPACT SIZE UINTS
* AND WILL THROW ON COMPACT SIZE UINTS OF DIFFERENT BYTE LENGTH.
*
* @param varLenData Variable length data preceded by a compact size uint.
* @returns An object holding the value of the compact size uint along with the
* compact size uint byte length.
*/
export function readCompactSizeUint(varLenData: Hex): {
value: number
byteLength: number
} {
// The varLenData is prefixed with the compact size uint. According to the docs
// (https://developer.bitcoin.org/reference/transactions.html#compactsize-unsigned-integers)
// a compact size uint can be 1, 3, 5 or 9 bytes. To determine the exact length,
// we need to look at the discriminant byte which is always the first byte of
// the compact size uint.
const discriminant = varLenData.toString().slice(0, 2)

switch (discriminant) {
case "ff":
case "fe":
case "fd": {
throw new Error(
"support for 3, 5 and 9 bytes compact size uints is not implemented yet"
)
}
default: {
// The discriminant tells the compact size uint is 1 byte. That means
// the discriminant represent the value itself.
return {
value: parseInt(discriminant, 16),
byteLength: 1,
}
}
}
}
8 changes: 7 additions & 1 deletion typescript/src/chain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
OptimisticMintingRequestedEvent,
} from "./optimistic-minting"
import { Hex } from "./hex"
import { RedemptionRequest } from "./redemption"
import { RedemptionRequest, RedemptionRequestedEvent } from "./redemption"
import {
DkgResultApprovedEvent,
DkgResultChallengedEvent,
Expand Down Expand Up @@ -260,6 +260,12 @@ export interface Bridge {
* @returns The hash of the UTXO.
*/
buildUtxoHash(utxo: UnspentTransactionOutput): Hex

/**
* Get emitted RedemptionRequested events.
* @see GetEventsFunction
*/
getRedemptionRequestedEvents: GetEvents.Function<RedemptionRequestedEvent>
}

/**
Expand Down
45 changes: 44 additions & 1 deletion typescript/src/ethereum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,13 @@ import {
DepositRevealedEvent,
} from "./deposit"
import { getEvents, sendWithRetry } from "./ethereum-helpers"
import { RedemptionRequest } from "./redemption"
import { RedemptionRequest, RedemptionRequestedEvent } from "./redemption"
import {
compressPublicKey,
computeHash160,
DecomposedRawTransaction,
Proof,
readCompactSizeUint,
TransactionHash,
UnspentTransactionOutput,
} from "./bitcoin"
Expand Down Expand Up @@ -762,6 +763,48 @@ export class Bridge
)
)
}

// eslint-disable-next-line valid-jsdoc
/**
* @see {ChainBridge#getDepositRevealedEvents}
*/
async getRedemptionRequestedEvents(
options?: GetEvents.Options,
...filterArgs: Array<unknown>
): Promise<RedemptionRequestedEvent[]> {
// FIXME: Filtering by indexed walletPubKeyHash field may not work
// until https://github.com/ethers-io/ethers.js/pull/4244 is
// included in the currently used version of ethers.js.
// Ultimately, we should upgrade ethers.js to include that fix.
// Short-term, we can workaround the problem as presented in:
// https://github.com/threshold-network/token-dashboard/blob/main/src/threshold-ts/tbtc/index.ts#L1041C1-L1093C1
const events: EthersEvent[] = await this.getEvents(
"RedemptionRequested",
options,
...filterArgs
)

return events.map<RedemptionRequestedEvent>((event) => {
const prefixedRedeemerOutputScript = Hex.from(
event.args!.redeemerOutputScript
)
const redeemerOutputScript = prefixedRedeemerOutputScript
.toString()
.slice(readCompactSizeUint(prefixedRedeemerOutputScript).byteLength * 2)

return {
blockNumber: BigNumber.from(event.blockNumber).toNumber(),
blockHash: Hex.from(event.blockHash),
transactionHash: Hex.from(event.transactionHash),
walletPublicKeyHash: Hex.from(event.args!.walletPubKeyHash).toString(),
redeemer: new Address(event.args!.redeemer),
redeemerOutputScript: redeemerOutputScript,
requestedAmount: BigNumber.from(event.args!.requestedAmount),
treasuryFee: BigNumber.from(event.args!.treasuryFee),
txMaxFee: BigNumber.from(event.args!.txMaxFee),
}
})
}
}

/**
Expand Down
16 changes: 15 additions & 1 deletion typescript/src/redemption.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
Client as BitcoinClient,
TransactionHash,
} from "./bitcoin"
import { Bridge, Identifier, TBTCToken } from "./chain"
import { Bridge, Event, Identifier, TBTCToken } from "./chain"
import { assembleTransactionProof } from "./proof"
import { determineWalletMainUtxo, WalletState } from "./wallet"
import { BitcoinNetwork } from "./bitcoin-network"
Expand Down Expand Up @@ -56,6 +56,20 @@ export interface RedemptionRequest {
requestedAt: number
}

/**
* Represents an event emitted on redemption request.
*/
export type RedemptionRequestedEvent = Omit<
RedemptionRequest,
"requestedAt"
> & {
/**
* Public key hash of the wallet that is meant to handle the redemption. Must
* be an unprefixed hex string (without 0x prefix).
*/
walletPublicKeyHash: string
} & Event

/**
* Requests a redemption of tBTC into BTC.
* @param walletPublicKey - The Bitcoin public key of the wallet. Must be in the
Expand Down
38 changes: 38 additions & 0 deletions typescript/test/bitcoin.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
targetToDifficulty,
createOutputScriptFromAddress,
createAddressFromOutputScript,
readCompactSizeUint,
} from "../src/bitcoin"
import { calculateDepositRefundLocktime } from "../src/deposit"
import { BitcoinNetwork } from "../src/bitcoin-network"
Expand Down Expand Up @@ -506,4 +507,41 @@ describe("Bitcoin", () => {
})
})
})

describe("readCompactSizeUint", () => {
context("when the compact size uint is 1-byte", () => {
it("should return the the uint value and byte length", () => {
expect(readCompactSizeUint(Hex.from("bb"))).to.be.eql({
value: 187,
byteLength: 1,
})
})
})

context("when the compact size uint is 3-byte", () => {
it("should throw", () => {
expect(() => readCompactSizeUint(Hex.from("fd0302"))).to.throw(
"support for 3, 5 and 9 bytes compact size uints is not implemented yet"
)
})
})

context("when the compact size uint is 5-byte", () => {
it("should throw", () => {
expect(() => readCompactSizeUint(Hex.from("fe703a0f00"))).to.throw(
"support for 3, 5 and 9 bytes compact size uints is not implemented yet"
)
})
})

context("when the compact size uint is 9-byte", () => {
it("should throw", () => {
expect(() => {
return readCompactSizeUint(Hex.from("ff57284e56dab40000"))
}).to.throw(
"support for 3, 5 and 9 bytes compact size uints is not implemented yet"
)
})
})
})
})
9 changes: 8 additions & 1 deletion typescript/test/utils/mock-bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
UnspentTransactionOutput,
} from "../../src/bitcoin"
import { BigNumberish, BigNumber, utils, constants } from "ethers"
import { RedemptionRequest } from "../redemption"
import { RedemptionRequest, RedemptionRequestedEvent } from "../redemption"
import {
Deposit,
DepositRevealedEvent,
Expand Down Expand Up @@ -365,4 +365,11 @@ export class MockBridge implements Bridge {
)
)
}

getRedemptionRequestedEvents(
options?: GetEvents.Options,
...filterArgs: Array<any>
): Promise<RedemptionRequestedEvent[]> {
throw new Error("not implemented")
}
}

0 comments on commit 9ca1370

Please sign in to comment.