diff --git a/protocol-units/bridge/contracts/src/AtomicBridgeInitiatorMOVE.sol b/protocol-units/bridge/contracts/src/AtomicBridgeInitiatorMOVE.sol index 3bff65592..09c0db80d 100644 --- a/protocol-units/bridge/contracts/src/AtomicBridgeInitiatorMOVE.sol +++ b/protocol-units/bridge/contracts/src/AtomicBridgeInitiatorMOVE.sol @@ -34,6 +34,8 @@ contract AtomicBridgeInitiatorMOVE is IAtomicBridgeInitiatorMOVE, OwnableUpgrade ERC20Upgradeable public moveToken; uint256 private nonce; + uint256 public sponsoredTransferFee; + // Configurable time lock duration uint256 public initiatorTimeLockDuration; @@ -42,7 +44,8 @@ contract AtomicBridgeInitiatorMOVE is IAtomicBridgeInitiatorMOVE, OwnableUpgrade address _moveToken, address owner, uint256 _timeLockDuration, - uint256 _initialPoolBalance + uint256 _initialPoolBalance, + uint256 _sponsoredTransferFee ) public initializer { require(_moveToken != address(0) && owner != address(0), "ZeroAddress"); moveToken = ERC20Upgradeable(_moveToken); @@ -53,6 +56,10 @@ contract AtomicBridgeInitiatorMOVE is IAtomicBridgeInitiatorMOVE, OwnableUpgrade // Set the initial pool balance poolBalance = _initialPoolBalance; + + // Set the sponsored transfer fee + sponsoredTransferFee = _sponsoredTransferFee; + } function setCounterpartyAddress(address _counterpartyAddress) external onlyOwner { @@ -65,6 +72,10 @@ contract AtomicBridgeInitiatorMOVE is IAtomicBridgeInitiatorMOVE, OwnableUpgrade rateLimiter = RateLimiter(_rateLimiter); } + function setSponsoredTransferFee(uint256 _sponsoredTransferFee) external onlyOwner { + sponsoredTransferFee = _sponsoredTransferFee; + } + function initiateBridgeTransfer(uint256 moveAmount, bytes32 recipient, bytes32 hashLock) external returns (bytes32 bridgeTransferId) @@ -72,12 +83,8 @@ contract AtomicBridgeInitiatorMOVE is IAtomicBridgeInitiatorMOVE, OwnableUpgrade rateLimiter.rateLimitOutbound(moveAmount); address originator = msg.sender; - require(moveAmount > 0, "ZeroAmount"); + require(moveAmount > sponsoredTransferFee, "INSUFFICIENT_AMOUNT"); - // Ensure there is a valid amount - if (moveAmount == 0) { - revert ZeroAmount(); - } // Transfer the MOVE tokens from the user to the contract if (!moveToken.transferFrom(originator, address(this), moveAmount)) { @@ -87,11 +94,13 @@ contract AtomicBridgeInitiatorMOVE is IAtomicBridgeInitiatorMOVE, OwnableUpgrade // Update the pool balance poolBalance += moveAmount; + uint256 amount = moveAmount - sponsoredTransferFee; // deduct the sponsored transfer fee + // Generate a unique nonce to prevent replay attacks, and generate a transfer ID bridgeTransferId = keccak256(abi.encodePacked(originator, recipient, hashLock, initiatorTimeLockDuration, block.timestamp, nonce++)); bridgeTransfers[bridgeTransferId] = BridgeTransfer({ - amount: moveAmount, + amount: amount, originator: originator, recipient: recipient, hashLock: hashLock, @@ -99,7 +108,7 @@ contract AtomicBridgeInitiatorMOVE is IAtomicBridgeInitiatorMOVE, OwnableUpgrade state: MessageState.INITIALIZED }); - emit BridgeTransferInitiated(bridgeTransferId, originator, recipient, moveAmount, hashLock, initiatorTimeLockDuration); + emit BridgeTransferInitiated(bridgeTransferId, originator, recipient, amount, hashLock, initiatorTimeLockDuration); return bridgeTransferId; } diff --git a/protocol-units/bridge/contracts/test/AtomicBridgeCounterpartyMOVE.t.sol b/protocol-units/bridge/contracts/test/AtomicBridgeCounterpartyMOVE.t.sol index 91cbe7e17..be3f3c2a3 100644 --- a/protocol-units/bridge/contracts/test/AtomicBridgeCounterpartyMOVE.t.sol +++ b/protocol-units/bridge/contracts/test/AtomicBridgeCounterpartyMOVE.t.sol @@ -28,6 +28,7 @@ contract AtomicBridgeCounterpartyMOVETest is Test { uint256 public amount = 100 * 10 ** 8; // 100 MOVEToken (assuming 8 decimals) uint256 public timeLock = 100; bytes32 public initiator = keccak256(abi.encodePacked(deployer)); + uint256 public constant sponsoredTransferFee = 1; bytes32 public bridgeTransferId = keccak256(abi.encodePacked(block.timestamp, initiator, recipient, amount, hashLock, timeLock)); @@ -53,11 +54,12 @@ contract AtomicBridgeCounterpartyMOVETest is Test { address(atomicBridgeInitiatorMOVEImplementation), address(deployer), abi.encodeWithSignature( - "initialize(address,address,uint256,uint256)", + "initialize(address,address,uint256,uint256,uint256)", address(moveToken), deployer, initiatorTimeLockDuration, - 0 ether // Initial pool balance + 0 ether, // Initial pool balance + sponsoredTransferFee ) ); atomicBridgeInitiatorMOVE = AtomicBridgeInitiatorMOVE(address(proxy)); diff --git a/protocol-units/bridge/contracts/test/AtomicBridgeInitiatorMOVE.t.sol b/protocol-units/bridge/contracts/test/AtomicBridgeInitiatorMOVE.t.sol index 4da72b316..45c370cde 100644 --- a/protocol-units/bridge/contracts/test/AtomicBridgeInitiatorMOVE.t.sol +++ b/protocol-units/bridge/contracts/test/AtomicBridgeInitiatorMOVE.t.sol @@ -24,6 +24,7 @@ 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 sponsoredTransferFee = 1; function setUp() public { // Deploy the MOVEToken contract and mint some tokens to the deployer @@ -40,11 +41,12 @@ contract AtomicBridgeInitiatorMOVETest is Test { address(atomicBridgeInitiatorImplementation), address(proxyAdmin), abi.encodeWithSignature( - "initialize(address,address,uint256,uint256)", + "initialize(address,address,uint256,uint256,uint256)", address(moveToken), address(this), timeLockDuration, - 0 ether + 0 ether, + sponsoredTransferFee ) ); @@ -96,7 +98,7 @@ contract AtomicBridgeInitiatorMOVETest is Test { AtomicBridgeInitiatorMOVE.MessageState transferState ) = atomicBridgeInitiatorMOVE.bridgeTransfers(bridgeTransferId); - assertEq(transferAmount, moveAmount); + assertEq(transferAmount, moveAmount - sponsoredTransferFee); assertEq(transferOriginator, originator); assertEq(transferRecipient, recipient); assertEq(transferHashLock, hashLock); @@ -143,7 +145,7 @@ contract AtomicBridgeInitiatorMOVETest is Test { AtomicBridgeInitiatorMOVE.MessageState completedState ) = atomicBridgeInitiatorMOVE.bridgeTransfers(bridgeTransferId); - assertEq(completedAmount, moveAmount); + assertEq(completedAmount, moveAmount - sponsoredTransferFee); assertEq(completedOriginator, originator); assertEq(completedRecipient, recipient); assertEq(completedHashLock, testHashLock); @@ -189,7 +191,7 @@ contract AtomicBridgeInitiatorMOVETest is Test { // Verify that the originator receives the refund and the balance is restored uint256 finalBalance = moveToken.balanceOf(originator); - assertEq(finalBalance, initialBalance, "MOVE balance mismatch"); + assertEq(finalBalance, initialBalance - sponsoredTransferFee, "MOVE balance mismatch"); } }