diff --git a/contracts/interfaces/IFlowLimit.sol b/contracts/interfaces/IFlowLimit.sol index 978c8176..c4c83334 100644 --- a/contracts/interfaces/IFlowLimit.sol +++ b/contracts/interfaces/IFlowLimit.sol @@ -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_); diff --git a/contracts/utils/FlowLimit.sol b/contracts/utils/FlowLimit.sol index 9598a925..9c439bbf 100644 --- a/contracts/utils/FlowLimit.sol +++ b/contracts/utils/FlowLimit.sol @@ -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)); diff --git a/test/InterchainTokenService.js b/test/InterchainTokenService.js index dfdb5556..1eafa9d8 100644 --- a/test/InterchainTokenService.js +++ b/test/InterchainTokenService.js @@ -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); @@ -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); @@ -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 = [];