forked from safe-global/safe-client-gateway
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: decode
multiSend
alerts (safe-global#821)
* feat: decode `multiSend` alerts * fix: use helpers * fix: import mapper * fix: rename + adjust vars.
- Loading branch information
Showing
7 changed files
with
307 additions
and
13 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
34 changes: 34 additions & 0 deletions
34
src/domain/alerts/__tests__/multisend-transactions.encoder.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,34 @@ | ||
import { | ||
Hex, | ||
concat, | ||
encodeFunctionData, | ||
encodePacked, | ||
getAddress, | ||
parseAbi, | ||
size, | ||
} from 'viem'; | ||
|
||
export function multiSendEncoder( | ||
transactions: Array<{ | ||
operation: number; | ||
to: string; | ||
value: bigint; | ||
data: Hex; | ||
}>, | ||
): Hex { | ||
const abi = parseAbi(['function multiSend(bytes memory transactions)']); | ||
|
||
const encodedTransactions = transactions.map( | ||
({ operation, to, value, data }) => | ||
encodePacked( | ||
['uint8', 'address', 'uint256', 'uint256', 'bytes'], | ||
[operation, getAddress(to), value, BigInt(size(data)), data], | ||
), | ||
); | ||
|
||
return encodeFunctionData({ | ||
abi, | ||
functionName: 'multiSend', | ||
args: [concat(encodedTransactions)], | ||
}); | ||
} |
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,95 @@ | ||
import { faker } from '@faker-js/faker'; | ||
import { parseAbi, encodeFunctionData, getAddress, Hex } from 'viem'; | ||
|
||
export function addOwnerWithThresholdEncoder( | ||
{ | ||
owner = faker.finance.ethereumAddress(), | ||
_threshold = faker.number.bigInt(), | ||
}: { | ||
owner: string; | ||
_threshold: bigint; | ||
} = { | ||
owner: faker.finance.ethereumAddress(), | ||
_threshold: faker.number.bigInt(), | ||
}, | ||
): Hex { | ||
const abi = parseAbi([ | ||
'function addOwnerWithThreshold(address owner, uint256 _threshold)', | ||
]); | ||
|
||
return encodeFunctionData({ | ||
abi, | ||
functionName: 'addOwnerWithThreshold', | ||
args: [getAddress(owner), _threshold], | ||
}); | ||
} | ||
|
||
export function removeOwnerEncoder( | ||
{ | ||
prevOwner = faker.finance.ethereumAddress(), | ||
owner = faker.finance.ethereumAddress(), | ||
_threshold = faker.number.bigInt(), | ||
}: { | ||
prevOwner: string; | ||
owner: string; | ||
_threshold: bigint; | ||
} = { | ||
prevOwner: faker.finance.ethereumAddress(), | ||
owner: faker.finance.ethereumAddress(), | ||
_threshold: faker.number.bigInt(), | ||
}, | ||
): Hex { | ||
const ABI = parseAbi([ | ||
'function removeOwner(address prevOwner, address owner, uint256 _threshold)', | ||
]); | ||
|
||
return encodeFunctionData({ | ||
abi: ABI, | ||
functionName: 'removeOwner', | ||
args: [getAddress(prevOwner), getAddress(owner), _threshold], | ||
}); | ||
} | ||
|
||
export function swapOwnerEncoder( | ||
{ | ||
prevOwner = faker.finance.ethereumAddress(), | ||
oldOwner = faker.finance.ethereumAddress(), | ||
newOwner = faker.finance.ethereumAddress(), | ||
}: { | ||
prevOwner: string; | ||
oldOwner: string; | ||
newOwner: string; | ||
} = { | ||
prevOwner: faker.finance.ethereumAddress(), | ||
oldOwner: faker.finance.ethereumAddress(), | ||
newOwner: faker.finance.ethereumAddress(), | ||
}, | ||
): Hex { | ||
const ABI = parseAbi([ | ||
'function swapOwner(address prevOwner, address oldOwner, address newOwner)', | ||
]); | ||
|
||
return encodeFunctionData({ | ||
abi: ABI, | ||
functionName: 'swapOwner', | ||
args: [getAddress(prevOwner), getAddress(oldOwner), getAddress(newOwner)], | ||
}); | ||
} | ||
|
||
export function changeThresholdEncoder( | ||
{ | ||
_threshold = faker.number.bigInt(), | ||
}: { | ||
_threshold: bigint; | ||
} = { | ||
_threshold: faker.number.bigInt(), | ||
}, | ||
): Hex { | ||
const ABI = parseAbi(['function changeThreshold(uint256 _threshold)']); | ||
|
||
return encodeFunctionData({ | ||
abi: ABI, | ||
functionName: 'changeThreshold', | ||
args: [_threshold], | ||
}); | ||
} |
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
40 changes: 40 additions & 0 deletions
40
src/domain/alerts/contracts/multi-send-decoder.helper.spec.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,40 @@ | ||
import { faker } from '@faker-js/faker'; | ||
import { getAddress } from 'viem'; | ||
import { MultiSendDecoder } from '@/domain/alerts/contracts/multi-send-decoder.helper'; | ||
import { | ||
addOwnerWithThresholdEncoder, | ||
changeThresholdEncoder, | ||
removeOwnerEncoder, | ||
swapOwnerEncoder, | ||
} from '@/domain/alerts/__tests__/safe-transactions.encoder'; | ||
import { multiSendEncoder } from '@/domain/alerts/__tests__/multisend-transactions.encoder'; | ||
|
||
describe('MultiSendDecoder', () => { | ||
let mapper: MultiSendDecoder; | ||
|
||
beforeEach(() => { | ||
mapper = new MultiSendDecoder(); | ||
}); | ||
|
||
describe('mapMultiSendTransactions', () => { | ||
it('maps multiSend transactions correctly', () => { | ||
const safeAddress = getAddress(faker.finance.ethereumAddress()); | ||
const transactions = [ | ||
addOwnerWithThresholdEncoder(), | ||
removeOwnerEncoder(), | ||
swapOwnerEncoder(), | ||
changeThresholdEncoder(), | ||
].map((data) => ({ | ||
operation: faker.number.int({ min: 0, max: 1 }), | ||
data, | ||
// Normally static (0/0) but more robust if we generate random values | ||
to: safeAddress, | ||
value: faker.number.bigInt(), | ||
})); | ||
|
||
const data = multiSendEncoder(transactions); | ||
|
||
expect(mapper.mapMultiSendTransactions(data)).toStrictEqual(transactions); | ||
}); | ||
}); | ||
}); |
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,89 @@ | ||
import { Injectable } from '@nestjs/common'; | ||
import { | ||
decodeFunctionData, | ||
getAddress, | ||
Hex, | ||
hexToBigInt, | ||
hexToNumber, | ||
parseAbi, | ||
size, | ||
slice, | ||
} from 'viem'; | ||
|
||
@Injectable() | ||
export class MultiSendDecoder { | ||
private readonly abi = parseAbi([ | ||
'function multiSend(bytes memory transactions)', | ||
]); | ||
|
||
// uint8 operation, address to, value uint256, dataLength uint256, bytes data | ||
private static readonly OPERATION_SIZE = 1; | ||
private static readonly TO_SIZE = 20; | ||
private static readonly VALUE_SIZE = 32; | ||
private static readonly DATA_LENGTH_SIZE = 32; | ||
|
||
mapMultiSendTransactions(multiSendData: Hex): Array<{ | ||
operation: number; | ||
to: string; | ||
value: bigint; | ||
data: Hex; | ||
}> { | ||
const mapped: Array<{ | ||
operation: number; | ||
to: string; | ||
value: bigint; | ||
data: Hex; | ||
}> = []; | ||
|
||
const multiSend = decodeFunctionData({ | ||
abi: this.abi, | ||
data: multiSendData, | ||
}); | ||
|
||
const transactions = multiSend.args[0]; | ||
const transactionsSize = size(transactions); | ||
|
||
let cursor = 0; | ||
|
||
while (cursor < transactionsSize) { | ||
const operation = slice( | ||
transactions, | ||
cursor, | ||
(cursor += MultiSendDecoder.OPERATION_SIZE), | ||
); | ||
|
||
const to = slice( | ||
transactions, | ||
cursor, | ||
(cursor += MultiSendDecoder.TO_SIZE), | ||
); | ||
|
||
const value = slice( | ||
transactions, | ||
cursor, | ||
(cursor += MultiSendDecoder.VALUE_SIZE), | ||
); | ||
|
||
const dataLength = slice( | ||
transactions, | ||
cursor, | ||
(cursor += MultiSendDecoder.DATA_LENGTH_SIZE), | ||
); | ||
|
||
const data = slice( | ||
transactions, | ||
cursor, | ||
(cursor += hexToNumber(dataLength)), | ||
); | ||
|
||
mapped.push({ | ||
operation: hexToNumber(operation), | ||
to: getAddress(to), | ||
value: hexToBigInt(value), | ||
data, | ||
}); | ||
} | ||
|
||
return mapped; | ||
} | ||
} |
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