-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
✨ (keyring-eth) [DSDK-435]: Add BuildTransactionContextTask (#215)
- Loading branch information
Showing
12 changed files
with
318 additions
and
50 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"@ledgerhq/keyring-eth": patch | ||
--- | ||
|
||
Add BuildTransactionContextTask |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
210 changes: 210 additions & 0 deletions
210
packages/signer/keyring-eth/src/internal/app-binder/task/BuildTransactionContextTask.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,210 @@ | ||
import { ClearSignContext } from "@ledgerhq/context-module"; | ||
import { Transaction } from "ethers-v6"; | ||
import { Left, Right } from "purify-ts"; | ||
|
||
import { TransactionMapperResult } from "@internal/transaction/service/mapper/model/TransactionMapperResult"; | ||
import { TransactionMapperService } from "@internal/transaction/service/mapper/TransactionMapperService"; | ||
|
||
import { BuildTransactionContextTask } from "./BuildTransactionContextTask"; | ||
|
||
describe("BuildTransactionContextTask", () => { | ||
const contextModuleMock = { | ||
getContexts: jest.fn(), | ||
getTypedDataFilters: jest.fn(), | ||
}; | ||
const mapperMock = { | ||
mapTransactionToSubset: jest.fn(), | ||
}; | ||
const defaultOptions = { | ||
domain: "domain-name.eth", | ||
}; | ||
let defaultTransaction: Transaction; | ||
|
||
beforeEach(() => { | ||
jest.clearAllMocks(); | ||
|
||
defaultTransaction = new Transaction(); | ||
defaultTransaction.chainId = 1n; | ||
defaultTransaction.nonce = 0; | ||
defaultTransaction.data = "0x"; | ||
}); | ||
|
||
it("should build the transaction context without clear sign contexts", async () => { | ||
// GIVEN | ||
const serializedTransaction = new Uint8Array([0x01, 0x02, 0x03]); | ||
const clearSignContexts: ClearSignContext[] = []; | ||
const mapperResult: TransactionMapperResult = { | ||
subset: { chainId: 1, to: undefined, data: "0x" }, | ||
serializedTransaction, | ||
}; | ||
mapperMock.mapTransactionToSubset.mockReturnValueOnce(Right(mapperResult)); | ||
contextModuleMock.getContexts.mockResolvedValueOnce(clearSignContexts); | ||
|
||
// WHEN | ||
const result = await new BuildTransactionContextTask( | ||
contextModuleMock, | ||
mapperMock as unknown as TransactionMapperService, | ||
defaultTransaction, | ||
defaultOptions, | ||
"challenge", | ||
).run(); | ||
|
||
// THEN | ||
expect(result).toEqual({ | ||
clearSignContexts, | ||
serializedTransaction, | ||
}); | ||
}); | ||
|
||
it("should build the transaction context with clear sign contexts", async () => { | ||
// GIVEN | ||
const serializedTransaction = new Uint8Array([0x01, 0x02, 0x03]); | ||
const clearSignContexts: ClearSignContext[] = [ | ||
{ | ||
type: "token", | ||
payload: "payload-1", | ||
}, | ||
{ | ||
type: "nft", | ||
payload: "payload-2", | ||
}, | ||
]; | ||
const mapperResult: TransactionMapperResult = { | ||
subset: { chainId: 1, to: undefined, data: "0x" }, | ||
serializedTransaction, | ||
}; | ||
mapperMock.mapTransactionToSubset.mockReturnValueOnce(Right(mapperResult)); | ||
contextModuleMock.getContexts.mockResolvedValueOnce(clearSignContexts); | ||
|
||
// WHEN | ||
const result = await new BuildTransactionContextTask( | ||
contextModuleMock, | ||
mapperMock as unknown as TransactionMapperService, | ||
defaultTransaction, | ||
defaultOptions, | ||
"challenge", | ||
).run(); | ||
|
||
// THEN | ||
expect(result).toEqual({ | ||
clearSignContexts, | ||
serializedTransaction, | ||
}); | ||
}); | ||
|
||
it("should call the mapper with the transaction", async () => { | ||
// GIVEN | ||
const serializedTransaction = new Uint8Array([0x01, 0x02, 0x03]); | ||
const clearSignContexts: ClearSignContext[] = []; | ||
const mapperResult: TransactionMapperResult = { | ||
subset: { chainId: 1, to: undefined, data: "0x" }, | ||
serializedTransaction, | ||
}; | ||
mapperMock.mapTransactionToSubset.mockReturnValueOnce(Right(mapperResult)); | ||
contextModuleMock.getContexts.mockResolvedValueOnce(clearSignContexts); | ||
|
||
// WHEN | ||
await new BuildTransactionContextTask( | ||
contextModuleMock, | ||
mapperMock as unknown as TransactionMapperService, | ||
defaultTransaction, | ||
defaultOptions, | ||
"challenge", | ||
).run(); | ||
|
||
// THEN | ||
expect(mapperMock.mapTransactionToSubset).toHaveBeenCalledWith( | ||
defaultTransaction, | ||
); | ||
}); | ||
|
||
it("should call the context module with the correct parameters", async () => { | ||
// GIVEN | ||
const serializedTransaction = new Uint8Array([0x01, 0x02, 0x03]); | ||
const clearSignContexts: ClearSignContext[] = []; | ||
const mapperResult: TransactionMapperResult = { | ||
subset: { chainId: 1, to: undefined, data: "0x" }, | ||
serializedTransaction, | ||
}; | ||
mapperMock.mapTransactionToSubset.mockReturnValueOnce(Right(mapperResult)); | ||
contextModuleMock.getContexts.mockResolvedValueOnce(clearSignContexts); | ||
|
||
// WHEN | ||
await new BuildTransactionContextTask( | ||
contextModuleMock, | ||
mapperMock as unknown as TransactionMapperService, | ||
defaultTransaction, | ||
defaultOptions, | ||
"challenge", | ||
).run(); | ||
|
||
// THEN | ||
expect(contextModuleMock.getContexts).toHaveBeenCalledWith({ | ||
challenge: "challenge", | ||
domain: "domain-name.eth", | ||
...mapperResult.subset, | ||
}); | ||
}); | ||
|
||
it("should throw an error if the mapper returns an error", async () => { | ||
// GIVEN | ||
const error = new Error("error"); | ||
mapperMock.mapTransactionToSubset.mockReturnValueOnce(Left(error)); | ||
|
||
// WHEN | ||
const task = new BuildTransactionContextTask( | ||
contextModuleMock, | ||
mapperMock as unknown as TransactionMapperService, | ||
defaultTransaction, | ||
defaultOptions, | ||
"challenge", | ||
); | ||
|
||
// THEN | ||
await expect(task.run()).rejects.toThrow(error); | ||
}); | ||
|
||
it("should exclude error contexts from the result", async () => { | ||
// GIVEN | ||
const serializedTransaction = new Uint8Array([0x01, 0x02, 0x03]); | ||
const clearSignContexts: ClearSignContext[] = [ | ||
{ | ||
type: "error", | ||
error: new Error("error"), | ||
}, | ||
{ | ||
type: "token", | ||
payload: "payload-1", | ||
}, | ||
{ | ||
type: "error", | ||
error: new Error("error"), | ||
}, | ||
{ | ||
type: "nft", | ||
payload: "payload-2", | ||
}, | ||
]; | ||
const mapperResult: TransactionMapperResult = { | ||
subset: { chainId: 1, to: undefined, data: "0x" }, | ||
serializedTransaction, | ||
}; | ||
mapperMock.mapTransactionToSubset.mockReturnValueOnce(Right(mapperResult)); | ||
contextModuleMock.getContexts.mockResolvedValueOnce(clearSignContexts); | ||
|
||
// WHEN | ||
const result = await new BuildTransactionContextTask( | ||
contextModuleMock, | ||
mapperMock as unknown as TransactionMapperService, | ||
defaultTransaction, | ||
defaultOptions, | ||
"challenge", | ||
).run(); | ||
|
||
// THEN | ||
expect(result).toEqual({ | ||
clearSignContexts: [clearSignContexts[1], clearSignContexts[3]], | ||
serializedTransaction, | ||
}); | ||
}); | ||
}); |
47 changes: 47 additions & 0 deletions
47
packages/signer/keyring-eth/src/internal/app-binder/task/BuildTransactionContextTask.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import { | ||
ClearSignContextSuccess, | ||
ContextModule, | ||
} from "@ledgerhq/context-module"; | ||
|
||
import { Transaction } from "@api/model/Transaction"; | ||
import { TransactionOptions } from "@api/model/TransactionOptions"; | ||
import { TransactionMapperService } from "@internal/transaction/service/mapper/TransactionMapperService"; | ||
|
||
export type BuildTransactionTaskResult = { | ||
readonly clearSignContexts: ClearSignContextSuccess[]; | ||
readonly serializedTransaction: Uint8Array; | ||
}; | ||
|
||
export class BuildTransactionContextTask { | ||
constructor( | ||
private contextModule: ContextModule, | ||
private mapper: TransactionMapperService, | ||
private transaction: Transaction, | ||
private options: TransactionOptions, | ||
private challenge: string, | ||
) {} | ||
|
||
async run(): Promise<BuildTransactionTaskResult> { | ||
const parsed = this.mapper.mapTransactionToSubset(this.transaction); | ||
parsed.ifLeft((err) => { | ||
throw err; | ||
}); | ||
const { subset, serializedTransaction } = parsed.unsafeCoerce(); | ||
|
||
const clearSignContexts = await this.contextModule.getContexts({ | ||
challenge: this.challenge, | ||
domain: this.options.domain, | ||
...subset, | ||
}); | ||
|
||
// TODO: for now we ignore the error contexts | ||
// as we consider that they are warnings and not blocking | ||
const clearSignContextsSuccess: ClearSignContextSuccess[] = | ||
clearSignContexts.filter((context) => context.type !== "error"); | ||
|
||
return { | ||
clearSignContexts: clearSignContextsSuccess, | ||
serializedTransaction, | ||
}; | ||
} | ||
} |
Oops, something went wrong.