From e65cfba59947e6a3f50a910891bcc52a3d3b6d20 Mon Sep 17 00:00:00 2001 From: Hussein Ait Lahcen Date: Mon, 25 Nov 2024 19:25:02 +0100 Subject: [PATCH 1/5] feat(zkgm): scaffold --- .../apps/ucs/03-zkgm/IEurekaModule.sol | 5 + evm/contracts/apps/ucs/03-zkgm/Zkgm.sol | 362 ++++++++++++++++++ evm/scripts/Deploy.s.sol | 1 + 3 files changed, 368 insertions(+) create mode 100644 evm/contracts/apps/ucs/03-zkgm/IEurekaModule.sol create mode 100644 evm/contracts/apps/ucs/03-zkgm/Zkgm.sol diff --git a/evm/contracts/apps/ucs/03-zkgm/IEurekaModule.sol b/evm/contracts/apps/ucs/03-zkgm/IEurekaModule.sol new file mode 100644 index 0000000000..a42e04d41d --- /dev/null +++ b/evm/contracts/apps/ucs/03-zkgm/IEurekaModule.sol @@ -0,0 +1,5 @@ +pragma solidity ^0.8.27; + +interface IEurekaModule { + function onZkgm(bytes calldata sender, bytes calldata message) external; +} diff --git a/evm/contracts/apps/ucs/03-zkgm/Zkgm.sol b/evm/contracts/apps/ucs/03-zkgm/Zkgm.sol new file mode 100644 index 0000000000..0a6720d274 --- /dev/null +++ b/evm/contracts/apps/ucs/03-zkgm/Zkgm.sol @@ -0,0 +1,362 @@ +pragma solidity ^0.8.27; + +import "../../Base.sol"; +import "../../../core/25-handler/IBCHandler.sol"; +import "../../../core/05-port/IIBCModule.sol"; + +import "./IEurekaModule.sol"; + +struct ZkgmPacket { + uint8 version; + bytes32 salt; + uint8 syscallIndex; + bytes packet; +} + +struct ForwardPacket { + uint32 channelId; + bytes zkgmPacket; +} + +struct MultiplexPacket { + bytes sender; + bool eureka; + bytes contractAddress; + bytes contractCalldata; +} + +struct BatchPacket { + bytes[] zkgmPackets; +} + +struct FungibleAssetTransferPacket { + bytes sender; + bytes receiver; + bytes sentToken; + uint256 sentAmount; + bytes askToken; + bytes askAmount; +} + +library ZkgmLib { + bytes public constant ACK_EMPTY = hex""; + bytes public constant ACK_FAILURE = abi.encode(0x00); + bytes public constant ACK_SUCCESS = abi.encode(0x01); + + uint8 public constant SYSCALL_FORWARD = 0x00; + uint8 public constant SYSCALL_MULTIPLEX = 0x01; + uint8 public constant SYSCALL_BATCH = 0x02; + uint8 public constant SYSCALL_FUNGIBLE_ASSET_TRANSFER = 0x03; + + uint8 public constant ZKGM_VERSION_0 = 0x00; + + error ErrUnsupportedVersion(); + error ErrUnimplemented(); + error ErrBatchMustBeSync(); + error ErrUnknownSyscall(); + error ErrInfiniteGame(); + error ErrUnauthorized(); + + function encode( + ZkgmPacket memory packet + ) internal pure returns (bytes memory) { + return abi.encode(packet); + } + + function decode( + bytes calldata stream + ) internal pure returns (ZkgmPacket calldata) { + ZkgmPacket calldata packet; + assembly { + packet := stream.offset + } + return packet; + } + + function decodeBatch( + bytes calldata stream + ) internal pure returns (BatchPacket calldata) { + BatchPacket calldata packet; + assembly { + packet := stream.offset + } + return packet; + } + + function decodeForward( + bytes calldata stream + ) internal pure returns (ForwardPacket calldata) { + ForwardPacket calldata packet; + assembly { + packet := stream.offset + } + return packet; + } + + function decodeMultiplex( + bytes calldata stream + ) internal pure returns (MultiplexPacket calldata) { + MultiplexPacket calldata packet; + assembly { + packet := stream.offset + } + return packet; + } + + function decodeFungibleAssetTransfer( + bytes calldata stream + ) internal pure returns (FungibleAssetTransferPacket calldata) { + FungibleAssetTransferPacket calldata packet; + assembly { + packet := stream.offset + } + return packet; + } +} + +contract Zkgm is IBCAppBase { + using ZkgmLib for *; + + IBCHandler private ibcHandler; + + constructor( + IBCHandler _ibcHandler + ) { + ibcHandler = _ibcHandler; + } + + function ibcAddress() public view virtual override returns (address) { + return address(ibcHandler); + } + + function onRecvPacket( + IBCPacket calldata packet, + address relayer, + bytes calldata relayerMsg + ) external virtual override onlyIBC returns (bytes memory) { + (bool success, bytes memory acknowledgement) = address(this).call( + abi.encodeWithSelector( + this.execute.selector, packet, packet.data, relayer, relayerMsg + ) + ); + if (success) { + // The acknowledgement may be asynchronous (forward/multiplex) + if (acknowledgement.length == 0) { + return ZkgmLib.ACK_EMPTY; + } else { + return abi.encode(ZkgmLib.ACK_SUCCESS, acknowledgement); + } + } else { + return ZkgmLib.ACK_FAILURE; + } + } + + function execute( + IBCPacket calldata ibcPacket, + address relayer, + bytes calldata relayerMsg, + bytes calldata rawZkgmPacket + ) public returns (bytes memory) { + // Only callable through the onRecvPacket endpoint. + if (msg.sender != address(this)) { + revert ZkgmLib.ErrUnauthorized(); + } + ZkgmPacket calldata zkgmPacket = ZkgmLib.decode(rawZkgmPacket); + return executeInternal( + ibcPacket, + relayer, + relayerMsg, + zkgmPacket.version, + zkgmPacket.salt, + zkgmPacket.syscallIndex, + zkgmPacket.packet + ); + } + + function executeInternal( + IBCPacket calldata ibcPacket, + address relayer, + bytes calldata relayerMsg, + uint8 version, + bytes32 salt, + uint8 syscallIndex, + bytes calldata packet + ) public returns (bytes memory) { + if (version != ZkgmLib.ZKGM_VERSION_0) { + revert ZkgmLib.ErrUnsupportedVersion(); + } + if (syscallIndex == ZkgmLib.SYSCALL_FUNGIBLE_ASSET_TRANSFER) { + return executeFungibleAssetTransfer( + ibcPacket, + relayer, + relayerMsg, + salt, + ZkgmLib.decodeFungibleAssetTransfer(packet) + ); + } else if (syscallIndex == ZkgmLib.SYSCALL_BATCH) { + return executeBatch( + ibcPacket, + relayer, + relayerMsg, + salt, + ZkgmLib.decodeBatch(packet) + ); + } else if (syscallIndex == ZkgmLib.SYSCALL_FORWARD) { + return executeForward( + ibcPacket, + relayer, + relayerMsg, + salt, + ZkgmLib.decodeForward(packet) + ); + } else if (syscallIndex == ZkgmLib.SYSCALL_MULTIPLEX) { + return executeMultiplex( + ibcPacket, + relayer, + relayerMsg, + salt, + ZkgmLib.decodeMultiplex(packet) + ); + } else { + revert ZkgmLib.ErrUnknownSyscall(); + } + } + + function executeBatch( + IBCPacket calldata ibcPacket, + address relayer, + bytes calldata relayerMsg, + bytes32 salt, + BatchPacket calldata batchPacket + ) internal returns (bytes memory) { + uint256 l = batchPacket.zkgmPackets.length; + bytes[] memory acknowledgements = new bytes[](l); + for (uint256 i = 0; i < l; i++) { + ZkgmPacket calldata zkgmPacket = + ZkgmLib.decode(batchPacket.zkgmPackets[i]); + acknowledgements[i] = executeInternal( + ibcPacket, + relayer, + relayerMsg, + zkgmPacket.version, + keccak256(abi.encode(salt, zkgmPacket.salt)), + zkgmPacket.syscallIndex, + zkgmPacket.packet + ); + if (acknowledgements[i].length == 0) { + revert ZkgmLib.ErrBatchMustBeSync(); + } + } + return abi.encode(acknowledgements); + } + + function executeForward( + IBCPacket calldata ibcPacket, + address relayer, + bytes calldata relayerMsg, + bytes32 salt, + ForwardPacket calldata forwardPacket + ) internal returns (bytes memory) { + revert ZkgmLib.ErrUnimplemented(); + } + + function executeMultiplex( + IBCPacket calldata ibcPacket, + address relayer, + bytes calldata relayerMsg, + bytes32 salt, + MultiplexPacket calldata multiplexPacket + ) internal returns (bytes memory) { + address contractAddress = + address(bytes20(multiplexPacket.contractAddress)); + if (multiplexPacket.eureka) { + IEurekaModule(contractAddress).onZkgm( + multiplexPacket.sender, multiplexPacket.contractCalldata + ); + return ZkgmLib.ACK_SUCCESS; + } else { + IBCPacket memory multiplexIbcPacket = IBCPacket({ + sourceChannel: ibcPacket.sourceChannel, + destinationChannel: ibcPacket.destinationChannel, + data: multiplexPacket.contractCalldata, + timeoutHeight: ibcPacket.timeoutHeight, + timeoutTimestamp: ibcPacket.timeoutTimestamp + }); + bytes memory acknowledgement = IIBCModule(contractAddress) + .onRecvPacket(multiplexIbcPacket, relayer, relayerMsg); + if (acknowledgement.length == 0) { + /* TODO: store the packet for async ack To handle async acks on + multiplexing, we need to have a mapping from (receiver, + virtualPacket) => ibcPacket. Then the receiver will be the + only one able to acknowledge a virtual packet, resulting in + the origin ibc packet to be acknowledged itself. + */ + revert ZkgmLib.ErrUnimplemented(); + } + return acknowledgement; + } + } + + function executeFungibleAssetTransfer( + IBCPacket calldata ibcPacket, + address relayer, + bytes calldata relayerMsg, + bytes32 salt, + FungibleAssetTransferPacket calldata assetTransferPacket + ) internal returns (bytes memory) { + revert ZkgmLib.ErrUnimplemented(); + } + + function onAcknowledgementPacket( + IBCPacket calldata, + bytes calldata acknowledgement, + address + ) external virtual override onlyIBC {} + + function onTimeoutPacket( + IBCPacket calldata, + address + ) external virtual override onlyIBC {} + + function onChanOpenInit( + uint32, + uint32, + string calldata, + address + ) external virtual override onlyIBC {} + + function onChanOpenTry( + uint32, + uint32, + uint32, + string calldata, + string calldata, + address + ) external virtual override onlyIBC {} + + function onChanOpenAck( + uint32 channelId, + uint32, + string calldata, + address + ) external virtual override onlyIBC {} + + function onChanOpenConfirm( + uint32 channelId, + address + ) external virtual override onlyIBC {} + + function onChanCloseInit( + uint32, + address + ) external virtual override onlyIBC { + revert ZkgmLib.ErrInfiniteGame(); + } + + function onChanCloseConfirm( + uint32, + address + ) external virtual override onlyIBC { + revert ZkgmLib.ErrInfiniteGame(); + } +} diff --git a/evm/scripts/Deploy.s.sol b/evm/scripts/Deploy.s.sol index 7ed3c11bbc..2e2dd9c225 100644 --- a/evm/scripts/Deploy.s.sol +++ b/evm/scripts/Deploy.s.sol @@ -20,6 +20,7 @@ import {EvmInCosmosClient} from "../contracts/clients/EvmInCosmosClient.sol"; import "../contracts/apps/ucs/00-pingpong/PingPong.sol"; import "../contracts/apps/ucs/01-relay/Relay.sol"; import "../contracts/apps/ucs/02-nft/NFT.sol"; +import "../contracts/apps/ucs/03-zkgm/Zkgm.sol"; import "../contracts/lib/Hex.sol"; import "./Deployer.sol"; From a63da94b2999328bded3db55541e6d9956f3ff04 Mon Sep 17 00:00:00 2001 From: Hussein Ait Lahcen Date: Wed, 27 Nov 2024 23:57:55 +0100 Subject: [PATCH 2/5] feat(zkgm): mint/unescrow mechanism for fungible assets --- evm/contracts/apps/ucs/03-zkgm/IZkgmERC20.sol | 9 + evm/contracts/apps/ucs/03-zkgm/Zkgm.sol | 444 ++++++++++++++++-- evm/contracts/apps/ucs/03-zkgm/ZkgmERC20.sol | 44 ++ 3 files changed, 450 insertions(+), 47 deletions(-) create mode 100644 evm/contracts/apps/ucs/03-zkgm/IZkgmERC20.sol create mode 100644 evm/contracts/apps/ucs/03-zkgm/ZkgmERC20.sol diff --git a/evm/contracts/apps/ucs/03-zkgm/IZkgmERC20.sol b/evm/contracts/apps/ucs/03-zkgm/IZkgmERC20.sol new file mode 100644 index 0000000000..4dcb9dc685 --- /dev/null +++ b/evm/contracts/apps/ucs/03-zkgm/IZkgmERC20.sol @@ -0,0 +1,9 @@ +pragma solidity ^0.8.27; + +import "@openzeppelin/token/ERC20/IERC20.sol"; +import "@openzeppelin/token/ERC20/extensions/IERC20Metadata.sol"; + +interface IZkgmERC20 is IERC20, IERC20Metadata { + function mint(address to, uint256 amount) external; + function burn(address from, uint256 amount) external; +} diff --git a/evm/contracts/apps/ucs/03-zkgm/Zkgm.sol b/evm/contracts/apps/ucs/03-zkgm/Zkgm.sol index 0a6720d274..63e854014b 100644 --- a/evm/contracts/apps/ucs/03-zkgm/Zkgm.sol +++ b/evm/contracts/apps/ucs/03-zkgm/Zkgm.sol @@ -1,21 +1,47 @@ pragma solidity ^0.8.27; +import "@openzeppelin/token/ERC20/IERC20.sol"; +import "solady/utils/CREATE3.sol"; + import "../../Base.sol"; import "../../../core/25-handler/IBCHandler.sol"; +import "../../../core/04-channel/IBCPacket.sol"; import "../../../core/05-port/IIBCModule.sol"; import "./IEurekaModule.sol"; +import "./IZkgmERC20.sol"; +import "./ZkgmERC20.sol"; + +struct Acknowledgement { + uint256 tag; + bytes innerAck; +} + +struct BatchAcknowledgement { + bytes[] acknowledgements; +} + +struct AssetTransferAcknowledgement { + uint256 fillType; + bytes marketMaker; +} struct ZkgmPacket { - uint8 version; bytes32 salt; - uint8 syscallIndex; + bytes syscall; +} + +struct SyscallPacket { + uint8 version; + uint8 index; bytes packet; } struct ForwardPacket { uint32 channelId; - bytes zkgmPacket; + uint64 timeoutHeight; + uint64 timeoutTimestamp; + bytes syscallPacket; } struct MultiplexPacket { @@ -26,7 +52,7 @@ struct MultiplexPacket { } struct BatchPacket { - bytes[] zkgmPackets; + bytes[] syscallPackets; } struct FungibleAssetTransferPacket { @@ -35,13 +61,20 @@ struct FungibleAssetTransferPacket { bytes sentToken; uint256 sentAmount; bytes askToken; - bytes askAmount; + uint256 askAmount; + bool onlyMaker; } library ZkgmLib { bytes public constant ACK_EMPTY = hex""; - bytes public constant ACK_FAILURE = abi.encode(0x00); - bytes public constant ACK_SUCCESS = abi.encode(0x01); + + uint256 public constant ACK_FAILURE = 0x00; + uint256 public constant ACK_SUCCESS = 0x01; + + bytes public constant ACK_ERR_ONLYMAKER = abi.encode(0xDEADC0DE); + + uint256 public constant FILL_TYPE_PROTOCOL = 0xB0CAD0; + uint256 public constant FILL_TYPE_MARKETMAKER = 0xD1CEC45E; uint8 public constant SYSCALL_FORWARD = 0x00; uint8 public constant SYSCALL_MULTIPLEX = 0x01; @@ -56,6 +89,57 @@ library ZkgmLib { error ErrUnknownSyscall(); error ErrInfiniteGame(); error ErrUnauthorized(); + error ErrInvalidAmount(); + error ErrOnlyMaker(); + error ErrInvalidFillType(); + + function encodeAssetTransferAck( + AssetTransferAcknowledgement memory ack + ) internal pure returns (bytes memory) { + return abi.encode(ack); + } + + function decodeAssetTransferAck( + bytes calldata stream + ) internal pure returns (AssetTransferAcknowledgement calldata) { + AssetTransferAcknowledgement calldata ack; + assembly { + ack := stream.offset + } + return ack; + } + + function encodeBatchAck( + BatchAcknowledgement memory ack + ) internal pure returns (bytes memory) { + return abi.encode(ack); + } + + function decodeBatchAck( + bytes calldata stream + ) internal pure returns (BatchAcknowledgement calldata) { + BatchAcknowledgement calldata acks; + assembly { + acks := stream.offset + } + return acks; + } + + function encodeAck( + Acknowledgement memory packet + ) internal pure returns (bytes memory) { + return abi.encode(packet); + } + + function decodeAck( + bytes calldata stream + ) internal pure returns (Acknowledgement calldata) { + Acknowledgement calldata packet; + assembly { + packet := stream.offset + } + return packet; + } function encode( ZkgmPacket memory packet @@ -73,6 +157,16 @@ library ZkgmLib { return packet; } + function decodeSyscall( + bytes calldata stream + ) internal pure returns (SyscallPacket calldata) { + SyscallPacket calldata packet; + assembly { + packet := stream.offset + } + return packet; + } + function decodeBatch( bytes calldata stream ) internal pure returns (BatchPacket calldata) { @@ -112,12 +206,24 @@ library ZkgmLib { } return packet; } + + function isDeployed( + address addr + ) internal returns (bool) { + uint32 size = 0; + assembly { + size := extcodesize(addr) + } + return (size > 0); + } } contract Zkgm is IBCAppBase { using ZkgmLib for *; IBCHandler private ibcHandler; + mapping(bytes32 => IBCPacket) private inFlightPacket; + mapping(uint32 => mapping(address => uint256)) private channelBalance; constructor( IBCHandler _ibcHandler @@ -143,11 +249,27 @@ contract Zkgm is IBCAppBase { // The acknowledgement may be asynchronous (forward/multiplex) if (acknowledgement.length == 0) { return ZkgmLib.ACK_EMPTY; + } else if ( + keccak256(acknowledgement) == keccak256(ZkgmLib.ACK_ERR_ONLYMAKER) + ) { + // Special case where we should avoid the packet from being + // received entirely as it is only fillable by a market maker. + revert ZkgmLib.ErrOnlyMaker(); } else { - return abi.encode(ZkgmLib.ACK_SUCCESS, acknowledgement); + return ZkgmLib.encodeAck( + Acknowledgement({ + tag: ZkgmLib.ACK_SUCCESS, + innerAck: acknowledgement + }) + ); } } else { - return ZkgmLib.ACK_FAILURE; + return ZkgmLib.encodeAck( + Acknowledgement({ + tag: ZkgmLib.ACK_FAILURE, + innerAck: ZkgmLib.ACK_EMPTY + }) + ); } } @@ -166,10 +288,8 @@ contract Zkgm is IBCAppBase { ibcPacket, relayer, relayerMsg, - zkgmPacket.version, zkgmPacket.salt, - zkgmPacket.syscallIndex, - zkgmPacket.packet + ZkgmLib.decodeSyscall(zkgmPacket.syscall) ); } @@ -177,45 +297,43 @@ contract Zkgm is IBCAppBase { IBCPacket calldata ibcPacket, address relayer, bytes calldata relayerMsg, - uint8 version, bytes32 salt, - uint8 syscallIndex, - bytes calldata packet - ) public returns (bytes memory) { - if (version != ZkgmLib.ZKGM_VERSION_0) { + SyscallPacket calldata syscallPacket + ) internal returns (bytes memory) { + if (syscallPacket.version != ZkgmLib.ZKGM_VERSION_0) { revert ZkgmLib.ErrUnsupportedVersion(); } - if (syscallIndex == ZkgmLib.SYSCALL_FUNGIBLE_ASSET_TRANSFER) { + if (syscallPacket.index == ZkgmLib.SYSCALL_FUNGIBLE_ASSET_TRANSFER) { return executeFungibleAssetTransfer( ibcPacket, relayer, relayerMsg, salt, - ZkgmLib.decodeFungibleAssetTransfer(packet) + ZkgmLib.decodeFungibleAssetTransfer(syscallPacket.packet) ); - } else if (syscallIndex == ZkgmLib.SYSCALL_BATCH) { + } else if (syscallPacket.index == ZkgmLib.SYSCALL_BATCH) { return executeBatch( ibcPacket, relayer, relayerMsg, salt, - ZkgmLib.decodeBatch(packet) + ZkgmLib.decodeBatch(syscallPacket.packet) ); - } else if (syscallIndex == ZkgmLib.SYSCALL_FORWARD) { + } else if (syscallPacket.index == ZkgmLib.SYSCALL_FORWARD) { return executeForward( ibcPacket, relayer, relayerMsg, salt, - ZkgmLib.decodeForward(packet) + ZkgmLib.decodeForward(syscallPacket.packet) ); - } else if (syscallIndex == ZkgmLib.SYSCALL_MULTIPLEX) { + } else if (syscallPacket.index == ZkgmLib.SYSCALL_MULTIPLEX) { return executeMultiplex( ibcPacket, relayer, relayerMsg, salt, - ZkgmLib.decodeMultiplex(packet) + ZkgmLib.decodeMultiplex(syscallPacket.packet) ); } else { revert ZkgmLib.ErrUnknownSyscall(); @@ -229,25 +347,25 @@ contract Zkgm is IBCAppBase { bytes32 salt, BatchPacket calldata batchPacket ) internal returns (bytes memory) { - uint256 l = batchPacket.zkgmPackets.length; - bytes[] memory acknowledgements = new bytes[](l); + uint256 l = batchPacket.syscallPackets.length; + bytes[] memory acks = new bytes[](l); for (uint256 i = 0; i < l; i++) { - ZkgmPacket calldata zkgmPacket = - ZkgmLib.decode(batchPacket.zkgmPackets[i]); - acknowledgements[i] = executeInternal( + SyscallPacket calldata syscallPacket = + ZkgmLib.decodeSyscall(batchPacket.syscallPackets[i]); + acks[i] = executeInternal( ibcPacket, relayer, relayerMsg, - zkgmPacket.version, - keccak256(abi.encode(salt, zkgmPacket.salt)), - zkgmPacket.syscallIndex, - zkgmPacket.packet + keccak256(abi.encode(salt)), + syscallPacket ); - if (acknowledgements[i].length == 0) { + if (acks[i].length == 0) { revert ZkgmLib.ErrBatchMustBeSync(); } } - return abi.encode(acknowledgements); + return ZkgmLib.encodeBatchAck( + BatchAcknowledgement({acknowledgements: acks}) + ); } function executeForward( @@ -257,7 +375,21 @@ contract Zkgm is IBCAppBase { bytes32 salt, ForwardPacket calldata forwardPacket ) internal returns (bytes memory) { - revert ZkgmLib.ErrUnimplemented(); + IBCPacket memory sentPacket = ibcHandler.sendPacket( + forwardPacket.channelId, + forwardPacket.timeoutHeight, + forwardPacket.timeoutTimestamp, + ZkgmLib.encode( + ZkgmPacket({ + salt: keccak256(abi.encode(salt)), + syscall: forwardPacket.syscallPacket + }) + ) + ); + // Guaranteed to be unique by the above sendPacket + bytes32 packetHash = IBCPacketLib.commitPacketMemory(sentPacket); + inFlightPacket[packetHash] = ibcPacket; + return ZkgmLib.ACK_EMPTY; } function executeMultiplex( @@ -273,12 +405,14 @@ contract Zkgm is IBCAppBase { IEurekaModule(contractAddress).onZkgm( multiplexPacket.sender, multiplexPacket.contractCalldata ); - return ZkgmLib.ACK_SUCCESS; + return abi.encode(ZkgmLib.ACK_SUCCESS); } else { IBCPacket memory multiplexIbcPacket = IBCPacket({ sourceChannel: ibcPacket.sourceChannel, destinationChannel: ibcPacket.destinationChannel, - data: multiplexPacket.contractCalldata, + data: abi.encode( + multiplexPacket.sender, multiplexPacket.contractCalldata + ), timeoutHeight: ibcPacket.timeoutHeight, timeoutTimestamp: ibcPacket.timeoutTimestamp }); @@ -297,6 +431,16 @@ contract Zkgm is IBCAppBase { } } + function predictWrappedToken( + uint32 channel, + bytes calldata token + ) internal returns (address, bytes32) { + bytes32 wrappedTokenSalt = keccak256(abi.encode(channel, token)); + address wrappedToken = + CREATE3.predictDeterministicAddress(wrappedTokenSalt); + return (wrappedToken, wrappedTokenSalt); + } + function executeFungibleAssetTransfer( IBCPacket calldata ibcPacket, address relayer, @@ -304,19 +448,225 @@ contract Zkgm is IBCAppBase { bytes32 salt, FungibleAssetTransferPacket calldata assetTransferPacket ) internal returns (bytes memory) { - revert ZkgmLib.ErrUnimplemented(); + if (assetTransferPacket.onlyMaker) { + return ZkgmLib.ACK_ERR_ONLYMAKER; + } + if (assetTransferPacket.askAmount > assetTransferPacket.sentAmount) { + revert ZkgmLib.ErrInvalidAmount(); + } + (address wrappedToken, bytes32 wrappedTokenSalt) = predictWrappedToken( + ibcPacket.destinationChannel, assetTransferPacket.sentToken + ); + address askToken = address(bytes20(assetTransferPacket.askToken)); + address receiver = address(bytes20(assetTransferPacket.receiver)); + uint256 fee = + assetTransferPacket.sentAmount - assetTransferPacket.askAmount; + if (askToken == wrappedToken) { + if (!ZkgmLib.isDeployed(wrappedToken)) { + CREATE3.deployDeterministic( + abi.encodePacked( + type(ZkgmERC20).creationCode, "test", "test" + ), + wrappedTokenSalt + ); + } + IZkgmERC20(wrappedToken).mint( + receiver, assetTransferPacket.askAmount + ); + if (fee > 0) { + IZkgmERC20(wrappedToken).mint(relayer, fee); + } + } else { + channelBalance[ibcPacket.destinationChannel][askToken] -= + assetTransferPacket.askAmount; + IERC20(askToken).transfer(receiver, assetTransferPacket.askAmount); + if (fee > 0) { + IERC20(askToken).transfer(relayer, fee); + } + } + return ZkgmLib.encodeAssetTransferAck( + AssetTransferAcknowledgement({ + fillType: ZkgmLib.FILL_TYPE_PROTOCOL, + marketMaker: ZkgmLib.ACK_EMPTY + }) + ); } function onAcknowledgementPacket( - IBCPacket calldata, - bytes calldata acknowledgement, - address - ) external virtual override onlyIBC {} + IBCPacket calldata ibcPacket, + bytes calldata ack, + address relayer + ) external virtual override onlyIBC { + bytes32 packetHash = IBCPacketLib.commitPacketMemory(ibcPacket); + IBCPacket memory parent = inFlightPacket[packetHash]; + // Specific case of forwarding where the ack is threaded back directly. + if (parent.timeoutTimestamp != 0 || parent.timeoutHeight != 0) { + ibcHandler.writeAcknowledgement(parent, ack); + delete inFlightPacket[packetHash]; + } else { + ZkgmPacket calldata zkgmPacket = ZkgmLib.decode(ibcPacket.data); + Acknowledgement calldata zkgmAck = ZkgmLib.decodeAck(ack); + acknowledgeInternal( + ibcPacket, + relayer, + zkgmPacket.salt, + ZkgmLib.decodeSyscall(zkgmPacket.syscall), + zkgmAck.tag == ZkgmLib.ACK_SUCCESS, + zkgmAck.innerAck + ); + } + } + + function acknowledgeInternal( + IBCPacket calldata ibcPacket, + address relayer, + bytes32 salt, + SyscallPacket calldata syscallPacket, + bool successful, + bytes calldata ack + ) internal { + if (syscallPacket.version != ZkgmLib.ZKGM_VERSION_0) { + revert ZkgmLib.ErrUnsupportedVersion(); + } + if (syscallPacket.index == ZkgmLib.SYSCALL_FUNGIBLE_ASSET_TRANSFER) { + acknowledgeFungibleAssetTransfer( + ibcPacket, + relayer, + salt, + ZkgmLib.decodeFungibleAssetTransfer(syscallPacket.packet), + successful, + ack + ); + } else if (syscallPacket.index == ZkgmLib.SYSCALL_BATCH) { + acknowledgeBatch( + ibcPacket, + relayer, + salt, + ZkgmLib.decodeBatch(syscallPacket.packet), + successful, + ack + ); + } else if (syscallPacket.index == ZkgmLib.SYSCALL_FORWARD) { + acknowledgeForward( + ibcPacket, + relayer, + salt, + ZkgmLib.decodeForward(syscallPacket.packet), + successful, + ack + ); + } else if (syscallPacket.index == ZkgmLib.SYSCALL_MULTIPLEX) { + acknowledgeMultiplex( + ibcPacket, + relayer, + salt, + ZkgmLib.decodeMultiplex(syscallPacket.packet), + successful, + ack + ); + } else { + revert ZkgmLib.ErrUnknownSyscall(); + } + } + + function acknowledgeBatch( + IBCPacket calldata ibcPacket, + address relayer, + bytes32 salt, + BatchPacket calldata batchPacket, + bool successful, + bytes calldata ack + ) internal { + uint256 l = batchPacket.syscallPackets.length; + BatchAcknowledgement calldata batchAck = ZkgmLib.decodeBatchAck(ack); + for (uint256 i = 0; i < l; i++) { + // The syscallAck is set to the ack by default just to satisfy the + // compiler. The failure branch will never read the ack, hence the + // assignation has no effect in the recursive handling semantic. + bytes calldata syscallAck = ack; + if (successful) { + syscallAck = batchAck.acknowledgements[i]; + } + acknowledgeInternal( + ibcPacket, + relayer, + keccak256(abi.encode(salt)), + ZkgmLib.decodeSyscall(batchPacket.syscallPackets[i]), + successful, + syscallAck + ); + } + } + + function acknowledgeForward( + IBCPacket calldata ibcPacket, + address relayer, + bytes32 salt, + ForwardPacket calldata forwardPacket, + bool successful, + bytes calldata ack + ) internal {} + + function acknowledgeMultiplex( + IBCPacket calldata ibcPacket, + address relayer, + bytes32 salt, + MultiplexPacket calldata multiplexPacket, + bool successful, + bytes calldata ack + ) internal { + if (successful && !multiplexPacket.eureka) { + IBCPacket memory multiplexIbcPacket = IBCPacket({ + sourceChannel: ibcPacket.sourceChannel, + destinationChannel: ibcPacket.destinationChannel, + data: abi.encode( + multiplexPacket.contractAddress, + multiplexPacket.contractCalldata + ), + timeoutHeight: ibcPacket.timeoutHeight, + timeoutTimestamp: ibcPacket.timeoutTimestamp + }); + IIBCModule(address(bytes20(multiplexPacket.sender))) + .onAcknowledgementPacket(multiplexIbcPacket, ack, relayer); + } + } + + function acknowledgeFungibleAssetTransfer( + IBCPacket calldata ibcPacket, + address relayer, + bytes32 salt, + FungibleAssetTransferPacket calldata assetTransferPacket, + bool successful, + bytes calldata ack + ) internal { + AssetTransferAcknowledgement calldata assetTransferAck = + ZkgmLib.decodeAssetTransferAck(ack); + if (assetTransferAck.fillType == ZkgmLib.FILL_TYPE_PROTOCOL) { + // The protocol filled, fee was paid to relayer. + } else if (assetTransferAck.fillType == ZkgmLib.FILL_TYPE_MARKETMAKER) { + IERC20(address(bytes20(assetTransferPacket.sentToken))).transfer( + address(bytes20(assetTransferAck.marketMaker)), + assetTransferPacket.sentAmount + ); + } else { + revert ZkgmLib.ErrInvalidFillType(); + } + } function onTimeoutPacket( - IBCPacket calldata, - address - ) external virtual override onlyIBC {} + IBCPacket calldata ibcPacket, + address relayer + ) external virtual override onlyIBC { + ZkgmPacket calldata zkgmPacket = ZkgmLib.decode(ibcPacket.data); + acknowledgeInternal( + ibcPacket, + relayer, + zkgmPacket.salt, + ZkgmLib.decodeSyscall(zkgmPacket.syscall), + false, + ibcPacket.data + ); + } function onChanOpenInit( uint32, diff --git a/evm/contracts/apps/ucs/03-zkgm/ZkgmERC20.sol b/evm/contracts/apps/ucs/03-zkgm/ZkgmERC20.sol new file mode 100644 index 0000000000..95578bb842 --- /dev/null +++ b/evm/contracts/apps/ucs/03-zkgm/ZkgmERC20.sol @@ -0,0 +1,44 @@ +pragma solidity ^0.8.27; + +import "@openzeppelin/token/ERC20/ERC20.sol"; +import "./IZkgmERC20.sol"; + +contract ZkgmERC20 is ERC20, IZkgmERC20 { + error ERC20Unauthorized(); + + address public admin; + uint8 private _decimals; + + constructor(string memory n, string memory s) ERC20(n, s) { + admin = msg.sender; + _decimals = 18; + } + + function decimals() + public + view + override(ERC20, IERC20Metadata) + returns (uint8) + { + return _decimals; + } + + function mint(address to, uint256 amount) external onlyAdmin { + _mint(to, amount); + } + + function burn(address from, uint256 amount) external onlyAdmin { + _burn(from, amount); + } + + modifier onlyAdmin() { + _checkAdmin(); + _; + } + + function _checkAdmin() internal view virtual { + if (msg.sender != admin) { + revert ERC20Unauthorized(); + } + } +} From cba50a213d7e10188398158b19de9ceb0a447aa0 Mon Sep 17 00:00:00 2001 From: Hussein Ait Lahcen Date: Tue, 3 Dec 2024 15:52:10 +0100 Subject: [PATCH 3/5] feat(zkgm): introduce send and verify, correct mint/burn mechanism based on channel --- evm/contracts/apps/ucs/03-zkgm/Zkgm.sol | 424 +++++++++++++++++++++--- evm/tests/src/app/Zkgm.t.sol | 94 ++++++ 2 files changed, 475 insertions(+), 43 deletions(-) create mode 100644 evm/tests/src/app/Zkgm.t.sol diff --git a/evm/contracts/apps/ucs/03-zkgm/Zkgm.sol b/evm/contracts/apps/ucs/03-zkgm/Zkgm.sol index 63e854014b..cd8d446a4b 100644 --- a/evm/contracts/apps/ucs/03-zkgm/Zkgm.sol +++ b/evm/contracts/apps/ucs/03-zkgm/Zkgm.sol @@ -1,7 +1,12 @@ pragma solidity ^0.8.27; import "@openzeppelin/token/ERC20/IERC20.sol"; +import "@openzeppelin/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/token/ERC20/extensions/IERC20Metadata.sol"; + import "solady/utils/CREATE3.sol"; +import "solady/utils/LibBit.sol"; +import "solady/utils/LibString.sol"; import "../../Base.sol"; import "../../../core/25-handler/IBCHandler.sol"; @@ -12,22 +17,9 @@ import "./IEurekaModule.sol"; import "./IZkgmERC20.sol"; import "./ZkgmERC20.sol"; -struct Acknowledgement { - uint256 tag; - bytes innerAck; -} - -struct BatchAcknowledgement { - bytes[] acknowledgements; -} - -struct AssetTransferAcknowledgement { - uint256 fillType; - bytes marketMaker; -} - struct ZkgmPacket { bytes32 salt; + uint256 path; bytes syscall; } @@ -59,12 +51,29 @@ struct FungibleAssetTransferPacket { bytes sender; bytes receiver; bytes sentToken; + uint256 sentTokenPrefix; + string sentSymbol; + string sentName; uint256 sentAmount; bytes askToken; uint256 askAmount; bool onlyMaker; } +struct Acknowledgement { + uint256 tag; + bytes innerAck; +} + +struct BatchAcknowledgement { + bytes[] acknowledgements; +} + +struct AssetTransferAcknowledgement { + uint256 fillType; + bytes marketMaker; +} + library ZkgmLib { bytes public constant ACK_EMPTY = hex""; @@ -83,6 +92,8 @@ library ZkgmLib { uint8 public constant ZKGM_VERSION_0 = 0x00; + bytes32 public constant IBC_VERSION = keccak256("ucs03-zkgm-0"); + error ErrUnsupportedVersion(); error ErrUnimplemented(); error ErrBatchMustBeSync(); @@ -92,6 +103,11 @@ library ZkgmLib { error ErrInvalidAmount(); error ErrOnlyMaker(); error ErrInvalidFillType(); + error ErrInvalidIBCVersion(); + error ErrInvalidHops(); + error ErrInvalidAssetOrigin(); + error ErrInvalidAssetSymbol(); + error ErrInvalidAssetName(); function encodeAssetTransferAck( AssetTransferAcknowledgement memory ack @@ -216,14 +232,40 @@ library ZkgmLib { } return (size > 0); } + + function updateChannelPath( + uint256 path, + uint32 nextChannelId + ) internal pure returns (uint256) { + if (path == 0) { + return uint256(nextChannelId); + } + uint256 nextHopIndex = LibBit.fls(path) / 32 + 1; + if (nextHopIndex > 7) { + revert ErrInvalidHops(); + } + return (uint256(nextChannelId) << 32 * nextHopIndex) | path; + } + + function lastChannelFromPath( + uint256 path + ) internal pure returns (uint32) { + if (path == 0) { + return 0; + } + uint256 currentHopIndex = LibBit.fls(path) / 32; + return uint32(path >> currentHopIndex * 32); + } } contract Zkgm is IBCAppBase { using ZkgmLib for *; + using LibString for *; IBCHandler private ibcHandler; mapping(bytes32 => IBCPacket) private inFlightPacket; mapping(uint32 => mapping(address => uint256)) private channelBalance; + mapping(address => uint256) tokenOrigin; constructor( IBCHandler _ibcHandler @@ -235,6 +277,120 @@ contract Zkgm is IBCAppBase { return address(ibcHandler); } + function send( + uint32 channelId, + uint64 timeoutHeight, + uint64 timeoutTimestamp, + bytes32 salt, + bytes calldata rawSyscall + ) public { + verifyInternal(channelId, 0, rawSyscall); + ibcHandler.sendPacket( + channelId, + timeoutHeight, + timeoutTimestamp, + ZkgmLib.encode( + ZkgmPacket({salt: salt, path: 0, syscall: rawSyscall}) + ) + ); + } + + function verifyInternal( + uint32 channelId, + uint256 path, + bytes calldata rawSyscall + ) internal { + SyscallPacket calldata syscallPacket = ZkgmLib.decodeSyscall(rawSyscall); + if (syscallPacket.version != ZkgmLib.ZKGM_VERSION_0) { + revert ZkgmLib.ErrUnsupportedVersion(); + } + if (syscallPacket.index == ZkgmLib.SYSCALL_FUNGIBLE_ASSET_TRANSFER) { + verifyFungibleAssetTransfer( + channelId, + path, + ZkgmLib.decodeFungibleAssetTransfer(syscallPacket.packet) + ); + } else if (syscallPacket.index == ZkgmLib.SYSCALL_BATCH) { + verifyBatch( + channelId, path, ZkgmLib.decodeBatch(syscallPacket.packet) + ); + } else if (syscallPacket.index == ZkgmLib.SYSCALL_FORWARD) { + verifyForward( + channelId, path, ZkgmLib.decodeForward(syscallPacket.packet) + ); + } else if (syscallPacket.index == ZkgmLib.SYSCALL_MULTIPLEX) { + verifyMultiplex( + channelId, path, ZkgmLib.decodeMultiplex(syscallPacket.packet) + ); + } else { + revert ZkgmLib.ErrUnknownSyscall(); + } + } + + function verifyFungibleAssetTransfer( + uint32 channelId, + uint256 path, + FungibleAssetTransferPacket calldata assetTransferPacket + ) internal { + IERC20Metadata sentToken = + IERC20Metadata(address(bytes20(assetTransferPacket.sentToken))); + if (!assetTransferPacket.sentName.eq(sentToken.name())) { + revert ZkgmLib.ErrInvalidAssetName(); + } + if (!assetTransferPacket.sentSymbol.eq(sentToken.symbol())) { + revert ZkgmLib.ErrInvalidAssetSymbol(); + } + uint256 origin = tokenOrigin[address(sentToken)]; + if (ZkgmLib.lastChannelFromPath(origin) == channelId) { + IZkgmERC20(address(sentToken)).burn( + msg.sender, assetTransferPacket.sentAmount + ); + } else { + // TODO: extract this as a step before verifying to allow for ERC777 + // send hook + SafeERC20.safeTransferFrom( + sentToken, + msg.sender, + address(this), + assetTransferPacket.sentAmount + ); + } + if (!assetTransferPacket.onlyMaker) { + if (assetTransferPacket.sentTokenPrefix != origin) { + revert ZkgmLib.ErrInvalidAssetOrigin(); + } + } + } + + function verifyBatch( + uint32 channelId, + uint256 path, + BatchPacket calldata batchPacket + ) internal { + uint256 l = batchPacket.syscallPackets.length; + for (uint256 i = 0; i < l; i++) { + verifyInternal(channelId, path, batchPacket.syscallPackets[i]); + } + } + + function verifyForward( + uint32 channelId, + uint256 path, + ForwardPacket calldata forwardPacket + ) internal { + verifyInternal( + channelId, + ZkgmLib.updateChannelPath(path, forwardPacket.channelId), + forwardPacket.syscallPacket + ); + } + + function verifyMultiplex( + uint32 channelId, + uint256 path, + MultiplexPacket calldata multiplexPacket + ) internal {} + function onRecvPacket( IBCPacket calldata packet, address relayer, @@ -250,7 +406,8 @@ contract Zkgm is IBCAppBase { if (acknowledgement.length == 0) { return ZkgmLib.ACK_EMPTY; } else if ( - keccak256(acknowledgement) == keccak256(ZkgmLib.ACK_ERR_ONLYMAKER) + keccak256(acknowledgement) + == keccak256(ZkgmLib.ACK_ERR_ONLYMAKER) ) { // Special case where we should avoid the packet from being // received entirely as it is only fillable by a market maker. @@ -289,6 +446,7 @@ contract Zkgm is IBCAppBase { relayer, relayerMsg, zkgmPacket.salt, + zkgmPacket.path, ZkgmLib.decodeSyscall(zkgmPacket.syscall) ); } @@ -298,6 +456,7 @@ contract Zkgm is IBCAppBase { address relayer, bytes calldata relayerMsg, bytes32 salt, + uint256 path, SyscallPacket calldata syscallPacket ) internal returns (bytes memory) { if (syscallPacket.version != ZkgmLib.ZKGM_VERSION_0) { @@ -309,6 +468,7 @@ contract Zkgm is IBCAppBase { relayer, relayerMsg, salt, + path, ZkgmLib.decodeFungibleAssetTransfer(syscallPacket.packet) ); } else if (syscallPacket.index == ZkgmLib.SYSCALL_BATCH) { @@ -317,6 +477,7 @@ contract Zkgm is IBCAppBase { relayer, relayerMsg, salt, + path, ZkgmLib.decodeBatch(syscallPacket.packet) ); } else if (syscallPacket.index == ZkgmLib.SYSCALL_FORWARD) { @@ -325,6 +486,7 @@ contract Zkgm is IBCAppBase { relayer, relayerMsg, salt, + path, ZkgmLib.decodeForward(syscallPacket.packet) ); } else if (syscallPacket.index == ZkgmLib.SYSCALL_MULTIPLEX) { @@ -345,6 +507,7 @@ contract Zkgm is IBCAppBase { address relayer, bytes calldata relayerMsg, bytes32 salt, + uint256 path, BatchPacket calldata batchPacket ) internal returns (bytes memory) { uint256 l = batchPacket.syscallPackets.length; @@ -357,6 +520,7 @@ contract Zkgm is IBCAppBase { relayer, relayerMsg, keccak256(abi.encode(salt)), + path, syscallPacket ); if (acks[i].length == 0) { @@ -373,8 +537,12 @@ contract Zkgm is IBCAppBase { address relayer, bytes calldata relayerMsg, bytes32 salt, + uint256 path, ForwardPacket calldata forwardPacket ) internal returns (bytes memory) { + // TODO: consider using a magic value for few bytes of the salt in order + // to know that it's a forwarded packet in the acknowledgement, without + // having to index in `inFlightPacket`, saving gas in the process. IBCPacket memory sentPacket = ibcHandler.sendPacket( forwardPacket.channelId, forwardPacket.timeoutHeight, @@ -382,6 +550,9 @@ contract Zkgm is IBCAppBase { ZkgmLib.encode( ZkgmPacket({ salt: keccak256(abi.encode(salt)), + path: ZkgmLib.updateChannelPath( + path, ibcPacket.destinationChannel + ), syscall: forwardPacket.syscallPacket }) ) @@ -432,10 +603,12 @@ contract Zkgm is IBCAppBase { } function predictWrappedToken( - uint32 channel, + uint256 path, + uint32 destinationChannel, bytes calldata token ) internal returns (address, bytes32) { - bytes32 wrappedTokenSalt = keccak256(abi.encode(channel, token)); + bytes32 wrappedTokenSalt = + keccak256(abi.encode(path, destinationChannel, token)); address wrappedToken = CREATE3.predictDeterministicAddress(wrappedTokenSalt); return (wrappedToken, wrappedTokenSalt); @@ -446,29 +619,38 @@ contract Zkgm is IBCAppBase { address relayer, bytes calldata relayerMsg, bytes32 salt, + uint256 path, FungibleAssetTransferPacket calldata assetTransferPacket ) internal returns (bytes memory) { if (assetTransferPacket.onlyMaker) { return ZkgmLib.ACK_ERR_ONLYMAKER; } + // The protocol can only wrap or unwrap an asset, hence 1:1 baked. + // The fee is the difference, which can only be positive. if (assetTransferPacket.askAmount > assetTransferPacket.sentAmount) { revert ZkgmLib.ErrInvalidAmount(); } (address wrappedToken, bytes32 wrappedTokenSalt) = predictWrappedToken( - ibcPacket.destinationChannel, assetTransferPacket.sentToken + path, ibcPacket.destinationChannel, assetTransferPacket.sentToken ); address askToken = address(bytes20(assetTransferPacket.askToken)); address receiver = address(bytes20(assetTransferPacket.receiver)); + // Previously asserted to be <=. uint256 fee = assetTransferPacket.sentAmount - assetTransferPacket.askAmount; if (askToken == wrappedToken) { if (!ZkgmLib.isDeployed(wrappedToken)) { CREATE3.deployDeterministic( abi.encodePacked( - type(ZkgmERC20).creationCode, "test", "test" + type(ZkgmERC20).creationCode, + assetTransferPacket.sentSymbol, + assetTransferPacket.sentName ), wrappedTokenSalt ); + tokenOrigin[wrappedToken] = ZkgmLib.updateChannelPath( + path, ibcPacket.destinationChannel + ); } IZkgmERC20(wrappedToken).mint( receiver, assetTransferPacket.askAmount @@ -477,11 +659,18 @@ contract Zkgm is IBCAppBase { IZkgmERC20(wrappedToken).mint(relayer, fee); } } else { - channelBalance[ibcPacket.destinationChannel][askToken] -= - assetTransferPacket.askAmount; - IERC20(askToken).transfer(receiver, assetTransferPacket.askAmount); - if (fee > 0) { - IERC20(askToken).transfer(relayer, fee); + if (assetTransferPacket.sentTokenPrefix == ibcPacket.sourceChannel) + { + channelBalance[ibcPacket.destinationChannel][askToken] -= + assetTransferPacket.askAmount; + SafeERC20.safeTransfer( + IERC20(askToken), receiver, assetTransferPacket.askAmount + ); + if (fee > 0) { + SafeERC20.safeTransfer(IERC20(askToken), relayer, fee); + } + } else { + return ZkgmLib.ACK_ERR_ONLYMAKER; } } return ZkgmLib.encodeAssetTransferAck( @@ -639,17 +828,39 @@ contract Zkgm is IBCAppBase { bool successful, bytes calldata ack ) internal { - AssetTransferAcknowledgement calldata assetTransferAck = - ZkgmLib.decodeAssetTransferAck(ack); - if (assetTransferAck.fillType == ZkgmLib.FILL_TYPE_PROTOCOL) { - // The protocol filled, fee was paid to relayer. - } else if (assetTransferAck.fillType == ZkgmLib.FILL_TYPE_MARKETMAKER) { - IERC20(address(bytes20(assetTransferPacket.sentToken))).transfer( - address(bytes20(assetTransferAck.marketMaker)), - assetTransferPacket.sentAmount - ); + if (successful) { + AssetTransferAcknowledgement calldata assetTransferAck = + ZkgmLib.decodeAssetTransferAck(ack); + if (assetTransferAck.fillType == ZkgmLib.FILL_TYPE_PROTOCOL) { + // The protocol filled, fee was paid to relayer. + } else if ( + assetTransferAck.fillType == ZkgmLib.FILL_TYPE_MARKETMAKER + ) { + // A market maker filled, we pay with the sent asset. + address marketMaker = + address(bytes20(assetTransferAck.marketMaker)); + address sentToken = + address(bytes20(assetTransferPacket.sentToken)); + if ( + ZkgmLib.lastChannelFromPath( + assetTransferPacket.sentTokenPrefix + ) == ibcPacket.sourceChannel + ) { + IZkgmERC20(address(sentToken)).mint( + marketMaker, assetTransferPacket.sentAmount + ); + } else { + SafeERC20.safeTransfer( + IERC20(sentToken), + marketMaker, + assetTransferPacket.sentAmount + ); + } + } else { + revert ZkgmLib.ErrInvalidFillType(); + } } else { - revert ZkgmLib.ErrInvalidFillType(); + refund(ibcPacket.sourceChannel, assetTransferPacket); } } @@ -658,31 +869,158 @@ contract Zkgm is IBCAppBase { address relayer ) external virtual override onlyIBC { ZkgmPacket calldata zkgmPacket = ZkgmLib.decode(ibcPacket.data); - acknowledgeInternal( + timeoutInternal( ibcPacket, relayer, zkgmPacket.salt, - ZkgmLib.decodeSyscall(zkgmPacket.syscall), - false, - ibcPacket.data + ZkgmLib.decodeSyscall(zkgmPacket.syscall) ); } + function timeoutInternal( + IBCPacket calldata ibcPacket, + address relayer, + bytes32 salt, + SyscallPacket calldata syscallPacket + ) internal { + if (syscallPacket.version != ZkgmLib.ZKGM_VERSION_0) { + revert ZkgmLib.ErrUnsupportedVersion(); + } + if (syscallPacket.index == ZkgmLib.SYSCALL_FUNGIBLE_ASSET_TRANSFER) { + timeoutFungibleAssetTransfer( + ibcPacket, + relayer, + salt, + ZkgmLib.decodeFungibleAssetTransfer(syscallPacket.packet) + ); + } else if (syscallPacket.index == ZkgmLib.SYSCALL_BATCH) { + timeoutBatch( + ibcPacket, + relayer, + salt, + ZkgmLib.decodeBatch(syscallPacket.packet) + ); + } else if (syscallPacket.index == ZkgmLib.SYSCALL_FORWARD) { + timeoutForward( + ibcPacket, + relayer, + salt, + ZkgmLib.decodeForward(syscallPacket.packet) + ); + } else if (syscallPacket.index == ZkgmLib.SYSCALL_MULTIPLEX) { + timeoutMultiplex( + ibcPacket, + relayer, + salt, + ZkgmLib.decodeMultiplex(syscallPacket.packet) + ); + } else { + revert ZkgmLib.ErrUnknownSyscall(); + } + } + + function timeoutBatch( + IBCPacket calldata ibcPacket, + address relayer, + bytes32 salt, + BatchPacket calldata batchPacket + ) internal { + uint256 l = batchPacket.syscallPackets.length; + for (uint256 i = 0; i < l; i++) { + timeoutInternal( + ibcPacket, + relayer, + keccak256(abi.encode(salt)), + ZkgmLib.decodeSyscall(batchPacket.syscallPackets[i]) + ); + } + } + + function timeoutForward( + IBCPacket calldata ibcPacket, + address relayer, + bytes32 salt, + ForwardPacket calldata forwardPacket + ) internal {} + + function timeoutMultiplex( + IBCPacket calldata ibcPacket, + address relayer, + bytes32 salt, + MultiplexPacket calldata multiplexPacket + ) internal { + if (!multiplexPacket.eureka) { + IBCPacket memory multiplexIbcPacket = IBCPacket({ + sourceChannel: ibcPacket.sourceChannel, + destinationChannel: ibcPacket.destinationChannel, + data: abi.encode( + multiplexPacket.contractAddress, + multiplexPacket.contractCalldata + ), + timeoutHeight: ibcPacket.timeoutHeight, + timeoutTimestamp: ibcPacket.timeoutTimestamp + }); + IIBCModule(address(bytes20(multiplexPacket.sender))).onTimeoutPacket( + multiplexIbcPacket, relayer + ); + } + } + + function timeoutFungibleAssetTransfer( + IBCPacket calldata ibcPacket, + address relayer, + bytes32 salt, + FungibleAssetTransferPacket calldata assetTransferPacket + ) internal { + refund(ibcPacket.sourceChannel, assetTransferPacket); + } + + function refund( + uint32 sourceChannel, + FungibleAssetTransferPacket calldata assetTransferPacket + ) internal { + address sender = address(bytes20(assetTransferPacket.sender)); + address sentToken = address(bytes20(assetTransferPacket.sentToken)); + if ( + ZkgmLib.lastChannelFromPath(assetTransferPacket.sentTokenPrefix) + == sourceChannel + ) { + IZkgmERC20(address(sentToken)).mint( + sender, assetTransferPacket.sentAmount + ); + } else { + SafeERC20.safeTransfer( + IERC20(sentToken), sender, assetTransferPacket.sentAmount + ); + } + } + function onChanOpenInit( uint32, uint32, - string calldata, + string calldata version, address - ) external virtual override onlyIBC {} + ) external virtual override onlyIBC { + if (keccak256(bytes(version)) != ZkgmLib.IBC_VERSION) { + revert ZkgmLib.ErrInvalidIBCVersion(); + } + } function onChanOpenTry( uint32, uint32, uint32, - string calldata, - string calldata, + string calldata version, + string calldata counterpartyVersion, address - ) external virtual override onlyIBC {} + ) external virtual override onlyIBC { + if (keccak256(bytes(version)) != ZkgmLib.IBC_VERSION) { + revert ZkgmLib.ErrInvalidIBCVersion(); + } + if (keccak256(bytes(counterpartyVersion)) != ZkgmLib.IBC_VERSION) { + revert ZkgmLib.ErrInvalidIBCVersion(); + } + } function onChanOpenAck( uint32 channelId, diff --git a/evm/tests/src/app/Zkgm.t.sol b/evm/tests/src/app/Zkgm.t.sol new file mode 100644 index 0000000000..605aab1ff4 --- /dev/null +++ b/evm/tests/src/app/Zkgm.t.sol @@ -0,0 +1,94 @@ +pragma solidity ^0.8.27; + +import "forge-std/Test.sol"; + +import "../../../contracts/apps/ucs/03-zkgm/Zkgm.sol"; + +contract ZkgmTests is Test { + function test_lastChannelFromPathOk_1( + uint32 a + ) public { + vm.assume(a > 0); + assertEq( + ZkgmLib.lastChannelFromPath(ZkgmLib.updateChannelPath(0, a)), a + ); + } + + function test_lastChannelFromPathOk_2(uint32 a, uint32 b) public { + vm.assume(a > 0); + vm.assume(b > 0); + assertEq( + ZkgmLib.lastChannelFromPath( + ZkgmLib.updateChannelPath(ZkgmLib.updateChannelPath(0, a), b) + ), + b + ); + } + + function test_lastChannelFromPathOk_3( + uint32 a, + uint32 b, + uint32 c + ) public { + vm.assume(a > 0); + vm.assume(b > 0); + vm.assume(c > 0); + assertEq( + ZkgmLib.lastChannelFromPath( + ZkgmLib.updateChannelPath( + ZkgmLib.updateChannelPath( + ZkgmLib.updateChannelPath(0, a), b + ), + c + ) + ), + c + ); + } + + function test_channelPathOk( + uint32 a, + uint32 b, + uint32 c, + uint32 d, + uint32 e, + uint32 f, + uint32 g, + uint32 h + ) public { + vm.assume(a > 0); + vm.assume(b > 0); + vm.assume(c > 0); + vm.assume(d > 0); + vm.assume(e > 0); + vm.assume(f > 0); + vm.assume(g > 0); + vm.assume(h > 0); + assertEq( + ZkgmLib.updateChannelPath( + ZkgmLib.updateChannelPath( + ZkgmLib.updateChannelPath( + ZkgmLib.updateChannelPath( + ZkgmLib.updateChannelPath( + ZkgmLib.updateChannelPath( + ZkgmLib.updateChannelPath( + ZkgmLib.updateChannelPath(0, a), b + ), + c + ), + d + ), + e + ), + f + ), + g + ), + h + ), + uint256(a) | uint256(b) << 32 | uint256(c) << 64 | uint256(d) << 96 + | uint256(e) << 128 | uint256(f) << 160 | uint256(g) << 192 + | uint256(h) << 224 + ); + } +} From aa691a0c2b22c25950b88eb70641fabc87257896 Mon Sep 17 00:00:00 2001 From: Hussein Ait Lahcen Date: Mon, 6 Jan 2025 12:29:03 +0100 Subject: [PATCH 4/5] feat(zkgm): upgradeable and fixes --- evm/contracts/apps/ucs/03-zkgm/Zkgm.sol | 146 +++++++++++++------ evm/contracts/apps/ucs/03-zkgm/ZkgmERC20.sol | 4 +- evm/tests/src/app/Zkgm.t.sol | 97 ++++++++++++ 3 files changed, 197 insertions(+), 50 deletions(-) diff --git a/evm/contracts/apps/ucs/03-zkgm/Zkgm.sol b/evm/contracts/apps/ucs/03-zkgm/Zkgm.sol index cd8d446a4b..97a977856a 100644 --- a/evm/contracts/apps/ucs/03-zkgm/Zkgm.sol +++ b/evm/contracts/apps/ucs/03-zkgm/Zkgm.sol @@ -1,5 +1,10 @@ pragma solidity ^0.8.27; +import "@openzeppelin-upgradeable/proxy/utils/Initializable.sol"; +import "@openzeppelin-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import "@openzeppelin-upgradeable/access/OwnableUpgradeable.sol"; +import "@openzeppelin-upgradeable/utils/PausableUpgradeable.sol"; + import "@openzeppelin/token/ERC20/IERC20.sol"; import "@openzeppelin/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/token/ERC20/extensions/IERC20Metadata.sol"; @@ -9,7 +14,6 @@ import "solady/utils/LibBit.sol"; import "solady/utils/LibString.sol"; import "../../Base.sol"; -import "../../../core/25-handler/IBCHandler.sol"; import "../../../core/04-channel/IBCPacket.sol"; import "../../../core/05-port/IIBCModule.sol"; @@ -80,7 +84,7 @@ library ZkgmLib { uint256 public constant ACK_FAILURE = 0x00; uint256 public constant ACK_SUCCESS = 0x01; - bytes public constant ACK_ERR_ONLYMAKER = abi.encode(0xDEADC0DE); + bytes public constant ACK_ERR_ONLYMAKER = hex"DEADC0DE"; uint256 public constant FILL_TYPE_PROTOCOL = 0xB0CAD0; uint256 public constant FILL_TYPE_MARKETMAKER = 0xD1CEC45E; @@ -112,7 +116,7 @@ library ZkgmLib { function encodeAssetTransferAck( AssetTransferAcknowledgement memory ack ) internal pure returns (bytes memory) { - return abi.encode(ack); + return abi.encode(ack.fillType, ack.marketMaker); } function decodeAssetTransferAck( @@ -128,7 +132,7 @@ library ZkgmLib { function encodeBatchAck( BatchAcknowledgement memory ack ) internal pure returns (bytes memory) { - return abi.encode(ack); + return abi.encode(ack.acknowledgements); } function decodeBatchAck( @@ -142,9 +146,9 @@ library ZkgmLib { } function encodeAck( - Acknowledgement memory packet + Acknowledgement memory ack ) internal pure returns (bytes memory) { - return abi.encode(packet); + return abi.encode(ack.tag, ack.innerAck); } function decodeAck( @@ -160,7 +164,7 @@ library ZkgmLib { function encode( ZkgmPacket memory packet ) internal pure returns (bytes memory) { - return abi.encode(packet); + return abi.encode(packet.salt, packet.path, packet.syscall); } function decode( @@ -230,7 +234,7 @@ library ZkgmLib { assembly { size := extcodesize(addr) } - return (size > 0); + return size > 0; } function updateChannelPath( @@ -258,18 +262,30 @@ library ZkgmLib { } } -contract Zkgm is IBCAppBase { +contract UCS03Zkgm is + IBCAppBase, + Initializable, + UUPSUpgradeable, + OwnableUpgradeable, + PausableUpgradeable +{ using ZkgmLib for *; using LibString for *; - IBCHandler private ibcHandler; - mapping(bytes32 => IBCPacket) private inFlightPacket; - mapping(uint32 => mapping(address => uint256)) private channelBalance; - mapping(address => uint256) tokenOrigin; + IIBCPacket public ibcHandler; + mapping(bytes32 => IBCPacket) public inFlightPacket; + mapping(uint32 => mapping(address => uint256)) public channelBalance; + mapping(address => uint256) public tokenOrigin; + + constructor() { + _disableInitializers(); + } - constructor( - IBCHandler _ibcHandler - ) { + function initialize( + IIBCPacket _ibcHandler, + address admin + ) public initializer { + __Ownable_init(admin); ibcHandler = _ibcHandler; } @@ -290,6 +306,7 @@ contract Zkgm is IBCAppBase { timeoutHeight, timeoutTimestamp, ZkgmLib.encode( + // TODO: change salt to string and then assert its prefixed with user address and keccak256 it ZkgmPacket({salt: salt, path: 0, syscall: rawSyscall}) ) ); @@ -354,6 +371,8 @@ contract Zkgm is IBCAppBase { address(this), assetTransferPacket.sentAmount ); + channelBalance[channelId][address(sentToken)] += + assetTransferPacket.sentAmount; } if (!assetTransferPacket.onlyMaker) { if (assetTransferPacket.sentTokenPrefix != origin) { @@ -396,30 +415,31 @@ contract Zkgm is IBCAppBase { address relayer, bytes calldata relayerMsg ) external virtual override onlyIBC returns (bytes memory) { - (bool success, bytes memory acknowledgement) = address(this).call( - abi.encodeWithSelector( - this.execute.selector, packet, packet.data, relayer, relayerMsg - ) + (bool success, bytes memory returnData) = address(this).call( + abi.encodeCall(this.execute, (packet, relayer, relayerMsg)) ); + bytes memory acknowledgement = abi.decode(returnData, (bytes)); if (success) { // The acknowledgement may be asynchronous (forward/multiplex) if (acknowledgement.length == 0) { return ZkgmLib.ACK_EMPTY; - } else if ( + } + + // Special case where we should avoid the packet from being + // received entirely as it is only fillable by a market maker. + if ( keccak256(acknowledgement) == keccak256(ZkgmLib.ACK_ERR_ONLYMAKER) ) { - // Special case where we should avoid the packet from being - // received entirely as it is only fillable by a market maker. revert ZkgmLib.ErrOnlyMaker(); - } else { - return ZkgmLib.encodeAck( - Acknowledgement({ - tag: ZkgmLib.ACK_SUCCESS, - innerAck: acknowledgement - }) - ); } + + return ZkgmLib.encodeAck( + Acknowledgement({ + tag: ZkgmLib.ACK_SUCCESS, + innerAck: acknowledgement + }) + ); } else { return ZkgmLib.encodeAck( Acknowledgement({ @@ -433,14 +453,13 @@ contract Zkgm is IBCAppBase { function execute( IBCPacket calldata ibcPacket, address relayer, - bytes calldata relayerMsg, - bytes calldata rawZkgmPacket + bytes calldata relayerMsg ) public returns (bytes memory) { // Only callable through the onRecvPacket endpoint. if (msg.sender != address(this)) { revert ZkgmLib.ErrUnauthorized(); } - ZkgmPacket calldata zkgmPacket = ZkgmLib.decode(rawZkgmPacket); + ZkgmPacket calldata zkgmPacket = ZkgmLib.decode(ibcPacket.data); return executeInternal( ibcPacket, relayer, @@ -602,18 +621,25 @@ contract Zkgm is IBCAppBase { } } - function predictWrappedToken( + function internalPredictWrappedToken( uint256 path, - uint32 destinationChannel, + uint32 channel, bytes calldata token ) internal returns (address, bytes32) { - bytes32 wrappedTokenSalt = - keccak256(abi.encode(path, destinationChannel, token)); + bytes32 wrappedTokenSalt = keccak256(abi.encode(path, channel, token)); address wrappedToken = CREATE3.predictDeterministicAddress(wrappedTokenSalt); return (wrappedToken, wrappedTokenSalt); } + function predictWrappedToken( + uint256 path, + uint32 channel, + bytes calldata token + ) public returns (address, bytes32) { + return internalPredictWrappedToken(path, channel, token); + } + function executeFungibleAssetTransfer( IBCPacket calldata ibcPacket, address relayer, @@ -630,7 +656,8 @@ contract Zkgm is IBCAppBase { if (assetTransferPacket.askAmount > assetTransferPacket.sentAmount) { revert ZkgmLib.ErrInvalidAmount(); } - (address wrappedToken, bytes32 wrappedTokenSalt) = predictWrappedToken( + (address wrappedToken, bytes32 wrappedTokenSalt) = + internalPredictWrappedToken( path, ibcPacket.destinationChannel, assetTransferPacket.sentToken ); address askToken = address(bytes20(assetTransferPacket.askToken)); @@ -643,8 +670,11 @@ contract Zkgm is IBCAppBase { CREATE3.deployDeterministic( abi.encodePacked( type(ZkgmERC20).creationCode, - assetTransferPacket.sentSymbol, - assetTransferPacket.sentName + abi.encode( + assetTransferPacket.sentName, + assetTransferPacket.sentSymbol, + address(this) + ) ), wrappedTokenSalt ); @@ -662,7 +692,7 @@ contract Zkgm is IBCAppBase { if (assetTransferPacket.sentTokenPrefix == ibcPacket.sourceChannel) { channelBalance[ibcPacket.destinationChannel][askToken] -= - assetTransferPacket.askAmount; + assetTransferPacket.sentAmount; SafeERC20.safeTransfer( IERC20(askToken), receiver, assetTransferPacket.askAmount ); @@ -868,13 +898,29 @@ contract Zkgm is IBCAppBase { IBCPacket calldata ibcPacket, address relayer ) external virtual override onlyIBC { - ZkgmPacket calldata zkgmPacket = ZkgmLib.decode(ibcPacket.data); - timeoutInternal( - ibcPacket, - relayer, - zkgmPacket.salt, - ZkgmLib.decodeSyscall(zkgmPacket.syscall) - ); + bytes32 packetHash = IBCPacketLib.commitPacketMemory(ibcPacket); + IBCPacket memory parent = inFlightPacket[packetHash]; + // Specific case of forwarding where the failure is threaded back directly. + if (parent.timeoutTimestamp != 0 || parent.timeoutHeight != 0) { + ibcHandler.writeAcknowledgement( + parent, + ZkgmLib.encodeAck( + Acknowledgement({ + tag: ZkgmLib.ACK_FAILURE, + innerAck: ZkgmLib.ACK_EMPTY + }) + ) + ); + delete inFlightPacket[packetHash]; + } else { + ZkgmPacket calldata zkgmPacket = ZkgmLib.decode(ibcPacket.data); + timeoutInternal( + ibcPacket, + relayer, + zkgmPacket.salt, + ZkgmLib.decodeSyscall(zkgmPacket.syscall) + ); + } } function timeoutInternal( @@ -1047,4 +1093,8 @@ contract Zkgm is IBCAppBase { ) external virtual override onlyIBC { revert ZkgmLib.ErrInfiniteGame(); } + + function _authorizeUpgrade( + address newImplementation + ) internal override onlyOwner {} } diff --git a/evm/contracts/apps/ucs/03-zkgm/ZkgmERC20.sol b/evm/contracts/apps/ucs/03-zkgm/ZkgmERC20.sol index 95578bb842..41f78b00e3 100644 --- a/evm/contracts/apps/ucs/03-zkgm/ZkgmERC20.sol +++ b/evm/contracts/apps/ucs/03-zkgm/ZkgmERC20.sol @@ -9,8 +9,8 @@ contract ZkgmERC20 is ERC20, IZkgmERC20 { address public admin; uint8 private _decimals; - constructor(string memory n, string memory s) ERC20(n, s) { - admin = msg.sender; + constructor(string memory n, string memory s, address a) ERC20(n, s) { + admin = a; _decimals = 18; } diff --git a/evm/tests/src/app/Zkgm.t.sol b/evm/tests/src/app/Zkgm.t.sol index 605aab1ff4..1c7116c618 100644 --- a/evm/tests/src/app/Zkgm.t.sol +++ b/evm/tests/src/app/Zkgm.t.sol @@ -2,9 +2,106 @@ pragma solidity ^0.8.27; import "forge-std/Test.sol"; +import "@openzeppelin/proxy/ERC1967/ERC1967Proxy.sol"; + import "../../../contracts/apps/ucs/03-zkgm/Zkgm.sol"; contract ZkgmTests is Test { + UCS03Zkgm zkgm; + + function setUp() public { + UCS03Zkgm implementation = new UCS03Zkgm(); + ERC1967Proxy proxy = new ERC1967Proxy( + address(implementation), + abi.encodeWithSelector( + UCS03Zkgm.initialize.selector, + IIBCPacket(address(this)), + address(this) + ) + ); + zkgm = UCS03Zkgm(address(proxy)); + } + + function decodeFungible( + bytes calldata b + ) public returns (FungibleAssetTransferPacket calldata) { + return ZkgmLib.decodeFungibleAssetTransfer(b); + } + + function decodeSyscall( + bytes calldata b + ) public returns (SyscallPacket calldata) { + return ZkgmLib.decodeSyscall(b); + } + + function decode( + bytes calldata b + ) public returns (ZkgmPacket calldata) { + return ZkgmLib.decode(b); + } + + function check(ZkgmPacket calldata a, bytes calldata b) public { + bytes memory x = ZkgmLib.encode(a); + console.logBytes(x); + console.logBytes(b); + assertEq(keccak256(x), keccak256(b)); + } + + function test_sendZkgmPacket() public { + bytes memory rawFungible = abi.encode( + hex"153919669Edc8A5D0c8D1E4507c9CE60435A1177", + hex"153919669Edc8A5D0c8D1E4507c9CE60435A1177", + hex"d1B482D1B947A96E96C9b76d15De34f7f70A20A1", + uint256(5), + "ChainLink Token", + "LINK", + uint256(9), + hex"779877a7b0d9e8603169ddbd7836e478b4624789", + uint256(8), + false + ); + FungibleAssetTransferPacket memory transfer = + this.decodeFungible(rawFungible); + console.log(transfer.onlyMaker); + console.log(transfer.askAmount); + console.logBytes(rawFungible); + console.logBytes( + abi.encode( + ZkgmLib.ZKGM_VERSION_0, + ZkgmLib.SYSCALL_FUNGIBLE_ASSET_TRANSFER, + rawFungible + ) + ); + + bytes memory rawZk = + hex"000000000000000000000000000000000000000000000000000000000000000900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000034000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000002c00000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000240000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000000000000000000900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014153919669edc8a5d0c8d1e4507c9ce60435a11770000000000000000000000000000000000000000000000000000000000000000000000000000000000000014153919669edc8a5d0c8d1e4507c9ce60435a11770000000000000000000000000000000000000000000000000000000000000000000000000000000000000014779877a7b0d9e8603169ddbd7836e478b462478900000000000000000000000000000000000000000000000000000000000000000000000000000000000000044c494e4b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f436861696e4c696e6b20546f6b656e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000142A2868c2fa4F1480A22FfE960aA4dac57f2D7a44000000000000000000000000"; + + ZkgmPacket memory p = this.decode(rawZk); + SyscallPacket memory s = this.decodeSyscall(p.syscall); + FungibleAssetTransferPacket memory f = this.decodeFungible(s.packet); + + assertEq(f.onlyMaker, false); + assertEq(f.sentTokenPrefix, 0); + + (address wrapped,) = zkgm.predictWrappedToken( + 0, 5, hex"779877a7b0d9e8603169ddbd7836e478b4624789" + ); + + console.log(wrapped); + + zkgm.onRecvPacket( + IBCPacket({ + sourceChannel: 3, + destinationChannel: 5, + data: rawZk, + timeoutHeight: 0, + timeoutTimestamp: 0 + }), + address(this), + hex"" + ); + } + function test_lastChannelFromPathOk_1( uint32 a ) public { From 400b7af113f21b0b74340ea358f8d98eb26d9822 Mon Sep 17 00:00:00 2001 From: Hussein Ait Lahcen Date: Mon, 6 Jan 2025 12:29:27 +0100 Subject: [PATCH 5/5] feat(zkgm): deployment scripts --- .../src/content/docs/protocol/deployments.mdx | 4 +- evm/evm.nix | 20 ++- evm/scripts/Deploy.s.sol | 120 +++++++++++++++++- 3 files changed, 141 insertions(+), 3 deletions(-) diff --git a/docs/src/content/docs/protocol/deployments.mdx b/docs/src/content/docs/protocol/deployments.mdx index 3b5a3e835e..7396b631cf 100644 --- a/docs/src/content/docs/protocol/deployments.mdx +++ b/docs/src/content/docs/protocol/deployments.mdx @@ -18,6 +18,7 @@ Deployments of `ibc-union` on EVM chains. Solidity contract sources can be found | **apps** | UCS00 | [`0x92735254407859361265B51cDb76583ED7E3359b`](https://eth-holesky.blockscout.com/address/0x92735254407859361265B51cDb76583ED7E3359b) | | | UCS01 | [`0xdF48f737cc7eE649FC119B312932a9b99C40f417`](https://eth-holesky.blockscout.com/address/0xdF48f737cc7eE649FC119B312932a9b99C40f417) | | | UCS02 | [`0x8c5BB6EE0C679D605Fda89341148b9921C0d119c`](https://eth-holesky.blockscout.com/address/0x8c5BB6EE0C679D605Fda89341148b9921C0d119c) | +| | UCS03 | [`0x7B7872fEc715C787A1BE3f062AdeDc82b3B06144`](https://eth-holesky.blockscout.com/address/0x7B7872fEc715C787A1BE3f062AdeDc82b3B06144) | | **support** | Deployer | [`0xa3cd41bfF71AD19fDDfd901A9773C975A0404D97`](https://eth-holesky.blockscout.com/address/0xa3cd41bfF71AD19fDDfd901A9773C975A0404D97) | | | Sender | [`0x153919669Edc8A5D0c8D1E4507c9CE60435A1177`](https://eth-holesky.blockscout.com/address/0x153919669Edc8A5D0c8D1E4507c9CE60435A1177) | | | Multicall | [`0x64A764A734648fA636525C7e4b3cE38Ca256b647`](https://eth-holesky.blockscout.com/address/0x64A764A734648fA636525C7e4b3cE38Ca256b647) | @@ -32,6 +33,7 @@ Deployments of `ibc-union` on EVM chains. Solidity contract sources can be found | **apps** | UCS00 | [`0x271126f4F9B36CE16d9e2eF75691485ddCE11dB6`](https://eth-sepolia.blockscout.com/address/0x271126f4F9B36CE16d9e2eF75691485ddCE11dB6) | | | UCS01 | [`0xCFb741465F8e0AE9C62A548Fa85D312E6E5615Ba`](https://eth-sepolia.blockscout.com/address/0xCFb741465F8e0AE9C62A548Fa85D312E6E5615Ba) | | | UCS02 | [`0x12650fCccE6dB9E99CEE482490A5fAF248A62B22`](https://eth-sepolia.blockscout.com/address/0x12650fCccE6dB9E99CEE482490A5fAF248A62B22) | +| | UCS03 | [`0x84F074C15513F15baeA0fbEd3ec42F0Bd1fb3efa`](https://eth-sepolia.blockscout.com/address/0x84F074C15513F15baeA0fbEd3ec42F0Bd1fb3efa) | | **support** | Deployer | [`0xac6dBD360ABCfe0578e998D359d4F43a5A117219`](https://eth-sepolia.blockscout.com/address/0xac6dBD360ABCfe0578e998D359d4F43a5A117219) | | | Sender | [`0x153919669Edc8A5D0c8D1E4507c9CE60435A1177`](https://eth-sepolia.blockscout.com/address/0x153919669Edc8A5D0c8D1E4507c9CE60435A1177) | | | Multicall | [`0x6FD4bf9438fAC8C535218E79191594A879E47E96`](https://eth-sepolia.blockscout.com/address/0x6FD4bf9438fAC8C535218E79191594A879E47E96) | @@ -50,7 +52,7 @@ Deployments of `ibc-union` on CosmWasm (cosmos) chains. CosmWasm contract source | `berachain-light-client` | [`union1au6fkkfcgqc6vn8dz9tq2a6ma0vzwn2zfwwgpm7awpaeekw346uqjedtky`](https://explorer.testnet-9.union.build/union/cosmwasm/0/transactions?contract=union1au6fkkfcgqc6vn8dz9tq2a6ma0vzwn2zfwwgpm7awpaeekw346uqjedtky) | | `ucs00` | [`union194e3rchcaqyynwcj6qr6647ge7lheymrgkhq9tdknw35050ufhuqzqz2he`](https://explorer.testnet-9.union.build/union/cosmwasm/0/transactions?contract=union194e3rchcaqyynwcj6qr6647ge7lheymrgkhq9tdknw35050ufhuqzqz2he) | | ? | `union1au6fkkfcgqc6vn8dz9tq2a6ma0vzwn2zfwwgpm7awpaeekw346uqjedtky` | - + ### Stargaze Testnet elgafar-1 diff --git a/evm/evm.nix b/evm/evm.nix index 6ccdd03539..4240f37c34 100644 --- a/evm/evm.nix +++ b/evm/evm.nix @@ -213,7 +213,7 @@ _: { } { network = "holesky"; - rpc-url = "https://ethereum-holesky-rpc.publicnode.com"; + rpc-url = "https://1rpc.io/holesky"; private-key = ''"$1"''; extra-args = ''--verify --verifier etherscan --etherscan-api-key "$2"''; } @@ -693,6 +693,12 @@ _: { value = eth-deploy-single ({ kind = "EvmLens"; } // args); }) networks ) + // builtins.listToAttrs ( + builtins.map (args: { + name = "eth-deploy-${args.network}-ucs03"; + value = eth-deploy-single ({ kind = "UCS03"; } // args); + }) networks + ) // builtins.listToAttrs ( builtins.map (args: { name = "eth-deploy-${args.network}-multicall"; @@ -723,6 +729,18 @@ _: { ); }) networks ) + // builtins.listToAttrs ( + builtins.map (args: { + name = "eth-upgrade-${args.network}-ucs03"; + value = eth-upgrade ( + { + dry = false; + protocol = "UCS03"; + } + // args + ); + }) networks + ) // builtins.listToAttrs ( builtins.map (args: { name = "eth-upgrade-${args.network}-evm-lens-client"; diff --git a/evm/scripts/Deploy.s.sol b/evm/scripts/Deploy.s.sol index 2e2dd9c225..e09042cbd0 100644 --- a/evm/scripts/Deploy.s.sol +++ b/evm/scripts/Deploy.s.sol @@ -57,6 +57,7 @@ library Protocols { string constant UCS00 = "ucs00"; string constant UCS01 = "ucs01"; string constant UCS02 = "ucs02"; + string constant UCS03 = "ucs03"; function make( string memory protocol @@ -189,6 +190,21 @@ abstract contract UnionScript is UnionBase { ); } + function deployUCS03( + IBCHandler handler, + address owner + ) internal returns (UCS03Zkgm) { + return UCS03Zkgm( + deploy( + Protocols.make(Protocols.UCS03), + abi.encode( + address(new UCS03Zkgm()), + abi.encodeCall(UCS03Zkgm.initialize, (handler, owner)) + ) + ) + ); + } + function deployIBC( address owner ) @@ -296,6 +312,47 @@ contract DeployEvmLens is UnionScript { } } +contract DeployUCS03 is UnionScript { + using LibString for *; + + address immutable deployer; + address immutable sender; + + constructor() { + deployer = vm.envAddress("DEPLOYER"); + sender = vm.envAddress("SENDER"); + } + + function getDeployer() internal view override returns (Deployer) { + return Deployer(deployer); + } + + function getDeployed( + string memory salt + ) internal view returns (address) { + return CREATE3.predictDeterministicAddress( + keccak256(abi.encodePacked(sender.toHexString(), "/", salt)), + deployer + ); + } + + function run() public { + uint256 privateKey = vm.envUint("PRIVATE_KEY"); + + address owner = vm.addr(privateKey); + + address handler = getDeployed(IBC.BASED); + + vm.startBroadcast(privateKey); + + UCS03Zkgm zkgm = deployUCS03(IBCHandler(handler), owner); + + vm.stopBroadcast(); + + console.log("UCS03: ", address(zkgm)); + } +} + contract DeployIBC is UnionScript { Deployer immutable deployer; @@ -415,6 +472,7 @@ contract GetDeployed is Script { address ucs00 = getDeployed(Protocols.make(Protocols.UCS00)); address ucs01 = getDeployed(Protocols.make(Protocols.UCS01)); address ucs02 = getDeployed(Protocols.make(Protocols.UCS02)); + address ucs03 = getDeployed(Protocols.make(Protocols.UCS03)); console.log( string(abi.encodePacked("Multicall: ", multicall.toHexString())) @@ -437,6 +495,7 @@ contract GetDeployed is Script { console.log(string(abi.encodePacked("UCS00: ", ucs00.toHexString()))); console.log(string(abi.encodePacked("UCS01: ", ucs01.toHexString()))); console.log(string(abi.encodePacked("UCS02: ", ucs02.toHexString()))); + console.log(string(abi.encodePacked("UCS03: ", ucs03.toHexString()))); string memory impls = "base"; @@ -542,6 +601,24 @@ contract GetDeployed is Script { ); impls.serialize(ucs02.toHexString(), proxyUCS02); + string memory proxyUCS03 = "proxyUCS03"; + proxyUCS03.serialize( + "contract", + string( + "libs/@openzeppelin/proxy/ERC1967/ERC1967Proxy.sol:ERC1967Proxy" + ) + ); + proxyUCS03 = proxyUCS03.serialize( + "args", + abi.encode( + implOf(ucs03), + abi.encodeCall( + UCS03Zkgm.initialize, (IIBCPacket(handler), sender) + ) + ) + ); + impls.serialize(ucs03.toHexString(), proxyUCS03); + string memory implMulticall = "implMulticall"; implMulticall.serialize( "contract", string("contracts/Multicall.sol:Multicall") @@ -594,12 +671,53 @@ contract GetDeployed is Script { "contract", string("contracts/apps/ucs/02-nft/NFT.sol:UCS02NFT") ); implUCS02 = implUCS02.serialize("args", bytes(hex"")); - impls = impls.serialize(implOf(ucs01).toHexString(), implUCS02); + impls = impls.serialize(implOf(ucs02).toHexString(), implUCS02); + + string memory implUCS03 = "implUCS03"; + implUCS03.serialize( + "contract", string("contracts/apps/ucs/03-zkgm/Zkgm.sol:UCS03Zkgm") + ); + implUCS03 = implUCS03.serialize("args", bytes(hex"")); + impls = impls.serialize(implOf(ucs03).toHexString(), implUCS03); impls.write(vm.envString("OUTPUT")); } } +contract UpgradeUCS03 is Script { + using LibString for *; + + address immutable deployer; + address immutable sender; + uint256 immutable privateKey; + + constructor() { + deployer = vm.envAddress("DEPLOYER"); + sender = vm.envAddress("SENDER"); + privateKey = vm.envUint("PRIVATE_KEY"); + } + + function getDeployed( + string memory salt + ) internal view returns (address) { + return CREATE3.predictDeterministicAddress( + keccak256(abi.encodePacked(sender.toHexString(), "/", salt)), + deployer + ); + } + + function run() public { + address ucs03 = getDeployed(Protocols.make(Protocols.UCS03)); + + console.log(string(abi.encodePacked("UCS03: ", ucs03.toHexString()))); + + vm.startBroadcast(privateKey); + address newImplementation = address(new UCS03Zkgm()); + UCS03Zkgm(ucs03).upgradeToAndCall(newImplementation, new bytes(0)); + vm.stopBroadcast(); + } +} + contract UpgradeUCS00 is Script { using LibString for *;