Skip to content
This repository was archived by the owner on Mar 11, 2025. It is now read-only.

Commit d7823d7

Browse files
authored
feat: limit permissionless generic call gas usage (#200)
1 parent 4463bcb commit d7823d7

File tree

5 files changed

+103
-5
lines changed

5 files changed

+103
-5
lines changed

contracts/handlers/PermissionlessGenericHandler.sol

+8-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import "../interfaces/IHandler.sol";
1010
@notice This contract is intended to be used with the Bridge contract.
1111
*/
1212
contract PermissionlessGenericHandler is IHandler {
13+
uint256 public constant MAX_FEE = 1000000;
14+
1315
address public immutable _bridgeAddress;
1416

1517
modifier onlyBridge() {
@@ -87,16 +89,19 @@ contract PermissionlessGenericHandler is IHandler {
8789
function deposit(bytes32 resourceID, address depositor, bytes calldata data) external view returns (bytes memory) {
8890
require(data.length >= 76, "Incorrect data length"); // 32 + 2 + 1 + 1 + 20 + 20
8991

92+
uint256 maxFee;
9093
uint16 lenExecuteFuncSignature;
9194
uint8 lenExecuteContractAddress;
9295
uint8 lenExecutionDataDepositor;
9396
address executionDataDepositor;
9497

98+
maxFee = uint256(bytes32(data[:32]));
9599
lenExecuteFuncSignature = uint16(bytes2(data[32:34]));
96100
lenExecuteContractAddress = uint8(bytes1(data[34 + lenExecuteFuncSignature:35 + lenExecuteFuncSignature]));
97101
lenExecutionDataDepositor = uint8(bytes1(data[35 + lenExecuteFuncSignature + lenExecuteContractAddress:36 + lenExecuteFuncSignature + lenExecuteContractAddress]));
98102
executionDataDepositor = address(uint160(bytes20(data[36 + lenExecuteFuncSignature + lenExecuteContractAddress:36 + lenExecuteFuncSignature + lenExecuteContractAddress + lenExecutionDataDepositor])));
99103

104+
require(maxFee < MAX_FEE, 'requested fee too large');
100105
require(depositor == executionDataDepositor, 'incorrect depositor in deposit data');
101106
}
102107

@@ -143,6 +148,7 @@ contract PermissionlessGenericHandler is IHandler {
143148
executeFuncSignature(address executionDataDepositor, uint[] uintArray, address addr)
144149
*/
145150
function executeProposal(bytes32 resourceID, bytes calldata data) external onlyBridge returns (bytes memory) {
151+
uint256 maxFee;
146152
uint16 lenExecuteFuncSignature;
147153
bytes4 executeFuncSignature;
148154
uint8 lenExecuteContractAddress;
@@ -151,6 +157,7 @@ contract PermissionlessGenericHandler is IHandler {
151157
address executionDataDepositor;
152158
bytes memory executionData;
153159

160+
maxFee = uint256(bytes32(data[0:32]));
154161
lenExecuteFuncSignature = uint16(bytes2(data[32:34]));
155162
executeFuncSignature = bytes4(data[34:34 + lenExecuteFuncSignature]);
156163
lenExecuteContractAddress = uint8(bytes1(data[34 + lenExecuteFuncSignature:35 + lenExecuteFuncSignature]));
@@ -160,7 +167,7 @@ contract PermissionlessGenericHandler is IHandler {
160167
executionData = bytes(data[36 + lenExecuteFuncSignature + lenExecuteContractAddress + lenExecutionDataDepositor:]);
161168

162169
bytes memory callData = abi.encodePacked(executeFuncSignature, abi.encode(executionDataDepositor), executionData);
163-
(bool success, bytes memory returndata) = executeContractAddress.call(callData);
170+
(bool success, bytes memory returndata) = executeContractAddress.call{gas: maxFee}(callData);
164171
return abi.encode(success, returndata);
165172
}
166173
}

test/handlers/fee/dynamic/calculateFeeGenericEVM.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ contract("DynamicGenericFeeHandlerEVM - [calculateFee]", async (accounts) => {
1818
const sender = accounts[0];
1919
const depositorAddress = accounts[1];
2020
const emptySetResourceData = "0x";
21-
const destinationMaxFee = 2000000;
21+
const destinationMaxFee = 900000;
2222
const msgGasLimit = 2300000;
2323
const ter = 0; // Not used
2424
const feeDataAmount = 0; // Not used

test/handlers/fee/dynamic/collectFeeGenericEVM.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ contract("DynamicGenericFeeHandlerEVM - [collectFee]", async (accounts) => {
2020
const depositorAddress = accounts[1];
2121

2222
const emptySetResourceData = "0x";
23-
const destinationMaxFee = 2000000;
23+
const destinationMaxFee = 900000;
2424
const msgGasLimit = 2300000;
2525
const hashOfTestStore = Ethers.utils.keccak256("0xc0ffee");
2626
const fee = Ethers.utils.parseEther("0.000036777");

test/handlers/generic/permissionlessDeposit.js

+24-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ contract("PermissionlessGenericHandler - [deposit]", async (accounts) => {
2020
const depositorAddress = accounts[1];
2121

2222
const feeData = "0x";
23-
const destinationMaxFee = 2000000;
23+
const destinationMaxFee = 900000;
2424
const hashOfTestStore = Ethers.utils.keccak256("0xc0ffee");
2525
const emptySetResourceData = "0x";
2626

@@ -152,4 +152,27 @@ contract("PermissionlessGenericHandler - [deposit]", async (accounts) => {
152152
"incorrect depositor in deposit data"
153153
);
154154
});
155+
156+
157+
it("should revert if max fee exceeds 1000000", async () => {
158+
const invalidMaxFee = 1000001;
159+
const invalidDepositData = Helpers.createPermissionlessGenericDepositData(
160+
depositFunctionSignature,
161+
TestStoreInstance.address,
162+
invalidMaxFee ,
163+
depositorAddress,
164+
hashOfTestStore
165+
);
166+
167+
await TruffleAssert.reverts(
168+
BridgeInstance.deposit(
169+
destinationDomainID,
170+
resourceID,
171+
invalidDepositData,
172+
feeData,
173+
{from: depositorAddress}
174+
),
175+
"requested fee too large"
176+
);
177+
});
155178
});

test/handlers/generic/permissionlessExecuteProposal.js

+69-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ contract(
2424
const invalidExecutionContractAddress = accounts[4];
2525

2626
const feeData = "0x";
27-
const destinationMaxFee = 2000000;
27+
const destinationMaxFee = 900000;
2828
const hashOfTestStore = Ethers.utils.keccak256("0xc0ffee");
2929
const handlerResponseLength = 64;
3030
const contractCallReturndata = Ethers.constants.HashZero;
@@ -226,6 +226,74 @@ contract(
226226
);
227227
});
228228

229+
it("ProposalExecution should be emitted even if gas specified too small", async () => {
230+
const num = 6;
231+
const addresses = [BridgeInstance.address, TestStoreInstance.address];
232+
const message = Ethers.utils.hexlify(Ethers.utils.toUtf8Bytes("message"));
233+
const executionData = Helpers.abiEncode(["uint", "address[]", "bytes"], [num, addresses, message]);
234+
235+
// If the target function accepts (address depositor, bytes executionData)
236+
// then this helper can be used
237+
const preparedExecutionData = await TestDepositInstance.prepareDepositData(executionData);
238+
const depositFunctionSignature = Helpers.getFunctionSignature(
239+
TestDepositInstance,
240+
"executePacked"
241+
);
242+
const tooSmallGas = 500;
243+
const depositData = Helpers.createPermissionlessGenericDepositData(
244+
depositFunctionSignature,
245+
TestDepositInstance.address,
246+
tooSmallGas,
247+
depositorAddress,
248+
preparedExecutionData
249+
);
250+
const proposal = {
251+
originDomainID: originDomainID,
252+
depositNonce: expectedDepositNonce,
253+
data: depositData,
254+
resourceID: resourceID,
255+
};
256+
const proposalSignedData = await Helpers.signTypedProposal(
257+
BridgeInstance.address,
258+
[proposal]
259+
);
260+
261+
// relayer1 executes the proposal
262+
const executeTx = await BridgeInstance.executeProposal(
263+
proposal,
264+
proposalSignedData,
265+
{from: relayer1Address}
266+
);
267+
// check that ProposalExecution event is emitted
268+
TruffleAssert.eventEmitted(executeTx, "ProposalExecution", (event) => {
269+
return (
270+
event.originDomainID.toNumber() === originDomainID &&
271+
event.depositNonce.toNumber() === expectedDepositNonce
272+
);
273+
});
274+
275+
// check that deposit nonce isn't unmarked as used in bitmap
276+
assert.isTrue(
277+
await BridgeInstance.isProposalExecuted(
278+
originDomainID,
279+
expectedDepositNonce
280+
)
281+
);
282+
283+
const internalTx = await TruffleAssert.createTransactionResult(
284+
TestDepositInstance,
285+
executeTx.tx
286+
);
287+
TruffleAssert.eventNotEmitted(internalTx, "TestExecute", (event) => {
288+
return (
289+
event.depositor === depositorAddress &&
290+
event.num.toNumber() === num &&
291+
event.addr === TestStoreInstance.address &&
292+
event.message === message
293+
);
294+
});
295+
});
296+
229297
it("call with packed depositData should be successful", async () => {
230298
const num = 5;
231299
const addresses = [BridgeInstance.address, TestStoreInstance.address];

0 commit comments

Comments
 (0)