Skip to content

Commit

Permalink
fix(flow-limit): handle large flow limit values (#294)
Browse files Browse the repository at this point in the history
  • Loading branch information
ahramy authored Nov 7, 2024
1 parent 19d27a4 commit 476e272
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 3 deletions.
2 changes: 2 additions & 0 deletions contracts/interfaces/IFlowLimit.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ pragma solidity ^0.8.0;
*/
interface IFlowLimit {
error FlowLimitExceeded(uint256 limit, uint256 flowAmount, address tokenManager);
error FlowAdditionOverflow(uint256 flowAmount, uint256 flowToAdd, address tokenManager);
error FlowLimitOverflow(uint256 flowLimit, uint256 flowToCompare, address tokenManager);

event FlowLimitSet(bytes32 indexed tokenId, address operator, uint256 flowLimit_);

Expand Down
4 changes: 4 additions & 0 deletions contracts/utils/FlowLimit.sol
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,10 @@ contract FlowLimit is IFlowLimit {
flowToCompare := sload(slotToCompare)
}

// Overflow checks for flowToAdd + flowAmount and flowToCompare + flowLimit_
if (flowToAdd > type(uint256).max - flowAmount) revert FlowAdditionOverflow(flowAmount, flowToAdd, address(this));
if (flowToCompare > type(uint256).max - flowLimit_) revert FlowLimitOverflow(flowLimit_, flowToCompare, address(this));

if (flowToAdd + flowAmount > flowToCompare + flowLimit_)
revert FlowLimitExceeded((flowToCompare + flowLimit_), flowToAdd + flowAmount, address(this));
if (flowAmount > flowLimit_) revert FlowLimitExceeded(flowLimit_, flowAmount, address(this));
Expand Down
65 changes: 62 additions & 3 deletions test/InterchainTokenService.js
Original file line number Diff line number Diff line change
Expand Up @@ -2714,7 +2714,7 @@ describe('Interchain Token Service', () => {
let tokenManager, tokenId;
const sendAmount = 1234;
const flowLimit = (sendAmount * 3) / 2;
const mintAmount = flowLimit * 3;
const mintAmount = MaxUint256;

before(async () => {
[, tokenManager, tokenId] = await deployFunctions.mintBurn(service, 'Test Token Lock Unlock', 'TT', 12, mintAmount);
Expand Down Expand Up @@ -2746,10 +2746,10 @@ describe('Interchain Token Service', () => {
expect(flowIn).to.eq(0);
expect(flowOut).to.eq(sendAmount);

async function receiveToken(sendAmount) {
async function receiveToken(amount) {
const payload = defaultAbiCoder.encode(
['uint256', 'bytes32', 'bytes', 'bytes', 'uint256', 'bytes'],
[MESSAGE_TYPE_INTERCHAIN_TRANSFER, tokenId, hexlify(wallet.address), wallet.address, sendAmount, '0x'],
[MESSAGE_TYPE_INTERCHAIN_TRANSFER, tokenId, hexlify(wallet.address), wallet.address, amount, '0x'],
);
const commandId = await approveContractCall(gateway, destinationChain, service.address, service.address, payload);

Expand All @@ -2776,6 +2776,65 @@ describe('Interchain Token Service', () => {
]);
});

it('Should revert if the flow limit overflows', async () => {
const tokenFlowLimit = await service.flowLimit(tokenId);
expect(tokenFlowLimit).to.eq(flowLimit);

const flowIn = await service.flowInAmount(tokenId);
const flowOut = await service.flowOutAmount(tokenId);

expect(flowIn).to.eq(sendAmount);
expect(flowOut).to.eq(sendAmount);

const newSendAmount = 1;
const newFlowLimit = MaxUint256;

await tokenManager.setFlowLimit(newFlowLimit).then((tx) => tx.wait);

async function receiveToken(amount) {
const payload = defaultAbiCoder.encode(
['uint256', 'bytes32', 'bytes', 'bytes', 'uint256', 'bytes'],
[MESSAGE_TYPE_INTERCHAIN_TRANSFER, tokenId, hexlify(wallet.address), wallet.address, amount, '0x'],
);
const commandId = await approveContractCall(gateway, destinationChain, service.address, service.address, payload);

return service.execute(commandId, destinationChain, service.address, payload);
}

const errorSignatureHash = id('FlowLimitOverflow(uint256,uint256,address)');
const selector = errorSignatureHash.substring(0, 10);
const errorData = defaultAbiCoder.encode(['uint256', 'uint256', 'address'], [newFlowLimit, flowIn, tokenManager.address]);

await expectRevert((gasOptions) => receiveToken(newSendAmount, gasOptions), service, 'GiveTokenFailed', [
selector + errorData.substring(2),
]);
});

it('Should revert if the flow addition overflows', async () => {
const tokenFlowLimit = await service.flowLimit(tokenId);
expect(tokenFlowLimit).to.eq(MaxUint256);

const flowIn = await service.flowInAmount(tokenId);
const flowOut = await service.flowOutAmount(tokenId);

expect(flowIn).to.eq(sendAmount);
expect(flowOut).to.eq(sendAmount);

const newSendAmount = MaxUint256;

const errorSignatureHash = id('FlowAdditionOverflow(uint256,uint256,address)');
const selector = errorSignatureHash.substring(0, 10);
const errorData = defaultAbiCoder.encode(['uint256', 'uint256', 'address'], [newSendAmount, flowOut, tokenManager.address]);

await expectRevert(
(gasOptions) =>
service.interchainTransfer(tokenId, destinationChain, destinationAddress, newSendAmount, '0x', 0, gasOptions),
service,
'TakeTokenFailed',
[selector + errorData.substring(2)],
);
});

it('Should be able to set flow limits for each token manager', async () => {
const tokenIds = [];
const tokenManagers = [];
Expand Down

0 comments on commit 476e272

Please sign in to comment.