Skip to content

Commit

Permalink
feat (zksync): add batching functionality for claim payment (#198)
Browse files Browse the repository at this point in the history
* feat: claim payment batch

* feat (ethereum): claim payment batch)

* fix (ethereum): add caller restrictions on claimPaymentBatch

* fix: call claim payment batch when batching from layer 1 + serialization issue

* fix: add missing CLAIM_PAYMENT_BATCH_NAME env variable to .env.test

* fix: set claim payment batch selector correctly

* feat (zksync): batching for claim payment

* test: failing cases for claimPaymentBatchZKSync

* test (zksync): uncomment test-suite

* test (zksync): claim payment batch
  • Loading branch information
taturosati authored Apr 3, 2024
1 parent f7a97ba commit 5ae6f6e
Show file tree
Hide file tree
Showing 7 changed files with 179 additions and 6 deletions.
44 changes: 42 additions & 2 deletions contracts/ethereum/src/PaymentRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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);
Expand Down
65 changes: 65 additions & 0 deletions contracts/ethereum/test/Transfer_Claim_ZKSync.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
32 changes: 32 additions & 0 deletions contracts/zksync/contracts/escrow.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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];
}
Expand Down
2 changes: 1 addition & 1 deletion contracts/zksync/deploy/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
4 changes: 2 additions & 2 deletions contracts/zksync/hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand All @@ -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,
},
Expand Down
2 changes: 1 addition & 1 deletion contracts/zksync/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
36 changes: 36 additions & 0 deletions contracts/zksync/test/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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");
});
});

0 comments on commit 5ae6f6e

Please sign in to comment.