diff --git a/contracts/ethereum/src/PaymentRegistry.sol b/contracts/ethereum/src/PaymentRegistry.sol index 146a32f1..09587573 100644 --- a/contracts/ethereum/src/PaymentRegistry.sol +++ b/contracts/ethereum/src/PaymentRegistry.sol @@ -134,8 +134,7 @@ contract PaymentRegistry is Initializable, OwnableUpgradeable, UUPSUpgradeable { uint256 gasLimit, uint256 gasPerPubdataByteLimit ) external payable onlyOwnerOrMM { - bytes32 index = keccak256(abi.encodePacked(orderId, destAddress, amount, Chain.ZKSync)); - require(transfers[index] == true, "Transfer not found."); //if this is claimed twice, Escrow will know + _verifyTransferExistsZKSync(orderId, destAddress, amount); //todo change place of this var bytes4 selector = 0xa5168739; //claim_payment selector in ZKSync //todo add in init, same as in SN @@ -159,6 +158,47 @@ contract PaymentRegistry is Initializable, OwnableUpgradeable, UUPSUpgradeable { emit ClaimPayment(orderId, destAddress, amount, Chain.ZKSync); //2100 gas } + function claimPaymentBatchZKSync( + uint256[] calldata orderIds, + address[] calldata destAddresses, + uint256[] calldata amounts, + uint256 gasLimit, + uint256 gasPerPubdataByteLimit + ) external payable onlyOwnerOrMM { + require(orderIds.length == destAddresses.length, "Invalid lengths."); + require(orderIds.length == amounts.length, "Invalid lengths."); + + for (uint32 idx = 0; idx < orderIds.length; idx++) { + _verifyTransferExistsZKSync(orderIds[idx], destAddresses[idx], amounts[idx]); + } + + //todo change place of this var + bytes4 selector = 0x156be1ae; //claim_payment_batch selector in ZKSync //todo add in init, same as in SN + bytes memory messageToL2 = abi.encodeWithSelector( + selector, + orderIds, + destAddresses, + amounts + ); + + _ZKSyncDiamondProxy.requestL2Transaction{value: msg.value}( + ZKSyncEscrowAddress, //L2 contract called + 0, //msg.value + messageToL2, //msg.calldata + gasLimit, + gasPerPubdataByteLimit, + new bytes[](0), //factory dependencies + msg.sender //refund recipient + ); + + emit ClaimPaymentBatch(orderIds, destAddresses, amounts, Chain.ZKSync); + } + + function _verifyTransferExistsZKSync(uint256 orderId, address destAddress, uint256 amount) internal view { + bytes32 index = keccak256(abi.encodePacked(orderId, destAddress, amount, Chain.ZKSync)); + require(transfers[index] == true, "Transfer not found."); //if this is claimed twice, Escrow will know + } + function setStarknetEscrowAddress(uint256 newStarknetEscrowAddress) external onlyOwner { StarknetEscrowAddress = newStarknetEscrowAddress; emit ModifiedStarknetEscrowAddress(newStarknetEscrowAddress); diff --git a/contracts/ethereum/test/Transfer_Claim_ZKSync.t.sol b/contracts/ethereum/test/Transfer_Claim_ZKSync.t.sol index f6d554cb..065c24ba 100644 --- a/contracts/ethereum/test/Transfer_Claim_ZKSync.t.sol +++ b/contracts/ethereum/test/Transfer_Claim_ZKSync.t.sol @@ -73,6 +73,71 @@ contract TransferTest is Test { assertEq(address(marketMaker).balance, 100); } + function test_claimPaymentBatch_zk() public { + hoax(marketMaker, 100 wei); + yab_caller.transfer{value: 100}(1, address(0x1), PaymentRegistry.Chain.ZKSync); + hoax(marketMaker, 101 wei); + yab_caller.transfer{value: 100}(2, address(0x1), PaymentRegistry.Chain.ZKSync); + + uint256[] memory orderIds = new uint256[](2); + address[] memory destAddresses = new address[](2); + uint256[] memory amounts = new uint256[](2); + + orderIds[0] = 1; + orderIds[1] = 2; + destAddresses[0] = address(0x1); + destAddresses[1] = address(0x1); + amounts[0] = 100; + amounts[1] = 100; + + vm.mockCall( + ZKSYNC_DIAMOND_PROXY_ADDRESS, + abi.encodeWithSelector(0x156be1ae, 0), //TODO add selector + abi.encode(0x12345678901234567890123456789012) //TODO add return data + ); + hoax(marketMaker); + yab_caller.claimPaymentBatchZKSync(orderIds, destAddresses, amounts, 1, 1); + } + + function test_claimPaymentBatch_zk_fail_MissingTransfer() public { + hoax(marketMaker, 100 wei); + yab_caller.transfer{value: 100}(1, address(0x1), PaymentRegistry.Chain.ZKSync); + + uint256[] memory orderIds = new uint256[](2); + address[] memory destAddresses = new address[](2); + uint256[] memory amounts = new uint256[](2); + + orderIds[0] = 1; + orderIds[1] = 2; + destAddresses[0] = address(0x1); + destAddresses[1] = address(0x1); + amounts[0] = 100; + amounts[1] = 100; + + vm.expectRevert("Transfer not found."); + hoax(marketMaker); + yab_caller.claimPaymentBatchZKSync(orderIds, destAddresses, amounts, 1, 1); + } + + function test_claimPaymentBatch_zk_fail_notOwnerOrMM() public { + hoax(marketMaker, 100 wei); + yab_caller.transfer{value: 100}(1, address(0x1), PaymentRegistry.Chain.ZKSync); + + uint256[] memory orderIds = new uint256[](2); + address[] memory destAddresses = new address[](2); + uint256[] memory amounts = new uint256[](2); + + orderIds[0] = 1; + orderIds[1] = 2; + destAddresses[0] = address(0x1); + destAddresses[1] = address(0x1); + amounts[0] = 100; + amounts[1] = 100; + + vm.expectRevert("Only Owner or MM can call this function"); + yab_caller.claimPaymentBatchZKSync(orderIds, destAddresses, amounts, 0, 1); + } + function test_claimPayment_zk_maxInt() public { uint256 maxInt = type(uint256).max; diff --git a/contracts/zksync/contracts/escrow.sol b/contracts/zksync/contracts/escrow.sol index 2df3e8c5..2ca52fcc 100644 --- a/contracts/zksync/contracts/escrow.sol +++ b/contracts/zksync/contracts/escrow.sol @@ -93,6 +93,38 @@ contract Escrow is Initializable, OwnableUpgradeable, PausableUpgradeable { //}, emit ClaimPayment(order_id, mm_zksync_wallet, amount); } + // l1 handler + function claim_payment_batch( + uint256[] calldata order_ids, + address[] calldata recipient_addresses, + uint256[] calldata amounts + ) public whenNotPaused { + require(msg.sender == ethereum_payment_registry, 'Only PAYMENT_REGISTRY can call'); + require(order_ids.length == recipient_addresses.length, 'Invalid lengths'); + require(order_ids.length == amounts.length, 'Invalid lengths'); + + for (uint32 idx = 0; idx < order_ids.length; idx++) { + uint256 order_id = order_ids[idx]; + address recipient_address = recipient_addresses[idx]; + uint256 amount = amounts[idx]; + + require(_orders_pending[order_id], 'Order claimed or nonexistent'); + + Order memory current_order = _orders[order_id]; //TODO check if order is memory or calldata + require(current_order.recipient_address == recipient_address, 'recipient_address not match L1'); + require(current_order.amount == amount, 'amount not match L1'); + + _orders_pending[order_id] = false; + uint256 payment_amount = current_order.amount + current_order.fee; // TODO check overflow + + // TODO: Might be best to do only one transfer + (bool success,) = payable(address(uint160(mm_zksync_wallet))).call{value: payment_amount}(""); + require(success, "Transfer failed."); + + emit ClaimPayment(order_id, mm_zksync_wallet, amount); + } + } + function is_order_pending(uint256 order_id) public view returns (bool) { return _orders_pending[order_id]; } diff --git a/contracts/zksync/deploy/deploy.ts b/contracts/zksync/deploy/deploy.ts index 2ecd0726..3f7c505d 100644 --- a/contracts/zksync/deploy/deploy.ts +++ b/contracts/zksync/deploy/deploy.ts @@ -29,5 +29,5 @@ export default async function () { const initResult = await escrow.initialize(PaymentRegistryL2Alias, mm_zksync_wallet); - // console.log("Initialization result:", initResult); + console.log("Initialization result:", initResult); } diff --git a/contracts/zksync/hardhat.config.ts b/contracts/zksync/hardhat.config.ts index 60d1be41..a40bf3d8 100644 --- a/contracts/zksync/hardhat.config.ts +++ b/contracts/zksync/hardhat.config.ts @@ -8,7 +8,7 @@ import "@matterlabs/hardhat-zksync-upgradable"; import "@nomicfoundation/hardhat-chai-matchers"; const config: HardhatUserConfig = { - defaultNetwork: "dockerizedNode", + defaultNetwork: "inMemoryNode", // defaultNetwork: "zkSyncSepoliaTestnet", networks: { zkSyncSepoliaTestnet: { @@ -35,7 +35,7 @@ const config: HardhatUserConfig = { zksync: true, }, inMemoryNode: { - url: "http://127.0.0.1:8011", + url: "http://localhost:8011", ethNetwork: "", // in-memory node doesn't support eth node; removing this line will cause an error zksync: true, }, diff --git a/contracts/zksync/package.json b/contracts/zksync/package.json index 7acf0377..9fd96b9e 100644 --- a/contracts/zksync/package.json +++ b/contracts/zksync/package.json @@ -7,7 +7,7 @@ "deploy-devnet": "hardhat deploy-zksync --network dockerizedNode --script deploy.ts", "compile": "hardhat compile", "clean": "hardhat clean", - "test": "hardhat test --network dockerizedNode --show-stack-traces" + "test": "hardhat test --network inMemoryNode --show-stack-traces" }, "devDependencies": { "@matterlabs/hardhat-zksync-deploy": "^1.1.2", diff --git a/contracts/zksync/test/main.test.ts b/contracts/zksync/test/main.test.ts index 960cc416..4499ec5f 100644 --- a/contracts/zksync/test/main.test.ts +++ b/contracts/zksync/test/main.test.ts @@ -128,3 +128,39 @@ describe('Ownable tests', function () { }); }) + +describe('Claim payment batch tests', function () { + + it("Should claim payment batch", async () => { + const setOrderTx = await escrow.connect(user_zk).set_order(user_eth, fee, {value}); + await setOrderTx.wait(); + + const setOrderTx2 = await escrow.connect(user_zk2).set_order(user_eth2, fee, {value}); + await setOrderTx2.wait(); + + await escrow.connect(deployer).set_ethereum_payment_registry(user_eth); + await escrow.connect(deployer).set_mm_zksync_wallet(user_zk); + + const tx = await escrow.connect(user_eth).claim_payment_batch([0, 1], [user_eth.address, user_eth2.address], [value-fee, value-fee]); + + await expect(tx) + .to.emit(escrow, "ClaimPayment").withArgs(0, user_zk.address, value-fee) + .to.emit(escrow, "ClaimPayment").withArgs(1, user_zk.address, value-fee); + + expect(await escrow.is_order_pending(0)).to.equal(false); + }); + + it("Should not claim payment batch when order missing", async () => { + await escrow.connect(deployer).set_ethereum_payment_registry(user_eth); + + await expect(escrow.connect(user_eth).claim_payment_batch([0], [user_eth.address], [value-fee])).to.be.revertedWith("Order claimed or nonexistent"); + }); + + it("Should not claim payment batch when not PAYMENT_REGISTRY", async () => { + const setOrderTx = await escrow.connect(user_zk).set_order(user_eth, fee, {value}); + await setOrderTx.wait(); + + await expect(escrow.connect(user_eth).claim_payment_batch([0], [user_eth.address], [value-fee])).to.be.revertedWith("Only PAYMENT_REGISTRY can call"); + }); +}); +