From 0711201668b71c4e927ccde2da898e3ef411c3c3 Mon Sep 17 00:00:00 2001 From: Richard Melkonian Date: Wed, 13 Nov 2024 16:06:14 +0000 Subject: [PATCH 1/9] feat: sketch initial --- .../bridge/contracts/src/RateLimiter.sol | 76 +++++++++++++++++++ .../move-modules/sources/placeholder.move | 6 ++ 2 files changed, 82 insertions(+) create mode 100644 protocol-units/bridge/contracts/src/RateLimiter.sol create mode 100644 protocol-units/bridge/move-modules/sources/placeholder.move diff --git a/protocol-units/bridge/contracts/src/RateLimiter.sol b/protocol-units/bridge/contracts/src/RateLimiter.sol new file mode 100644 index 000000000..7cd9b1cb5 --- /dev/null +++ b/protocol-units/bridge/contracts/src/RateLimiter.sol @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.22; + +import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; + +contract RateLimiter is OwnableUpgradeable { + enum TransferDirection { + L1_TO_L2, + L2_TO_L1 + } + + // Maximum amount that can be transferred in each direction within the risk period + uint256 public rateLimitL1L2; + uint256 public rateLimitL2L1; + + // Track the accumulated budget per transfer direction + uint256 public budgetL1L2; + uint256 public budgetL2L1; + + // Risk period for rate limiting (in seconds) + uint256 public riskPeriod; + + // Security fund balance + uint256 public securityFund; + + event RateLimitExceeded(TransferDirection direction); + event RateLimitUpdated(uint256 newRateLimitL1L2, uint256 newRateLimitL2L1); + event SecurityFundUpdated(uint256 newSecurityFund); + + // Initialize the contract with initial rate limits and risk period + function initialize(uint256 _riskPeriod, uint256 _securityFund) public initializer { + riskPeriod = _riskPeriod; + securityFund = _securityFund; + __Ownable_init(); + _updateRateLimits(); + } + + // Modifier to check if a transfer exceeds the rate limit + modifier withinRateLimit(uint256 amount, TransferDirection direction) { + uint256 currentBudget = (direction == TransferDirection.L1_TO_L2) ? budgetL1L2 : budgetL2L1; + uint256 rateLimit = (direction == TransferDirection.L1_TO_L2) ? rateLimitL1L2 : rateLimitL2L1; + + require(currentBudget + amount <= rateLimit, "RATE_LIMIT_EXCEEDED"); + _; + } + + // Public function to initiate a transfer and check against the rate limit + function initiateTransfer(uint256 amount, TransferDirection direction) external withinRateLimit(amount, direction) { + if (direction == TransferDirection.L1_TO_L2) { + budgetL1L2 += amount; + } else { + budgetL2L1 += amount; + } + } + + // Update the security fund and recalculate rate limits + function updateSecurityFund(uint256 newSecurityFund) external onlyOwner { + securityFund = newSecurityFund; + _updateRateLimits(); + emit SecurityFundUpdated(newSecurityFund); + } + + // Private function to update the rate limits based on the security fund and risk period + function _updateRateLimits() private { + rateLimitL1L2 = (securityFund * 5) / (riskPeriod * 10); // 0.5 * securityFund / riskPeriod + rateLimitL2L1 = (securityFund * 5) / (riskPeriod * 10); // Same calculation as for L1 to L2 + + emit RateLimitUpdated(rateLimitL1L2, rateLimitL2L1); + } + + // Reset the budget for each direction; this could be called periodically or by governance if all transfers are confirmed + function resetBudget() external onlyOwner { + budgetL1L2 = 0; + budgetL2L1 = 0; + } +} diff --git a/protocol-units/bridge/move-modules/sources/placeholder.move b/protocol-units/bridge/move-modules/sources/placeholder.move new file mode 100644 index 000000000..b45972a31 --- /dev/null +++ b/protocol-units/bridge/move-modules/sources/placeholder.move @@ -0,0 +1,6 @@ +//sources/ with a .move file is required for the CLI to build the scripts +module atomic_bridge::some_module { + public fun some_function() { + return + } +} From c0e82aa87b47ac81a211c8e600c11f894c2be523 Mon Sep 17 00:00:00 2001 From: Richard Melkonian Date: Wed, 13 Nov 2024 16:30:26 +0000 Subject: [PATCH 2/9] update: fix init --- protocol-units/bridge/contracts/src/RateLimiter.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/protocol-units/bridge/contracts/src/RateLimiter.sol b/protocol-units/bridge/contracts/src/RateLimiter.sol index 7cd9b1cb5..fadc88da3 100644 --- a/protocol-units/bridge/contracts/src/RateLimiter.sol +++ b/protocol-units/bridge/contracts/src/RateLimiter.sol @@ -28,10 +28,10 @@ contract RateLimiter is OwnableUpgradeable { event SecurityFundUpdated(uint256 newSecurityFund); // Initialize the contract with initial rate limits and risk period - function initialize(uint256 _riskPeriod, uint256 _securityFund) public initializer { + function initialize(address owner, uint256 _riskPeriod, uint256 _securityFund) public initializer { riskPeriod = _riskPeriod; securityFund = _securityFund; - __Ownable_init(); + __Ownable_init(owner); _updateRateLimits(); } From f6d4ba2e3e5ff550d4bd3102480053063c78125a Mon Sep 17 00:00:00 2001 From: Richard Melkonian Date: Wed, 13 Nov 2024 16:53:05 +0000 Subject: [PATCH 3/9] update: RateLimit initiate call --- .../src/AtomicBridgeInitiatorMOVE.sol | 26 ++++++++++++------- .../bridge/contracts/src/RateLimiter.sol | 14 ++++++++-- 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/protocol-units/bridge/contracts/src/AtomicBridgeInitiatorMOVE.sol b/protocol-units/bridge/contracts/src/AtomicBridgeInitiatorMOVE.sol index e9e1226f7..db2752766 100644 --- a/protocol-units/bridge/contracts/src/AtomicBridgeInitiatorMOVE.sol +++ b/protocol-units/bridge/contracts/src/AtomicBridgeInitiatorMOVE.sol @@ -5,6 +5,7 @@ import {IAtomicBridgeInitiatorMOVE} from "./IAtomicBridgeInitiatorMOVE.sol"; import {MockMOVEToken} from "./MockMOVEToken.sol"; import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import {ERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; +import {RateLimiter} from "./RateLimiter.sol"; contract AtomicBridgeInitiatorMOVE is IAtomicBridgeInitiatorMOVE, OwnableUpgradeable { enum MessageState { @@ -35,23 +36,25 @@ contract AtomicBridgeInitiatorMOVE is IAtomicBridgeInitiatorMOVE, OwnableUpgrade // Configurable time lock duration uint256 public initiatorTimeLockDuration; - // Initialize the contract with MOVE token address, owner, custom time lock duration, and initial pool balance + // RateLimiter contract instance + RateLimiter public rateLimiter; + + // Initialize the contract with MOVE token address, owner, custom time lock duration, initial pool balance, and RateLimiter contract address function initialize( address _moveToken, address owner, uint256 _timeLockDuration, - uint256 _initialPoolBalance + uint256 _initialPoolBalance, + address _rateLimiter ) public initializer { - if (_moveToken == address(0)) { - revert ZeroAddress(); - } + if (_moveToken == address(0)) revert ZeroAddress(); + if (_rateLimiter == address(0)) revert ZeroAddress(); + moveToken = ERC20Upgradeable(_moveToken); + rateLimiter = RateLimiter(_rateLimiter); __Ownable_init(owner); - // Set the custom time lock duration initiatorTimeLockDuration = _timeLockDuration; - - // Set the initial pool balance poolBalance = _initialPoolBalance; } @@ -67,8 +70,11 @@ contract AtomicBridgeInitiatorMOVE is IAtomicBridgeInitiatorMOVE, OwnableUpgrade address originator = msg.sender; // Ensure there is a valid amount - if (moveAmount == 0) { - revert ZeroAmount(); + if (moveAmount == 0) revert ZeroAmount(); + + // Check the rate limit before proceeding with the transfer + if (!rateLimiter.initiateTransfer(moveAmount, RateLimiter.TransferDirection.L1_TO_L2)) { + revert("RATE_LIMIT_EXCEEDED"); } // Transfer the MOVE tokens from the user to the contract diff --git a/protocol-units/bridge/contracts/src/RateLimiter.sol b/protocol-units/bridge/contracts/src/RateLimiter.sol index fadc88da3..dbaa05a67 100644 --- a/protocol-units/bridge/contracts/src/RateLimiter.sol +++ b/protocol-units/bridge/contracts/src/RateLimiter.sol @@ -44,13 +44,23 @@ contract RateLimiter is OwnableUpgradeable { _; } - // Public function to initiate a transfer and check against the rate limit - function initiateTransfer(uint256 amount, TransferDirection direction) external withinRateLimit(amount, direction) { + function initiateTransfer(uint256 amount, TransferDirection direction) external returns (bool) { + uint256 currentBudget = (direction == TransferDirection.L1_TO_L2) ? budgetL1L2 : budgetL2L1; + uint256 rateLimit = (direction == TransferDirection.L1_TO_L2) ? rateLimitL1L2 : rateLimitL2L1; + + if (currentBudget + amount > rateLimit) { + emit RateLimitExceeded(direction); + return false; + } + + // Update the budget for the specified direction if (direction == TransferDirection.L1_TO_L2) { budgetL1L2 += amount; } else { budgetL2L1 += amount; } + + return true; } // Update the security fund and recalculate rate limits From aeb3cf116391490616072f9d8407e21313b7adc1 Mon Sep 17 00:00:00 2001 From: Richard Melkonian Date: Wed, 13 Nov 2024 19:31:44 +0000 Subject: [PATCH 4/9] update: counterparty solidity contract --- .../src/AtomicBridgeCounterpartyMOVE.sol | 26 ++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/protocol-units/bridge/contracts/src/AtomicBridgeCounterpartyMOVE.sol b/protocol-units/bridge/contracts/src/AtomicBridgeCounterpartyMOVE.sol index fa2ca4f73..c6bd066a1 100644 --- a/protocol-units/bridge/contracts/src/AtomicBridgeCounterpartyMOVE.sol +++ b/protocol-units/bridge/contracts/src/AtomicBridgeCounterpartyMOVE.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.22; import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import {IAtomicBridgeCounterpartyMOVE} from "./IAtomicBridgeCounterpartyMOVE.sol"; import {AtomicBridgeInitiatorMOVE} from "./AtomicBridgeInitiatorMOVE.sol"; +import {RateLimiter} from "./RateLimiter.sol"; contract AtomicBridgeCounterpartyMOVE is IAtomicBridgeCounterpartyMOVE, OwnableUpgradeable { enum MessageState { @@ -22,17 +23,26 @@ contract AtomicBridgeCounterpartyMOVE is IAtomicBridgeCounterpartyMOVE, OwnableU } AtomicBridgeInitiatorMOVE public atomicBridgeInitiatorMOVE; + RateLimiter public rateLimiter; mapping(bytes32 => BridgeTransferDetails) public bridgeTransfers; // Configurable time lock duration uint256 public counterpartyTimeLockDuration; - function initialize(address _atomicBridgeInitiator, address owner, uint256 _timeLockDuration) public initializer { + // Initialize with initiator, RateLimiter, owner, and time lock duration + function initialize( + address _atomicBridgeInitiator, + address _rateLimiter, + address owner, + uint256 _timeLockDuration + ) public initializer { if (_atomicBridgeInitiator == address(0)) revert ZeroAddress(); + if (_rateLimiter == address(0)) revert ZeroAddress(); + atomicBridgeInitiatorMOVE = AtomicBridgeInitiatorMOVE(_atomicBridgeInitiator); + rateLimiter = RateLimiter(_rateLimiter); __Ownable_init(owner); - // Set the configurable time lock duration counterpartyTimeLockDuration = _timeLockDuration; } @@ -41,6 +51,11 @@ contract AtomicBridgeCounterpartyMOVE is IAtomicBridgeCounterpartyMOVE, OwnableU atomicBridgeInitiatorMOVE = AtomicBridgeInitiatorMOVE(_atomicBridgeInitiator); } + function setRateLimiter(address _rateLimiter) external onlyOwner { + if (_rateLimiter == address(0)) revert ZeroAddress(); + rateLimiter = RateLimiter(_rateLimiter); + } + function setTimeLockDuration(uint256 _timeLockDuration) external onlyOwner { counterpartyTimeLockDuration = _timeLockDuration; } @@ -55,7 +70,12 @@ contract AtomicBridgeCounterpartyMOVE is IAtomicBridgeCounterpartyMOVE, OwnableU if (amount == 0) revert ZeroAmount(); if (atomicBridgeInitiatorMOVE.poolBalance() < amount) revert InsufficientMOVEBalance(); - // The time lock is now based on the configurable duration + bool isWithinRateLimit = rateLimiter.initiateTransfer(amount, RateLimiter.TransferDirection.L2_TO_L1); + if (!isWithinRateLimit) { + revert("RATE_LIMIT_EXCEEDED"); + } + + // The time lock is based on the configurable duration uint256 timeLock = block.timestamp + counterpartyTimeLockDuration; bridgeTransfers[bridgeTransferId] = BridgeTransferDetails({ From 4c4c0a8c4b18859f21fd766f8a0ef6d81095a1e1 Mon Sep 17 00:00:00 2001 From: Richard Melkonian Date: Wed, 13 Nov 2024 19:43:33 +0000 Subject: [PATCH 5/9] update: test Initiator --- .../test/AtomicBridgeInitiatorMOVE.t.sol | 144 +++++------------- 1 file changed, 40 insertions(+), 104 deletions(-) diff --git a/protocol-units/bridge/contracts/test/AtomicBridgeInitiatorMOVE.t.sol b/protocol-units/bridge/contracts/test/AtomicBridgeInitiatorMOVE.t.sol index 0f676f154..1f7b8ffb8 100644 --- a/protocol-units/bridge/contracts/test/AtomicBridgeInitiatorMOVE.t.sol +++ b/protocol-units/bridge/contracts/test/AtomicBridgeInitiatorMOVE.t.sol @@ -8,6 +8,7 @@ import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.s import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; import {MockMOVEToken} from "../src/MockMOVEToken.sol"; import {console} from "forge-std/console.sol"; +import {RateLimiter} from "../src/RateLimiter.sol"; contract AtomicBridgeInitiatorMOVETest is Test { AtomicBridgeInitiatorMOVE public atomicBridgeInitiatorImplementation; @@ -15,56 +16,77 @@ contract AtomicBridgeInitiatorMOVETest is Test { ProxyAdmin public proxyAdmin; TransparentUpgradeableProxy public proxy; AtomicBridgeInitiatorMOVE public atomicBridgeInitiatorMOVE; + RateLimiter public rateLimiter; address public originator = address(1); bytes32 public recipient = keccak256(abi.encodePacked(address(2))); bytes32 public hashLock = keccak256(abi.encodePacked("secret")); uint256 public amount = 1 ether; uint256 public constant timeLockDuration = 48 * 60 * 60; // 48 hours in seconds + uint256 public constant riskPeriod = 24 * 60 * 60; // 24 hours in seconds + uint256 public constant securityFund = 10 ether; // Example security fund function setUp() public { // Deploy the MOVEToken contract and mint some tokens to the deployer moveToken = new MockMOVEToken(); - moveToken.initialize(address(this)); // Contract will hold initial MOVE tokens + moveToken.initialize(address(this)); - originator = vm.addr(uint256(keccak256(abi.encodePacked(block.timestamp, block.prevrandao)))); + // Deploy the RateLimiter contract with owner, riskPeriod, and securityFund + rateLimiter = new RateLimiter(); + rateLimiter.initialize(address(this), riskPeriod, securityFund); - // Deploy the AtomicBridgeInitiatorMOVE contract + // Deploy the AtomicBridgeInitiatorMOVE contract with RateLimiter integration atomicBridgeInitiatorImplementation = new AtomicBridgeInitiatorMOVE(); proxyAdmin = new ProxyAdmin(msg.sender); proxy = new TransparentUpgradeableProxy( address(atomicBridgeInitiatorImplementation), address(proxyAdmin), abi.encodeWithSignature( - "initialize(address,address,uint256,uint256)", + "initialize(address,address,uint256,uint256,address)", address(moveToken), address(this), timeLockDuration, - 0 ether + 0 ether, + address(rateLimiter) ) ); atomicBridgeInitiatorMOVE = AtomicBridgeInitiatorMOVE(address(proxy)); + + // Set up the originator with initial MOVE balance + originator = vm.addr(uint256(keccak256(abi.encodePacked(block.timestamp, block.prevrandao)))); + moveToken.transfer(originator, 10 ether); // Fund originator for testing } - function testInitiateBridgeTransferWithMove() public { - uint256 moveAmount = 100 * 10**8; - // Transfer moveAmount tokens to the originator and check initial balance - moveToken.transfer(originator, moveAmount); - uint256 initialBalance = moveToken.balanceOf(originator); + function testRateLimitExceeded() public { + uint256 moveAmount = 6 ether; // Set to exceed the rate limit based on security fund and risk period + // Transfer MOVE tokens to originator and approve the bridge contract vm.startPrank(originator); moveToken.approve(address(atomicBridgeInitiatorMOVE), moveAmount); - // Initiate the bridge transfer - bytes32 bridgeTransferId = atomicBridgeInitiatorMOVE.initiateBridgeTransfer( - moveAmount, - recipient, - hashLock - ); + // First transfer should succeed if under rate limit + bytes32 bridgeTransferId1 = atomicBridgeInitiatorMOVE.initiateBridgeTransfer(moveAmount / 2, recipient, hashLock); + + // Second transfer should trigger the rate limit if it exceeds the allowed amount + vm.expectRevert("RATE_LIMIT_EXCEEDED"); + atomicBridgeInitiatorMOVE.initiateBridgeTransfer(moveAmount, recipient, hashLock); + + vm.stopPrank(); + } + + function testWithinRateLimit() public { + uint256 moveAmount = 2 ether; // Within rate limit + + // Transfer MOVE tokens to originator and approve the bridge contract + vm.startPrank(originator); + moveToken.approve(address(atomicBridgeInitiatorMOVE), moveAmount); - // Verify the bridge transfer details + // First transfer within limit + bytes32 bridgeTransferId1 = atomicBridgeInitiatorMOVE.initiateBridgeTransfer(moveAmount, recipient, hashLock); + + // Verify transfer details ( uint256 transferAmount, address transferOriginator, @@ -72,7 +94,7 @@ contract AtomicBridgeInitiatorMOVETest is Test { bytes32 transferHashLock, uint256 transferTimeLock, AtomicBridgeInitiatorMOVE.MessageState transferState - ) = atomicBridgeInitiatorMOVE.bridgeTransfers(bridgeTransferId); + ) = atomicBridgeInitiatorMOVE.bridgeTransfers(bridgeTransferId1); assertEq(transferAmount, moveAmount); assertEq(transferOriginator, originator); @@ -81,93 +103,7 @@ contract AtomicBridgeInitiatorMOVETest is Test { assertGt(transferTimeLock, block.timestamp); assertEq(uint8(transferState), uint8(AtomicBridgeInitiatorMOVE.MessageState.INITIALIZED)); - // Check the originator's MOVE balance after initiating the transfer - uint256 finalBalance = moveToken.balanceOf(originator); - assertEq(finalBalance, initialBalance - moveAmount); - vm.stopPrank(); } - - function testCompleteBridgeTransfer() public { - bytes32 secret = "secret"; - bytes32 testHashLock = keccak256(abi.encodePacked(secret)); - uint256 moveAmount = 100 * 10**8; // 100 MOVEToken - - // Transfer moveAmount tokens to the originator and check initial balance - moveToken.transfer(originator, moveAmount); - uint256 initialBalance = moveToken.balanceOf(originator); - - vm.startPrank(originator); - moveToken.approve(address(atomicBridgeInitiatorMOVE), moveAmount); - - // Initiate the bridge transfer - bytes32 bridgeTransferId = atomicBridgeInitiatorMOVE.initiateBridgeTransfer( - moveAmount, - recipient, - testHashLock - ); - - vm.stopPrank(); - - atomicBridgeInitiatorMOVE.completeBridgeTransfer(bridgeTransferId, secret); - - // Verify the bridge transfer details after completion - ( - uint256 completedAmount, - address completedOriginator, - bytes32 completedRecipient, - bytes32 completedHashLock, - uint256 completedTimeLock, - AtomicBridgeInitiatorMOVE.MessageState completedState - ) = atomicBridgeInitiatorMOVE.bridgeTransfers(bridgeTransferId); - - assertEq(completedAmount, moveAmount); - assertEq(completedOriginator, originator); - assertEq(completedRecipient, recipient); - assertEq(completedHashLock, testHashLock); - assertGt(completedTimeLock, block.timestamp); - assertEq(uint8(completedState), uint8(AtomicBridgeInitiatorMOVE.MessageState.COMPLETED)); - - // Ensure no changes to the originator's balance after the transfer is completed - uint256 finalBalance = moveToken.balanceOf(originator); - assertEq(finalBalance, initialBalance - moveAmount); - } - - function testRefundBridgeTransfer() public { - uint256 moveAmount = 100 * 10**8; // 100 MOVEToken - - // Transfer moveAmount tokens to the originator and check initial balance - moveToken.transfer(originator, moveAmount); - uint256 initialBalance = moveToken.balanceOf(originator); - - vm.startPrank(originator); - moveToken.approve(address(atomicBridgeInitiatorMOVE), moveAmount); - - // Initiate the bridge transfer - bytes32 bridgeTransferId = atomicBridgeInitiatorMOVE.initiateBridgeTransfer( - moveAmount, - recipient, - hashLock - ); - vm.stopPrank(); - - // Advance time and block height to ensure the time lock has expired - vm.warp(block.timestamp + timeLockDuration + 1); - - // Test that a non-owner cannot call refund - vm.startPrank(originator); - vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, originator)); - atomicBridgeInitiatorMOVE.refundBridgeTransfer(bridgeTransferId); - vm.stopPrank(); - - // Owner refunds the transfer - vm.expectEmit(); - emit IAtomicBridgeInitiatorMOVE.BridgeTransferRefunded(bridgeTransferId); - atomicBridgeInitiatorMOVE.refundBridgeTransfer(bridgeTransferId); - - // Verify that the originator receives the refund and the balance is restored - uint256 finalBalance = moveToken.balanceOf(originator); - assertEq(finalBalance, initialBalance, "MOVE balance mismatch"); - } } From bbdcf8f9512b084c2db4c7804720e2ba964516be Mon Sep 17 00:00:00 2001 From: Richard Melkonian Date: Wed, 13 Nov 2024 19:44:57 +0000 Subject: [PATCH 6/9] fix: bring back old cases --- .../test/AtomicBridgeInitiatorMOVE.t.sol | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/protocol-units/bridge/contracts/test/AtomicBridgeInitiatorMOVE.t.sol b/protocol-units/bridge/contracts/test/AtomicBridgeInitiatorMOVE.t.sol index 1f7b8ffb8..f8051d5a0 100644 --- a/protocol-units/bridge/contracts/test/AtomicBridgeInitiatorMOVE.t.sol +++ b/protocol-units/bridge/contracts/test/AtomicBridgeInitiatorMOVE.t.sol @@ -58,6 +58,88 @@ contract AtomicBridgeInitiatorMOVETest is Test { moveToken.transfer(originator, 10 ether); // Fund originator for testing } +function testCompleteBridgeTransfer() public { + bytes32 secret = "secret"; + bytes32 testHashLock = keccak256(abi.encodePacked(secret)); + uint256 moveAmount = 100 * 10**8; // 100 MOVEToken + + // Transfer moveAmount tokens to the originator and check initial balance + moveToken.transfer(originator, moveAmount); + uint256 initialBalance = moveToken.balanceOf(originator); + + vm.startPrank(originator); + moveToken.approve(address(atomicBridgeInitiatorMOVE), moveAmount); + + // Initiate the bridge transfer + bytes32 bridgeTransferId = atomicBridgeInitiatorMOVE.initiateBridgeTransfer( + moveAmount, + recipient, + testHashLock + ); + + vm.stopPrank(); + + atomicBridgeInitiatorMOVE.completeBridgeTransfer(bridgeTransferId, secret); + + // Verify the bridge transfer details after completion + ( + uint256 completedAmount, + address completedOriginator, + bytes32 completedRecipient, + bytes32 completedHashLock, + uint256 completedTimeLock, + AtomicBridgeInitiatorMOVE.MessageState completedState + ) = atomicBridgeInitiatorMOVE.bridgeTransfers(bridgeTransferId); + + assertEq(completedAmount, moveAmount); + assertEq(completedOriginator, originator); + assertEq(completedRecipient, recipient); + assertEq(completedHashLock, testHashLock); + assertGt(completedTimeLock, block.timestamp); + assertEq(uint8(completedState), uint8(AtomicBridgeInitiatorMOVE.MessageState.COMPLETED)); + + // Ensure no changes to the originator's balance after the transfer is completed + uint256 finalBalance = moveToken.balanceOf(originator); + assertEq(finalBalance, initialBalance - moveAmount); + } + + function testRefundBridgeTransfer() public { + uint256 moveAmount = 100 * 10**8; // 100 MOVEToken + + // Transfer moveAmount tokens to the originator and check initial balance + moveToken.transfer(originator, moveAmount); + uint256 initialBalance = moveToken.balanceOf(originator); + + vm.startPrank(originator); + moveToken.approve(address(atomicBridgeInitiatorMOVE), moveAmount); + + // Initiate the bridge transfer + bytes32 bridgeTransferId = atomicBridgeInitiatorMOVE.initiateBridgeTransfer( + moveAmount, + recipient, + hashLock + ); + vm.stopPrank(); + + // Advance time and block height to ensure the time lock has expired + vm.warp(block.timestamp + timeLockDuration + 1); + + // Test that a non-owner cannot call refund + vm.startPrank(originator); + vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, originator)); + atomicBridgeInitiatorMOVE.refundBridgeTransfer(bridgeTransferId); + vm.stopPrank(); + + // Owner refunds the transfer + vm.expectEmit(); + emit IAtomicBridgeInitiatorMOVE.BridgeTransferRefunded(bridgeTransferId); + atomicBridgeInitiatorMOVE.refundBridgeTransfer(bridgeTransferId); + + // Verify that the originator receives the refund and the balance is restored + uint256 finalBalance = moveToken.balanceOf(originator); + assertEq(finalBalance, initialBalance, "MOVE balance mismatch"); + } + function testRateLimitExceeded() public { uint256 moveAmount = 6 ether; // Set to exceed the rate limit based on security fund and risk period From ffac4825aa01c3485a5e4405dc52ce452b0af907 Mon Sep 17 00:00:00 2001 From: Richard Melkonian Date: Thu, 14 Nov 2024 10:53:00 +0000 Subject: [PATCH 7/9] update: init for test and add new test case --- .../test/AtomicBridgeInitiatorMOVE.t.sol | 131 ++++++++++++------ 1 file changed, 85 insertions(+), 46 deletions(-) diff --git a/protocol-units/bridge/contracts/test/AtomicBridgeInitiatorMOVE.t.sol b/protocol-units/bridge/contracts/test/AtomicBridgeInitiatorMOVE.t.sol index f8051d5a0..e94da2d5f 100644 --- a/protocol-units/bridge/contracts/test/AtomicBridgeInitiatorMOVE.t.sol +++ b/protocol-units/bridge/contracts/test/AtomicBridgeInitiatorMOVE.t.sol @@ -23,19 +23,22 @@ contract AtomicBridgeInitiatorMOVETest is Test { bytes32 public hashLock = keccak256(abi.encodePacked("secret")); uint256 public amount = 1 ether; uint256 public constant timeLockDuration = 48 * 60 * 60; // 48 hours in seconds - uint256 public constant riskPeriod = 24 * 60 * 60; // 24 hours in seconds - uint256 public constant securityFund = 10 ether; // Example security fund function setUp() public { // Deploy the MOVEToken contract and mint some tokens to the deployer moveToken = new MockMOVEToken(); - moveToken.initialize(address(this)); + moveToken.initialize(address(this)); // Contract will hold initial MOVE tokens - // Deploy the RateLimiter contract with owner, riskPeriod, and securityFund + // Set up the originator with an address derived from a hash + originator = vm.addr(uint256(keccak256(abi.encodePacked(block.timestamp, block.prevrandao)))); + + // Deploy and initialize the RateLimiter contract rateLimiter = new RateLimiter(); + uint256 riskPeriod = 24 * 60 * 60; // 24 hours in seconds + uint256 securityFund = 10 ether; // Example security fund rateLimiter.initialize(address(this), riskPeriod, securityFund); - // Deploy the AtomicBridgeInitiatorMOVE contract with RateLimiter integration + // Deploy the AtomicBridgeInitiatorMOVE contract with the RateLimiter instance atomicBridgeInitiatorImplementation = new AtomicBridgeInitiatorMOVE(); proxyAdmin = new ProxyAdmin(msg.sender); proxy = new TransparentUpgradeableProxy( @@ -53,12 +56,54 @@ contract AtomicBridgeInitiatorMOVETest is Test { atomicBridgeInitiatorMOVE = AtomicBridgeInitiatorMOVE(address(proxy)); - // Set up the originator with initial MOVE balance - originator = vm.addr(uint256(keccak256(abi.encodePacked(block.timestamp, block.prevrandao)))); - moveToken.transfer(originator, 10 ether); // Fund originator for testing + // Fund the originator for testing with initial MOVE balance + uint256 moveAmount = 100 * 10**8; // 100 MOVEToken + moveToken.transfer(originator, moveAmount); + } + + + function testInitiateBridgeTransferWithMove() public { + uint256 moveAmount = 100 * 10**8; + + // Transfer moveAmount tokens to the originator and check initial balance + moveToken.transfer(originator, moveAmount); + uint256 initialBalance = moveToken.balanceOf(originator); + + vm.startPrank(originator); + moveToken.approve(address(atomicBridgeInitiatorMOVE), moveAmount); + + // Initiate the bridge transfer + bytes32 bridgeTransferId = atomicBridgeInitiatorMOVE.initiateBridgeTransfer( + moveAmount, + recipient, + hashLock + ); + + // Verify the bridge transfer details + ( + uint256 transferAmount, + address transferOriginator, + bytes32 transferRecipient, + bytes32 transferHashLock, + uint256 transferTimeLock, + AtomicBridgeInitiatorMOVE.MessageState transferState + ) = atomicBridgeInitiatorMOVE.bridgeTransfers(bridgeTransferId); + + assertEq(transferAmount, moveAmount); + assertEq(transferOriginator, originator); + assertEq(transferRecipient, recipient); + assertEq(transferHashLock, hashLock); + assertGt(transferTimeLock, block.timestamp); + assertEq(uint8(transferState), uint8(AtomicBridgeInitiatorMOVE.MessageState.INITIALIZED)); + + // Check the originator's MOVE balance after initiating the transfer + uint256 finalBalance = moveToken.balanceOf(originator); + assertEq(finalBalance, initialBalance - moveAmount); + + vm.stopPrank(); } -function testCompleteBridgeTransfer() public { + function testCompleteBridgeTransfer() public { bytes32 secret = "secret"; bytes32 testHashLock = keccak256(abi.encodePacked(secret)); uint256 moveAmount = 100 * 10**8; // 100 MOVEToken @@ -140,52 +185,46 @@ function testCompleteBridgeTransfer() public { assertEq(finalBalance, initialBalance, "MOVE balance mismatch"); } - function testRateLimitExceeded() public { - uint256 moveAmount = 6 ether; // Set to exceed the rate limit based on security fund and risk period + uint256 moveAmount = 6 ether; // An amount designed to exceed the rate limit in the second transfer. - // Transfer MOVE tokens to originator and approve the bridge contract + // Transfer tokens to the originator and set up their approval + moveToken.transfer(originator, 10 ether); // Ensure originator has enough tokens for multiple transfers vm.startPrank(originator); - moveToken.approve(address(atomicBridgeInitiatorMOVE), moveAmount); - - // First transfer should succeed if under rate limit - bytes32 bridgeTransferId1 = atomicBridgeInitiatorMOVE.initiateBridgeTransfer(moveAmount / 2, recipient, hashLock); - - // Second transfer should trigger the rate limit if it exceeds the allowed amount - vm.expectRevert("RATE_LIMIT_EXCEEDED"); - atomicBridgeInitiatorMOVE.initiateBridgeTransfer(moveAmount, recipient, hashLock); + moveToken.approve(address(atomicBridgeInitiatorMOVE), 10 ether); - vm.stopPrank(); - } - - function testWithinRateLimit() public { - uint256 moveAmount = 2 ether; // Within rate limit - - // Transfer MOVE tokens to originator and approve the bridge contract - vm.startPrank(originator); - moveToken.approve(address(atomicBridgeInitiatorMOVE), moveAmount); - - // First transfer within limit - bytes32 bridgeTransferId1 = atomicBridgeInitiatorMOVE.initiateBridgeTransfer(moveAmount, recipient, hashLock); + // First transfer: within the rate limit + uint256 initialBalance = moveToken.balanceOf(originator); + bytes32 bridgeTransferId1 = atomicBridgeInitiatorMOVE.initiateBridgeTransfer( + moveAmount / 2, + recipient, + hashLock + ); - // Verify transfer details + // Verify that the first transfer succeeded ( - uint256 transferAmount, - address transferOriginator, - bytes32 transferRecipient, - bytes32 transferHashLock, - uint256 transferTimeLock, - AtomicBridgeInitiatorMOVE.MessageState transferState + uint256 transferAmount1, + , + , + , + , + AtomicBridgeInitiatorMOVE.MessageState transferState1 ) = atomicBridgeInitiatorMOVE.bridgeTransfers(bridgeTransferId1); + assertEq(transferAmount1, moveAmount / 2); + assertEq(uint8(transferState1), uint8(AtomicBridgeInitiatorMOVE.MessageState.INITIALIZED)); - assertEq(transferAmount, moveAmount); - assertEq(transferOriginator, originator); - assertEq(transferRecipient, recipient); - assertEq(transferHashLock, hashLock); - assertGt(transferTimeLock, block.timestamp); - assertEq(uint8(transferState), uint8(AtomicBridgeInitiatorMOVE.MessageState.INITIALIZED)); + // Second transfer: attempt to exceed the rate limit + vm.expectRevert("RATE_LIMIT_EXCEEDED"); + atomicBridgeInitiatorMOVE.initiateBridgeTransfer( + moveAmount, // This second transfer will exceed the rate limit + recipient, + hashLock + ); + + // Verify that the originator’s balance reflects only the first transfer + uint256 finalBalance = moveToken.balanceOf(originator); + assertEq(finalBalance, initialBalance - (moveAmount / 2)); vm.stopPrank(); } } - From 297d9719ad31ac20fd745d34736f74c5490f97f6 Mon Sep 17 00:00:00 2001 From: Richard Melkonian Date: Thu, 14 Nov 2024 13:04:01 +0000 Subject: [PATCH 8/9] fix: RateLimit test --- .../test/AtomicBridgeCounterpartyMOVE.t.sol | 27 +++++++++++++------ .../test/AtomicBridgeInitiatorMOVE.t.sol | 10 +++---- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/protocol-units/bridge/contracts/test/AtomicBridgeCounterpartyMOVE.t.sol b/protocol-units/bridge/contracts/test/AtomicBridgeCounterpartyMOVE.t.sol index 2a7aae30e..7ed060298 100644 --- a/protocol-units/bridge/contracts/test/AtomicBridgeCounterpartyMOVE.t.sol +++ b/protocol-units/bridge/contracts/test/AtomicBridgeCounterpartyMOVE.t.sol @@ -8,12 +8,14 @@ import {AtomicBridgeInitiatorMOVE} from "../src/AtomicBridgeInitiatorMOVE.sol"; import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; import {MockMOVEToken} from "../src/MockMOVEToken.sol"; +import {RateLimiter} from "../src/RateLimiter.sol"; contract AtomicBridgeCounterpartyMOVETest is Test { AtomicBridgeCounterpartyMOVE public atomicBridgeCounterpartyMOVEImplementation; AtomicBridgeCounterpartyMOVE public atomicBridgeCounterpartyMOVE; AtomicBridgeInitiatorMOVE public atomicBridgeInitiatorMOVEImplementation; AtomicBridgeInitiatorMOVE public atomicBridgeInitiatorMOVE; + RateLimiter public rateLimiter; MockMOVEToken public moveToken; ProxyAdmin public proxyAdmin; TransparentUpgradeableProxy public proxy; @@ -23,7 +25,7 @@ contract AtomicBridgeCounterpartyMOVETest is Test { address public recipient = address(0x2); address public otherUser = address(0x3); bytes32 public hashLock = keccak256(abi.encodePacked("secret")); - uint256 public amount = 100 * 10 ** 8; // 100 MOVEToken (assuming 8 decimals) + uint256 public amount = 100 * 10 ** 8; uint256 public timeLock = 100; bytes32 public initiator = keccak256(abi.encodePacked(deployer)); bytes32 public bridgeTransferId = @@ -51,37 +53,45 @@ contract AtomicBridgeCounterpartyMOVETest is Test { originator = vm.addr(uint256(keccak256(abi.encodePacked(block.timestamp, block.prevrandao)))); - // Deploy the AtomicBridgeInitiator contract with a 48-hour time lock + // Deploy and initialize the RateLimiter contract + rateLimiter = new RateLimiter(); + uint256 riskPeriod = 24 * 60 * 60; // 24 hours in seconds + uint256 securityFund = 10 ether; // Example security fund + rateLimiter.initialize(address(this), riskPeriod, securityFund); + + // Deploy the AtomicBridgeInitiatorMOVE contract with a 48-hour time lock and RateLimiter instance atomicBridgeInitiatorMOVEImplementation = new AtomicBridgeInitiatorMOVE(); proxyAdmin = new ProxyAdmin(deployer); proxy = new TransparentUpgradeableProxy( address(atomicBridgeInitiatorMOVEImplementation), address(proxyAdmin), abi.encodeWithSignature( - "initialize(address,address,uint256,uint256)", + "initialize(address,address,uint256,uint256,address)", address(moveToken), - deployer, + deployer, initiatorTimeLockDuration, - 0 ether // Initial pool balance + 0 ether, // Initial pool balance + address(rateLimiter) ) ); atomicBridgeInitiatorMOVE = AtomicBridgeInitiatorMOVE(address(proxy)); - // Deploy the AtomicBridgeCounterparty contract with a 24-hour time lock + // Deploy the AtomicBridgeCounterpartyMOVE contract with a 24-hour time lock and RateLimiter instance atomicBridgeCounterpartyMOVEImplementation = new AtomicBridgeCounterpartyMOVE(); proxy = new TransparentUpgradeableProxy( address(atomicBridgeCounterpartyMOVEImplementation), address(proxyAdmin), abi.encodeWithSignature( - "initialize(address,address,uint256)", + "initialize(address,address,address,uint256)", address(atomicBridgeInitiatorMOVE), + address(rateLimiter), deployer, counterpartyTimeLockDuration ) ); atomicBridgeCounterpartyMOVE = AtomicBridgeCounterpartyMOVE(address(proxy)); - // Set the counterparty contract in the AtomicBridgeInitiator contract + // Set the counterparty contract in the AtomicBridgeInitiatorMOVE contract vm.startPrank(deployer); atomicBridgeInitiatorMOVE.setCounterpartyAddress( address(atomicBridgeCounterpartyMOVE) @@ -89,6 +99,7 @@ contract AtomicBridgeCounterpartyMOVETest is Test { vm.stopPrank(); } + function testLockBridgeTransfer() public { uint256 moveAmount = 100 * 10**8; moveToken.transfer(originator, moveAmount); diff --git a/protocol-units/bridge/contracts/test/AtomicBridgeInitiatorMOVE.t.sol b/protocol-units/bridge/contracts/test/AtomicBridgeInitiatorMOVE.t.sol index e94da2d5f..1b0b4b207 100644 --- a/protocol-units/bridge/contracts/test/AtomicBridgeInitiatorMOVE.t.sol +++ b/protocol-units/bridge/contracts/test/AtomicBridgeInitiatorMOVE.t.sol @@ -35,7 +35,7 @@ contract AtomicBridgeInitiatorMOVETest is Test { // Deploy and initialize the RateLimiter contract rateLimiter = new RateLimiter(); uint256 riskPeriod = 24 * 60 * 60; // 24 hours in seconds - uint256 securityFund = 10 ether; // Example security fund + uint256 securityFund = 5 ether; // Example security fund rateLimiter.initialize(address(this), riskPeriod, securityFund); // Deploy the AtomicBridgeInitiatorMOVE contract with the RateLimiter instance @@ -186,10 +186,8 @@ contract AtomicBridgeInitiatorMOVETest is Test { } function testRateLimitExceeded() public { - uint256 moveAmount = 6 ether; // An amount designed to exceed the rate limit in the second transfer. + uint256 moveAmount = 100 * 10**8; // 100 MOVEToken - // Transfer tokens to the originator and set up their approval - moveToken.transfer(originator, 10 ether); // Ensure originator has enough tokens for multiple transfers vm.startPrank(originator); moveToken.approve(address(atomicBridgeInitiatorMOVE), 10 ether); @@ -210,17 +208,19 @@ contract AtomicBridgeInitiatorMOVETest is Test { , AtomicBridgeInitiatorMOVE.MessageState transferState1 ) = atomicBridgeInitiatorMOVE.bridgeTransfers(bridgeTransferId1); + console.log("initiated"); assertEq(transferAmount1, moveAmount / 2); assertEq(uint8(transferState1), uint8(AtomicBridgeInitiatorMOVE.MessageState.INITIALIZED)); // Second transfer: attempt to exceed the rate limit vm.expectRevert("RATE_LIMIT_EXCEEDED"); atomicBridgeInitiatorMOVE.initiateBridgeTransfer( - moveAmount, // This second transfer will exceed the rate limit + 5 ether, recipient, hashLock ); + // Verify that the originator’s balance reflects only the first transfer uint256 finalBalance = moveToken.balanceOf(originator); assertEq(finalBalance, initialBalance - (moveAmount / 2)); From 2e329cd59e3988cbe57d25adc3f9aa016e48a849 Mon Sep 17 00:00:00 2001 From: Richard Melkonian Date: Thu, 14 Nov 2024 13:24:53 +0000 Subject: [PATCH 9/9] update: counterparty ratelimit test --- .../test/AtomicBridgeCounterpartyMOVE.t.sol | 215 +++++++++++------- .../test/AtomicBridgeInitiatorMOVE.t.sol | 4 +- 2 files changed, 141 insertions(+), 78 deletions(-) diff --git a/protocol-units/bridge/contracts/test/AtomicBridgeCounterpartyMOVE.t.sol b/protocol-units/bridge/contracts/test/AtomicBridgeCounterpartyMOVE.t.sol index 7ed060298..08c9e631a 100644 --- a/protocol-units/bridge/contracts/test/AtomicBridgeCounterpartyMOVE.t.sol +++ b/protocol-units/bridge/contracts/test/AtomicBridgeCounterpartyMOVE.t.sol @@ -56,7 +56,7 @@ contract AtomicBridgeCounterpartyMOVETest is Test { // Deploy and initialize the RateLimiter contract rateLimiter = new RateLimiter(); uint256 riskPeriod = 24 * 60 * 60; // 24 hours in seconds - uint256 securityFund = 10 ether; // Example security fund + uint256 securityFund = 5 ether; rateLimiter.initialize(address(this), riskPeriod, securityFund); // Deploy the AtomicBridgeInitiatorMOVE contract with a 48-hour time lock and RateLimiter instance @@ -191,89 +191,152 @@ contract AtomicBridgeCounterpartyMOVETest is Test { uint256 completedAmount, bytes32 completedHashLock, uint256 completedTimeLock, - AtomicBridgeCounterpartyMOVE.MessageState completedState + AtomicBridgeCounterpartyMOVE.MessageState completedState + ) = atomicBridgeCounterpartyMOVE.bridgeTransfers(bridgeTransferId); + + assertEq(completedInitiator, initiator); + assertEq(completedRecipient, recipient); + assertEq(completedAmount, amount); + assertEq(completedHashLock, testHashLock); + assertGt(completedTimeLock, block.timestamp); + assertEq( + uint8(completedState), + uint8(AtomicBridgeCounterpartyMOVE.MessageState.COMPLETED) + ); + + vm.stopPrank(); + } + + function testAbortBridgeTransfer() public { + uint256 moveAmount = 100 * 10**8; + moveToken.transfer(originator, moveAmount); + vm.startPrank(originator); + + // Approve the AtomicBridgeInitiatorMOVE contract to spend MOVEToken + moveToken.approve(address(atomicBridgeInitiatorMOVE), amount); + + // Initiate the bridge transfer + atomicBridgeInitiatorMOVE.initiateBridgeTransfer( + amount, + initiator, + hashLock + ); + + vm.stopPrank(); + + vm.startPrank(deployer); + + atomicBridgeCounterpartyMOVE.lockBridgeTransfer( + initiator, + bridgeTransferId, + hashLock, + recipient, + amount + ); + + vm.stopPrank(); + + // Advance the block number to beyond the timelock period + vm.warp(block.timestamp + COUNTERPARTY_TIME_LOCK_DURATION + 1); + + // Try to abort as a malicious user (this should fail) + //vm.startPrank(otherUser); + //vm.expectRevert("Ownable: caller is not the owner"); + //atomicBridgeCounterpartyMOVE.abortBridgeTransfer(bridgeTransferId); + //vm.stopPrank(); + + // Abort as the owner (this should pass) + vm.startPrank(deployer); // The deployer is the owner + atomicBridgeCounterpartyMOVE.abortBridgeTransfer(bridgeTransferId); + + ( + bytes32 abortedInitiator, + address abortedRecipient, + uint256 abortedAmount, + bytes32 abortedHashLock, + uint256 abortedTimeLock, + AtomicBridgeCounterpartyMOVE.MessageState abortedState ) = atomicBridgeCounterpartyMOVE.bridgeTransfers(bridgeTransferId); - assertEq(completedInitiator, initiator); - assertEq(completedRecipient, recipient); - assertEq(completedAmount, amount); - assertEq(completedHashLock, testHashLock); - assertGt(completedTimeLock, block.timestamp); + assertEq(abortedInitiator, initiator); + assertEq(abortedRecipient, recipient); + assertEq(abortedAmount, amount); + assertEq(abortedHashLock, hashLock); + assertLe( + abortedTimeLock, + block.timestamp, + "Timelock is not less than or equal to current timestamp" + ); assertEq( - uint8(completedState), - uint8(AtomicBridgeCounterpartyMOVE.MessageState.COMPLETED) + uint8(abortedState), + uint8(AtomicBridgeCounterpartyMOVE.MessageState.REFUNDED) ); vm.stopPrank(); } -function testAbortBridgeTransfer() public { - uint256 moveAmount = 100 * 10**8; - moveToken.transfer(originator, moveAmount); - vm.startPrank(originator); - - // Approve the AtomicBridgeInitiatorMOVE contract to spend MOVEToken - moveToken.approve(address(atomicBridgeInitiatorMOVE), amount); - - // Initiate the bridge transfer - atomicBridgeInitiatorMOVE.initiateBridgeTransfer( - amount, - initiator, - hashLock - ); - - vm.stopPrank(); - - vm.startPrank(deployer); - - atomicBridgeCounterpartyMOVE.lockBridgeTransfer( - initiator, - bridgeTransferId, - hashLock, - recipient, - amount - ); - - vm.stopPrank(); - - // Advance the block number to beyond the timelock period - vm.warp(block.timestamp + COUNTERPARTY_TIME_LOCK_DURATION + 1); - - // Try to abort as a malicious user (this should fail) - //vm.startPrank(otherUser); - //vm.expectRevert("Ownable: caller is not the owner"); - //atomicBridgeCounterpartyMOVE.abortBridgeTransfer(bridgeTransferId); - //vm.stopPrank(); - - // Abort as the owner (this should pass) - vm.startPrank(deployer); // The deployer is the owner - atomicBridgeCounterpartyMOVE.abortBridgeTransfer(bridgeTransferId); - - ( - bytes32 abortedInitiator, - address abortedRecipient, - uint256 abortedAmount, - bytes32 abortedHashLock, - uint256 abortedTimeLock, - AtomicBridgeCounterpartyMOVE.MessageState abortedState - ) = atomicBridgeCounterpartyMOVE.bridgeTransfers(bridgeTransferId); - - assertEq(abortedInitiator, initiator); - assertEq(abortedRecipient, recipient); - assertEq(abortedAmount, amount); - assertEq(abortedHashLock, hashLock); - assertLe( - abortedTimeLock, - block.timestamp, - "Timelock is not less than or equal to current timestamp" - ); - assertEq( - uint8(abortedState), - uint8(AtomicBridgeCounterpartyMOVE.MessageState.REFUNDED) - ); - - vm.stopPrank(); -} + function testRateLimitExceeded() public { + uint256 moveAmount = 100 * 10**8; // 100 MOVEToken + moveToken.transfer(originator, moveAmount); + + vm.startPrank(originator); + moveToken.approve(address(atomicBridgeInitiatorMOVE), moveAmount); + + // Initiate the first bridge transfer through the initiator + atomicBridgeInitiatorMOVE.initiateBridgeTransfer( + moveAmount / 2, + initiator, + hashLock + ); + + vm.stopPrank(); + + // Start locking the transfer on the counterparty side + vm.startPrank(deployer); + + // Lock the first transfer (within rate limit) + atomicBridgeCounterpartyMOVE.lockBridgeTransfer( + initiator, + bridgeTransferId, + hashLock, + recipient, + moveAmount / 2 + ); + // Verify the first transfer was locked successfully + ( + bytes32 lockedInitiator, + address lockedRecipient, + uint256 lockedAmount, + bytes32 lockedHashLock, + uint256 lockedTimeLock, + AtomicBridgeCounterpartyMOVE.MessageState lockedState + ) = atomicBridgeCounterpartyMOVE.bridgeTransfers(bridgeTransferId); + assertEq(lockedInitiator, initiator); + assertEq(lockedRecipient, recipient); + assertEq(lockedAmount, moveAmount / 2); + assertEq(lockedHashLock, hashLock); + assertGt(lockedTimeLock, block.timestamp); + assertEq( + uint8(lockedState), + uint8(AtomicBridgeCounterpartyMOVE.MessageState.PENDING) + ); + + // Attempt a second transfer to exceed the rate limit + bytes32 secondBridgeTransferId = keccak256( + abi.encodePacked(block.timestamp, initiator, recipient, moveAmount, hashLock, timeLock) + ); + // Expect the second transfer to exceed the rate limit and revert + vm.expectRevert("RATE_LIMIT_EXCEEDED"); + atomicBridgeCounterpartyMOVE.lockBridgeTransfer( + initiator, + secondBridgeTransferId, + hashLock, + recipient, + moveAmount + ); + + vm.stopPrank(); + } } diff --git a/protocol-units/bridge/contracts/test/AtomicBridgeInitiatorMOVE.t.sol b/protocol-units/bridge/contracts/test/AtomicBridgeInitiatorMOVE.t.sol index 1b0b4b207..639df61bb 100644 --- a/protocol-units/bridge/contracts/test/AtomicBridgeInitiatorMOVE.t.sol +++ b/protocol-units/bridge/contracts/test/AtomicBridgeInitiatorMOVE.t.sol @@ -35,7 +35,7 @@ contract AtomicBridgeInitiatorMOVETest is Test { // Deploy and initialize the RateLimiter contract rateLimiter = new RateLimiter(); uint256 riskPeriod = 24 * 60 * 60; // 24 hours in seconds - uint256 securityFund = 5 ether; // Example security fund + uint256 securityFund = 5 ether; rateLimiter.initialize(address(this), riskPeriod, securityFund); // Deploy the AtomicBridgeInitiatorMOVE contract with the RateLimiter instance @@ -189,7 +189,7 @@ contract AtomicBridgeInitiatorMOVETest is Test { uint256 moveAmount = 100 * 10**8; // 100 MOVEToken vm.startPrank(originator); - moveToken.approve(address(atomicBridgeInitiatorMOVE), 10 ether); + moveToken.approve(address(atomicBridgeInitiatorMOVE), moveAmount); // First transfer: within the rate limit uint256 initialBalance = moveToken.balanceOf(originator);