Skip to content

Commit

Permalink
feat (starknet): add batching functionality for claim_payment (#184)
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

* test: permissons & missing order tests for batch

* refactor: use constants for ETH_USER address

* feat: emit event on claim payment batch

* refactor: rename _claimPaymentStarknet -> _verifyTransferExistsStarknet to make it more descriptive

* refactor (starknet): rename tests & remove commented code
  • Loading branch information
taturosati authored Mar 27, 2024
1 parent 8484591 commit 529c248
Show file tree
Hide file tree
Showing 13 changed files with 582 additions and 47 deletions.
11 changes: 9 additions & 2 deletions contracts/ethereum/script/Deploy.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,20 @@ contract Deploy is Script {
address snMessagingAddress = vm.envAddress("STARKNET_MESSAGING_ADDRESS");
uint256 snEscrowAddress = 0x0; // this value is set in a call to the smart contract, once deployed
uint256 snClaimPaymentSelector = 0x0; // this value is set in a call to the smart contract, once deployed
uint256 snClaimPaymentBatchSelector = 0x0; // this value is set in a call to the smart contract, once deployed
address marketMaker = vm.envAddress("MM_ETHEREUM_WALLET_ADDRESS");
address ZKSYNC_DIAMOND_PROXY_ADDRESS = vm.envAddress("ZKSYNC_DIAMOND_PROXY_ADDRESS");

PaymentRegistry yab = new PaymentRegistry();
ERC1967Proxy proxy = new ERC1967Proxy(address(yab), "");
PaymentRegistry(address(proxy)).initialize(snMessagingAddress, snEscrowAddress, snClaimPaymentSelector, marketMaker, ZKSYNC_DIAMOND_PROXY_ADDRESS);

PaymentRegistry(address(proxy)).initialize(
snMessagingAddress,
snEscrowAddress,
snClaimPaymentSelector,
snClaimPaymentBatchSelector,
marketMaker,
ZKSYNC_DIAMOND_PROXY_ADDRESS
);
vm.stopBroadcast();

return (address(proxy), address(yab));
Expand Down
12 changes: 12 additions & 0 deletions contracts/ethereum/set_starknet_claim_payment_selector.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ if [ -z "$CLAIM_PAYMENT_NAME" ]; then
echo "CLAIM_PAYMENT_NAME Variable is empty. Aborting execution.\n"
exit 1
fi
if [ -z "$CLAIM_PAYMENT_BATCH_NAME" ]; then
printf "\n${RED}ERROR:${COLOR_RESET}\n"
echo "CLAIM_PAYMENT_BATCH_NAME Variable is empty. Aborting execution.\n"
exit 1
fi

printf "${GREEN}\n=> [ETH] Setting Starknet ClaimPayment Selector on ETH Smart Contract${COLOR_RESET}\n"
echo "Smart contract being modified:" $PAYMENT_REGISTRY_PROXY_ADDRESS
Expand All @@ -20,3 +25,10 @@ echo "New ClaimPayment Selector: ${CLAIM_PAYMENT_SELECTOR}"

cast send --rpc-url $ETHEREUM_RPC --private-key $ETHEREUM_PRIVATE_KEY $PAYMENT_REGISTRY_PROXY_ADDRESS "setStarknetClaimPaymentSelector(uint256)" "${CLAIM_PAYMENT_SELECTOR}" | grep "transactionHash"
echo "Done setting ClaimPayment selector"

printf "${GREEN}\n=> [ETH] Setting Starknet ClaimPaymentBatch Selector on ETH Smart Contract${COLOR_RESET}\n"
CLAIM_PAYMENT_BATCH_SELECTOR=$(starkli selector $CLAIM_PAYMENT_BATCH_NAME)
echo "New ClaimPaymentBatch Selector: ${CLAIM_PAYMENT_BATCH_SELECTOR}"

cast send --rpc-url $ETHEREUM_RPC --private-key $ETHEREUM_PRIVATE_KEY $PAYMENT_REGISTRY_PROXY_ADDRESS "setStarknetClaimPaymentBatchSelector(uint256)" "${CLAIM_PAYMENT_BATCH_SELECTOR}" | grep "transactionHash"
echo "Done setting ClaimPaymentBatch selector"
67 changes: 59 additions & 8 deletions contracts/ethereum/src/PaymentRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,17 @@ contract PaymentRegistry is Initializable, OwnableUpgradeable, UUPSUpgradeable {
event ModifiedZKSyncEscrowAddress(address newEscrowAddress);
event ModifiedStarknetEscrowAddress(uint256 newEscrowAddress);
event ModifiedStarknetClaimPaymentSelector(uint256 newEscrowClaimPaymentSelector);
event ClaimPayment(TransferInfo transferInfo);
event ModifiedStarknetClaimPaymentBatchSelector(uint256 newEscrowClaimPaymentSelector);
event ClaimPayment(uint256 orderId, uint256 destAddress, uint256 amount, Chain chainId);
event ClaimPaymentBatch(uint256[] orderIds, uint256[] destAddresses, uint256[] amounts, Chain chainId);

mapping(bytes32 => TransferInfo) public transfers;
address public marketMaker;
uint256 public StarknetEscrowAddress;
address public ZKSyncEscrowAddress;
uint256 public StarknetEscrowClaimPaymentSelector;
uint256 public StarknetEscrowClaimPaymentBatchSelector;

IZkSync private _ZKSyncDiamondProxy;
IStarknetMessaging private _snMessaging;

Expand All @@ -41,6 +45,7 @@ contract PaymentRegistry is Initializable, OwnableUpgradeable, UUPSUpgradeable {
address snMessaging,
uint256 StarknetEscrowAddress_,
uint256 StarknetEscrowClaimPaymentSelector_,
uint256 StarknetEscrowClaimPaymentBatchSelector_,
address marketMaker_,
address ZKSyncDiamondProxyAddress) public initializer {
__Ownable_init(msg.sender);
Expand All @@ -51,6 +56,8 @@ contract PaymentRegistry is Initializable, OwnableUpgradeable, UUPSUpgradeable {

StarknetEscrowAddress = StarknetEscrowAddress_;
StarknetEscrowClaimPaymentSelector = StarknetEscrowClaimPaymentSelector_; // TODO remove this or set the correct value in init
StarknetEscrowClaimPaymentBatchSelector = StarknetEscrowClaimPaymentBatchSelector_; // TODO remove this or set the correct value in init

marketMaker = marketMaker_;
}

Expand All @@ -75,14 +82,12 @@ contract PaymentRegistry is Initializable, OwnableUpgradeable, UUPSUpgradeable {

//TODO change name to claimPaymentStarknet
function claimPayment(uint256 orderId, uint256 destAddress, uint256 amount) external payable onlyOwnerOrMM {
bytes32 index = keccak256(abi.encodePacked(orderId, destAddress, amount, Chain.Starknet));
TransferInfo storage transferInfo = transfers[index];
require(transferInfo.isUsed == true, "Transfer not found.");
_verifyTransferExistsStarknet(orderId, destAddress, amount);

uint256[] memory payload = new uint256[](5); //TODO why array of 256 if then filled with 128?
payload[0] = uint128(orderId); // low
payload[1] = uint128(orderId >> 128); // high
payload[2] = transferInfo.destAddress;
payload[2] = destAddress;
payload[3] = uint128(amount); // low
payload[4] = uint128(amount >> 128); // high

Expand All @@ -91,7 +96,48 @@ contract PaymentRegistry is Initializable, OwnableUpgradeable, UUPSUpgradeable {
StarknetEscrowClaimPaymentSelector,
payload);

emit ClaimPayment(transferInfo);
emit ClaimPayment(orderId, destAddress, amount, Chain.Starknet);
}

function claimPaymentBatch(
uint256[] calldata orderIds,
uint256[] calldata destAddresses,
uint256[] calldata amounts
) external payable onlyOwnerOrMM() {
require(orderIds.length == destAddresses.length, "Invalid lengths.");
require(orderIds.length == amounts.length, "Invalid lengths.");

uint256[] memory payload = new uint256[](5 * orderIds.length + 1);

payload[0] = orderIds.length;

for (uint32 idx = 0; idx < orderIds.length; idx++) {
uint256 orderId = orderIds[idx];
uint256 destAddress = destAddresses[idx];
uint256 amount = amounts[idx];

_verifyTransferExistsStarknet(orderId, destAddress, amount);

uint32 base_idx = 1 + 5 * idx;
payload[base_idx] = uint128(orderId); // low
payload[base_idx + 1] = uint128(orderId >> 128); // high
payload[base_idx + 2] = destAddress;
payload[base_idx + 3] = uint128(amount); // low
payload[base_idx + 4] = uint128(amount >> 128); // high
}

_snMessaging.sendMessageToL2{value: msg.value}(
StarknetEscrowAddress,
StarknetEscrowClaimPaymentBatchSelector,
payload);

emit ClaimPaymentBatch(orderIds, destAddresses, amounts, Chain.Starknet);
}

function _verifyTransferExistsStarknet(uint256 orderId, uint256 destAddress, uint256 amount) internal view {
bytes32 index = keccak256(abi.encodePacked(orderId, destAddress, amount, Chain.Starknet));
TransferInfo storage transferInfo = transfers[index];
require(transferInfo.isUsed == true, "Transfer not found.");
}

function claimPaymentZKSync(
Expand Down Expand Up @@ -122,14 +168,15 @@ contract PaymentRegistry is Initializable, OwnableUpgradeable, UUPSUpgradeable {
msg.sender //refund recipient
);

emit ClaimPayment(transferInfo);
emit ClaimPayment(orderId, destAddress, amount, Chain.ZKSync);
}

function setStarknetEscrowAddress(uint256 newStarknetEscrowAddress) external onlyOwner {
StarknetEscrowAddress = newStarknetEscrowAddress;
emit ModifiedStarknetEscrowAddress(newStarknetEscrowAddress);
}


function setZKSyncEscrowAddress(address newZKSyncEscrowAddress) external onlyOwner {
ZKSyncEscrowAddress = newZKSyncEscrowAddress;
emit ModifiedZKSyncEscrowAddress(newZKSyncEscrowAddress);
Expand All @@ -141,7 +188,11 @@ contract PaymentRegistry is Initializable, OwnableUpgradeable, UUPSUpgradeable {
StarknetEscrowClaimPaymentSelector = NewStarknetEscrowClaimPaymentSelector;
emit ModifiedStarknetClaimPaymentSelector(StarknetEscrowClaimPaymentSelector);
}


function setStarknetClaimPaymentBatchSelector(uint256 NewStarknetEscrowClaimPaymentBatchSelector) external onlyOwner {
StarknetEscrowClaimPaymentBatchSelector = NewStarknetEscrowClaimPaymentBatchSelector;
emit ModifiedStarknetClaimPaymentBatchSelector(StarknetEscrowClaimPaymentBatchSelector);
}

//// MM ACL:

Expand Down
2 changes: 1 addition & 1 deletion contracts/ethereum/test/ACL.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ contract TransferTest is Test {
yab = new PaymentRegistry();
proxy = new ERC1967Proxy(address(yab), "");
yab_caller = PaymentRegistry(address(proxy));
yab_caller.initialize(SN_MESSAGING_ADDRESS, snEscrowAddress, SN_ESCROW_CLAIM_PAYMENT_SELECTOR, marketMaker, ZKSYNC_DIAMOND_PROXY_ADDRESS);
yab_caller.initialize(SN_MESSAGING_ADDRESS, snEscrowAddress, SN_ESCROW_CLAIM_PAYMENT_SELECTOR, 0x0, marketMaker, ZKSYNC_DIAMOND_PROXY_ADDRESS);

vm.stopPrank();
}
Expand Down
111 changes: 110 additions & 1 deletion contracts/ethereum/test/Transfer_Claim_SN.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import "../src/PaymentRegistry.sol";
import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";

contract TransferTest is Test {
event ClaimPaymentBatch(uint256[] orderIds, uint256[] destAddresses, uint256[] amounts, PaymentRegistry.Chain chainId);

address public deployer = makeAddr('deployer');
address public marketMaker = makeAddr("marketMaker");
Expand All @@ -25,7 +26,7 @@ contract TransferTest is Test {
yab = new PaymentRegistry();
proxy = new ERC1967Proxy(address(yab), "");
yab_caller = PaymentRegistry(address(proxy));
yab_caller.initialize(STARKNET_MESSAGING_ADDRESS, snEscrowAddress, SN_ESCROW_CLAIM_PAYMENT_SELECTOR, marketMaker, ZKSYNC_DIAMOND_PROXY_ADDRESS);
yab_caller.initialize(STARKNET_MESSAGING_ADDRESS, snEscrowAddress, SN_ESCROW_CLAIM_PAYMENT_SELECTOR, 0x0, marketMaker, ZKSYNC_DIAMOND_PROXY_ADDRESS);

// Mock calls to Starknet Messaging contract
vm.mockCall(
Expand Down Expand Up @@ -82,6 +83,114 @@ contract TransferTest is Test {
yab_caller.claimPayment(1, 0x1, 1);
}

function testClaimPaymentBatch() public {
hoax(marketMaker, 3 wei);
yab_caller.transfer{value: 3}(1, 0x1, PaymentRegistry.Chain.Starknet);
hoax(marketMaker, 2 wei);
yab_caller.transfer{value: 2}(2, 0x3, PaymentRegistry.Chain.Starknet);
hoax(marketMaker, 1 wei);
yab_caller.transfer{value: 1}(3, 0x5, PaymentRegistry.Chain.Starknet);

uint256[] memory orderIds = new uint256[](3);
uint256[] memory destAddresses = new uint256[](3);
uint256[] memory amounts = new uint256[](3);

orderIds[0] = 1;
orderIds[1] = 2;
orderIds[2] = 3;

destAddresses[0] = 0x1;
destAddresses[1] = 0x3;
destAddresses[2] = 0x5;

amounts[0] = 3;
amounts[1] = 2;
amounts[2] = 1;

hoax(marketMaker);
vm.expectEmit(true, true, true, true);
emit ClaimPaymentBatch(orderIds, destAddresses, amounts, PaymentRegistry.Chain.Starknet);
yab_caller.claimPaymentBatch(orderIds, destAddresses, amounts);

assertEq(address(0x1).balance, 3);
assertEq(address(0x3).balance, 2);
assertEq(address(0x5).balance, 1);
}

function testClaimPaymentBatchPartial() public {
hoax(marketMaker, 3 wei);
yab_caller.transfer{value: 3}(1, 0x1, PaymentRegistry.Chain.Starknet);
hoax(marketMaker, 2 wei);
yab_caller.transfer{value: 2}(2, 0x3, PaymentRegistry.Chain.Starknet);
hoax(marketMaker, 1 wei);
yab_caller.transfer{value: 1}(3, 0x5, PaymentRegistry.Chain.Starknet);

uint256[] memory orderIds = new uint256[](2);
uint256[] memory destAddresses = new uint256[](2);
uint256[] memory amounts = new uint256[](2);

orderIds[0] = 1;
orderIds[1] = 2;

destAddresses[0] = 0x1;
destAddresses[1] = 0x3;

amounts[0] = 3;
amounts[1] = 2;

hoax(marketMaker);
yab_caller.claimPaymentBatch(orderIds, destAddresses, amounts);

assertEq(address(0x1).balance, 3);
assertEq(address(0x3).balance, 2);
}

function testClaimPaymentBatch_fail_MissingTransfer() public {
hoax(marketMaker, 3 wei);
yab_caller.transfer{value: 3}(1, 0x1, PaymentRegistry.Chain.Starknet);
hoax(marketMaker, 2 wei);
yab_caller.transfer{value: 2}(2, 0x3, PaymentRegistry.Chain.Starknet);

uint256[] memory orderIds = new uint256[](3);
uint256[] memory destAddresses = new uint256[](3);
uint256[] memory amounts = new uint256[](3);

orderIds[0] = 1;
orderIds[1] = 2;
orderIds[2] = 3;

destAddresses[0] = 0x1;
destAddresses[1] = 0x3;
destAddresses[2] = 0x5;

amounts[0] = 3;
amounts[1] = 2;
amounts[2] = 1;

vm.expectRevert("Transfer not found.");
hoax(marketMaker);
yab_caller.claimPaymentBatch(orderIds, destAddresses, amounts);
}

function testClaimPaymentBatch_fail_notOwnerOrMM() public {
hoax(marketMaker, 3 wei);
yab_caller.transfer{value: 3}(1, 0x1, PaymentRegistry.Chain.Starknet);

uint256[] memory orderIds = new uint256[](1);
uint256[] memory destAddresses = new uint256[](1);
uint256[] memory amounts = new uint256[](1);

orderIds[0] = 1;

destAddresses[0] = 0x1;

amounts[0] = 3;

hoax(makeAddr("bob"), 100 wei);
vm.expectRevert("Only Owner or MM can call this function");
yab_caller.claimPaymentBatch(orderIds, destAddresses, amounts);
}

function test_claimPayment_fail_wrongChain() public {
hoax(marketMaker, 1 wei);
yab_caller.transfer{value: 1}(1, 0x1, PaymentRegistry.Chain.Starknet);
Expand Down
2 changes: 1 addition & 1 deletion contracts/ethereum/test/Transfer_Claim_ZKSync.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ contract TransferTest is Test {
yab = new PaymentRegistry();
proxy = new ERC1967Proxy(address(yab), "");
yab_caller = PaymentRegistry(address(proxy));
yab_caller.initialize(SN_MESSAGING_ADDRESS, snEscrowAddress, SN_ESCROW_CLAIM_PAYMENT_SELECTOR, marketMaker, ZKSYNC_DIAMOND_PROXY_ADDRESS);
yab_caller.initialize(SN_MESSAGING_ADDRESS, snEscrowAddress, SN_ESCROW_CLAIM_PAYMENT_SELECTOR, 0x0, marketMaker, ZKSYNC_DIAMOND_PROXY_ADDRESS);

//Mock calls to ZKSync Mailbox contract
vm.mockCall(
Expand Down
1 change: 1 addition & 0 deletions contracts/starknet/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ STARKNET_RPC=<starknet_rpc_url>
STARKNET_ESCROW_OWNER=<starknet_escrow_owner> #in lowercase hexa with the 0x prefix
MM_STARKNET_WALLET_ADDRESS=<MarketMaker_starknet_address> #in lowercase hexa with the 0x prefix
CLAIM_PAYMENT_NAME=<claim_payment_function_name> #must match the exact name of the function to claim the payment from the starknet smart contract
CLAIM_PAYMENT_BATCH_NAME=<claim_payment_batch_function_name> #must match the exact name of the function to claim the payment batch from the starknet smart contract
MM_ETHEREUM_WALLET_ADDRESS=<MarketMaker_ethereum_contract_address> #in lowercase hexa with the 0x prefix
NATIVE_TOKEN_ETH_STARKNET=<eth_erc20_in_starknet> #in lowercase hexa with the 0x prefix
1 change: 1 addition & 0 deletions contracts/starknet/.env.test
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ STARKNET_RPC=http://0.0.0.0:5050
STARKNET_ESCROW_OWNER=0x517ececd29116499f4a1b64b094da79ba08dfd54a3edaa316134c41f8160973
MM_STARKNET_WALLET_ADDRESS=0x5686a647a9cdd63ade617e0baf3b364856b813b508f03903eb58a7e622d5855
CLAIM_PAYMENT_NAME=claim_payment
CLAIM_PAYMENT_BATCH_NAME=claim_payment_batch
MM_ETHEREUM_WALLET_ADDRESS=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
NATIVE_TOKEN_ETH_STARKNET=0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7
Loading

0 comments on commit 529c248

Please sign in to comment.