UCS03Zkgm

Description:

Multi-signature wallet contract requiring multiple confirmations for transaction execution.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "contracts/apps/ucs/03-zkgm/Zkgm.sol": {
      "content": "pragma solidity ^0.8.27;

import "./Send.sol";
import "./TokenOrder.sol";
import "./Store.sol";

import "../../../ProxyAccount.sol";

// Dummy lib to ensure all types are exported
contract AbiExport {
    function ensureExported(
        ZkgmPacket calldata,
        Instruction calldata,
        Forward calldata,
        Call calldata,
        Batch calldata,
        TokenOrderV1 calldata,
        Ack calldata,
        BatchAck calldata,
        TokenOrderAck calldata,
        TokenOrderV2 calldata,
        TokenMetadata calldata,
        SolverMetadata calldata
    ) public {}

    function ensureCreateWrappedTokenExported(
        uint256 path,
        uint32 channelId,
        bytes calldata baseToken,
        address quoteToken,
        bytes calldata metadata,
        uint8 kind
    ) public {
        emit ZkgmLib.CreateWrappedToken(
            path, channelId, baseToken, quoteToken, metadata, kind
        );
    }

    function ensureCreateProxyAccountExported(
        uint256 path,
        uint32 channelId,
        bytes calldata owner,
        address proxyAccount
    ) public {
        emit ZkgmLib.CreateProxyAccount(path, channelId, owner, proxyAccount);
    }
}

function passthrough(
    address impl
) {
    assembly {
        calldatacopy(0, 0, calldatasize())
        let result := delegatecall(gas(), impl, 0, calldatasize(), 0, 0)
        returndatacopy(0, 0, returndatasize())
        switch result
        case 0 { revert(0, returndatasize()) }
        default { return(0, returndatasize()) }
    }
}

contract UCS03Zkgm is
    IBCAppBase,
    Initializable,
    UUPSUpgradeable,
    AccessManagedUpgradeable,
    PausableUpgradeable,
    TokenBucket,
    Versioned,
    IZkgm,
    UCS03ZkgmStore
{
    using ZkgmLib for *;
    using LibString for *;
    using LibBytes for *;
    using SafeERC20 for *;
    using Address for *;
    using LibCall for *;

    uint256 public constant EXEC_MIN_GAS = 50_000;

    IIBCModulePacket public immutable IBC_HANDLER;
    address public immutable SEND_IMPL;
    address public immutable FAO_IMPL;
    address public immutable ACCOUNT_IMPL;

    constructor(
        IIBCModulePacket _ibcHandler,
        UCS03ZkgmSendImpl _sendImpl,
        UCS03ZkgmTokenOrderImpl _faoImpl,
        ProxyAccount _accountImpl
    ) {
        _disableInitializers();
        IBC_HANDLER = _ibcHandler;
        SEND_IMPL = address(_sendImpl);
        FAO_IMPL = address(_faoImpl);
        ACCOUNT_IMPL = address(_accountImpl);
    }

    function initialize(
        address _authority
    ) public initializer {
        __AccessManaged_init(_authority);
        __UUPSUpgradeable_init();
        __Pausable_init();
    }

    function ibcAddress() public view virtual override returns (address) {
        return address(IBC_HANDLER);
    }

    function predictWrappedToken(
        uint256 path,
        uint32 channel,
        bytes calldata token
    ) public returns (address, bytes32) {
        passthrough(address(SEND_IMPL));
    }

    function predictWrappedTokenV2(
        uint256 path,
        uint32 channel,
        bytes calldata token,
        TokenMetadata calldata metadata
    ) external returns (address, bytes32) {
        passthrough(address(SEND_IMPL));
    }

    function predictWrappedTokenFromMetadataImageV2(
        uint256 path,
        uint32 channel,
        bytes calldata token,
        bytes32 metadataImage
    ) external returns (address, bytes32) {
        passthrough(address(SEND_IMPL));
    }

    function predictProxyAccount(
        uint256 path,
        uint32 channel,
        bytes calldata sender
    ) external returns (address, bytes32) {
        passthrough(address(SEND_IMPL));
    }

    function send(
        uint32 channelId,
        uint64 timeoutHeight,
        uint64 timeoutTimestamp,
        bytes32 salt,
        Instruction calldata instruction
    ) public payable whenNotPaused {
        passthrough(address(SEND_IMPL));
    }

    function onRecvIntentPacket(
        address caller,
        IBCPacket calldata packet,
        address relayer,
        bytes calldata relayerMsg
    ) external virtual override onlyIBC whenNotPaused returns (bytes memory) {
        return _processReceive(caller, packet, relayer, relayerMsg, true);
    }

    function onRecvPacket(
        address caller,
        IBCPacket calldata packet,
        address relayer,
        bytes calldata relayerMsg
    ) external virtual override onlyIBC whenNotPaused returns (bytes memory) {
        return _processReceive(caller, packet, relayer, relayerMsg, false);
    }

    function _processReceive(
        address caller,
        IBCPacket calldata packet,
        address relayer,
        bytes calldata relayerMsg,
        bool intent
    ) internal returns (bytes memory) {
        (bool success, bytes memory returnData) = address(this).call(
            abi.encodeCall(
                this.execute, (caller, packet, relayer, relayerMsg, intent)
            )
        );
        // Avoid gas-starvation trick. Enforce a minimum for griefing relayers.
        // See: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/bd325d56b4c62c9c5c1aff048c37c6bb18ac0290/contracts/metatx/MinimalForwarder.sol#L58-L68
        if (gasleft() <= EXEC_MIN_GAS / 63) {
            assembly {
                invalid()
            }
        }
        if (success) {
            bytes memory acknowledgement = abi.decode(returnData, (bytes));
            // The acknowledgement may be asynchronous (forward/call).
            if (acknowledgement.length == 0) {
                return ZkgmLib.ACK_EMPTY;
            }

            // Special case where we should avoid the packet from being
            // received entirely as it is only fillable by a market maker.
            if (
                EfficientHashLib.hash(acknowledgement)
                    == ZkgmLib.ACK_ERR_ONLYMAKER_HASH
            ) {
                revert ZkgmLib.ErrOnlyMaker();
            }

            return ZkgmLib.encodeAck(
                Ack({tag: ZkgmLib.ACK_SUCCESS, innerAck: acknowledgement})
            );
        } else {
            return ZkgmLib.encodeAck(
                Ack({tag: ZkgmLib.ACK_FAILURE, innerAck: ZkgmLib.ACK_EMPTY})
            );
        }
    }

    function execute(
        address caller,
        IBCPacket calldata ibcPacket,
        address relayer,
        bytes calldata relayerMsg,
        bool intent
    ) public returns (bytes memory) {
        // Only callable through the onRecvPacket endpoint.
        if (msg.sender != address(this)) {
            revert ZkgmLib.ErrUnauthorized();
        }
        ZkgmPacket calldata zkgmPacket = ZkgmLib.decode(ibcPacket.data);
        return _executeInternal(
            caller,
            ibcPacket,
            relayer,
            relayerMsg,
            zkgmPacket.salt,
            zkgmPacket.path,
            zkgmPacket.instruction,
            intent
        );
    }

    function _executeInternal(
        address caller,
        IBCPacket calldata ibcPacket,
        address relayer,
        bytes calldata relayerMsg,
        bytes32 salt,
        uint256 path,
        Instruction calldata instruction,
        bool intent
    ) internal returns (bytes memory) {
        if (instruction.isInst(ZkgmLib.OP_TOKEN_ORDER, ZkgmLib.INSTR_VERSION_1))
        {
            TokenOrderV1 calldata order =
                ZkgmLib.decodeTokenOrderV1(instruction.operand);
            bytes memory rawResult = _callFAOImpl(
                abi.encodeCall(
                    UCS03ZkgmTokenOrderImpl.executeTokenOrderV1,
                    (
                        caller,
                        ibcPacket,
                        relayer,
                        relayerMsg,
                        path,
                        order,
                        intent
                    )
                )
            );
            return abi.decode(rawResult, (bytes));
        } else if (
            instruction.isInst(ZkgmLib.OP_TOKEN_ORDER, ZkgmLib.INSTR_VERSION_2)
        ) {
            TokenOrderV2 calldata order =
                ZkgmLib.decodeTokenOrderV2(instruction.operand);
            bytes memory rawResult = _callFAOImpl(
                abi.encodeCall(
                    UCS03ZkgmTokenOrderImpl.executeTokenOrderV2,
                    (
                        caller,
                        ibcPacket,
                        relayer,
                        relayerMsg,
                        path,
                        order,
                        intent
                    )
                )
            );
            return abi.decode(rawResult, (bytes));
        } else if (
            instruction.isInst(ZkgmLib.OP_BATCH, ZkgmLib.INSTR_VERSION_0)
        ) {
            return _executeBatch(
                caller,
                ibcPacket,
                relayer,
                relayerMsg,
                salt,
                path,
                ZkgmLib.decodeBatch(instruction.operand),
                intent
            );
        } else if (
            instruction.isInst(ZkgmLib.OP_FORWARD, ZkgmLib.INSTR_VERSION_0)
        ) {
            return _executeForward(
                ibcPacket,
                relayer,
                relayerMsg,
                salt,
                path,
                instruction.version,
                ZkgmLib.decodeForward(instruction.operand),
                intent
            );
        } else if (instruction.isInst(ZkgmLib.OP_CALL, ZkgmLib.INSTR_VERSION_0))
        {
            return _executeCall(
                caller,
                ibcPacket,
                relayer,
                relayerMsg,
                path,
                salt,
                ZkgmLib.decodeCall(instruction.operand),
                intent
            );
        } else {
            revert ZkgmLib.ErrUnknownOpcode();
        }
    }

    function _executeBatch(
        address caller,
        IBCPacket calldata ibcPacket,
        address relayer,
        bytes calldata relayerMsg,
        bytes32 salt,
        uint256 path,
        Batch calldata batch,
        bool intent
    ) internal returns (bytes memory) {
        uint256 l = batch.instructions.length;
        bytes[] memory acks = new bytes[](l);
        for (uint256 i = 0; i < l; i++) {
            Instruction calldata instruction = batch.instructions[i];
            if (!ZkgmLib.isAllowedBatchInstruction(instruction.opcode)) {
                revert ZkgmLib.ErrInvalidBatchInstruction();
            }
            acks[i] = _executeInternal(
                caller,
                ibcPacket,
                relayer,
                relayerMsg,
                ZkgmLib.deriveBatchSalt(i, salt),
                path,
                instruction,
                intent
            );
            // We should have the guarantee that the acks are non empty because
            // the only instructions allowed in a batch are call and
            // fungibleAssetOrder which returns non-empty acks only.
            if (acks[i].length == 0) {
                revert ZkgmLib.ErrBatchMustBeSync();
            } else if (
                EfficientHashLib.hash(acks[i]) == ZkgmLib.ACK_ERR_ONLYMAKER_HASH
            ) {
                return acks[i];
            }
        }
        return ZkgmLib.encodeBatchAck(BatchAck({acknowledgements: acks}));
    }

    function _executeForward(
        IBCPacket calldata ibcPacket,
        address relayer,
        bytes calldata relayerMsg,
        bytes32 salt,
        uint256 path,
        uint8 version,
        Forward calldata forward,
        bool intent
    ) internal returns (bytes memory) {
        if (!ZkgmLib.isAllowedForwardInstruction(forward.instruction.opcode)) {
            revert ZkgmLib.ErrInvalidForwardInstruction();
        }
        // We cannot allow market makers to fill packets containing forward
        // instruction. This would allow them to submit of a proof and fill via the
        // protocol on destination for a fake forward.

        // Instead, they must first fill on destination the orders, awaits finality
        // to settle the forward, then cascade acknowledge.
        if (intent) {
            return ZkgmLib.ACK_ERR_ONLYMAKER;
        }

        (uint256 tailPath, uint32 previousDestinationChannelId) =
            ZkgmLib.dequeueChannelFromPath(forward.path);
        (uint256 continuationPath, uint32 nextSourceChannelId) =
            ZkgmLib.dequeueChannelFromPath(tailPath);
        if (ibcPacket.destinationChannelId != previousDestinationChannelId) {
            revert ZkgmLib.ErrInvalidForwardDestinationChannelId();
        }
        Instruction memory nextInstruction;
        if (continuationPath == 0) {
            // If we are done hopping, the sub-instruction is dispatched for execution.
            nextInstruction = forward.instruction;
        } else {
            // If we are not done, the continuation path is used and the forward is re-executed.
            nextInstruction = Instruction({
                version: version,
                opcode: ZkgmLib.OP_FORWARD,
                operand: ZkgmLib.encodeForward(
                    Forward({
                        path: continuationPath,
                        timeoutHeight: forward.timeoutHeight,
                        timeoutTimestamp: forward.timeoutTimestamp,
                        instruction: forward.instruction
                    })
                )
            });
        }
        IBCPacket memory sentPacket = IBC_HANDLER.sendPacket(
            nextSourceChannelId,
            forward.timeoutHeight,
            forward.timeoutTimestamp,
            ZkgmLib.encode(
                ZkgmPacket({
                    salt: ZkgmLib.deriveForwardSalt(salt),
                    path: ZkgmLib.updateChannelPath(
                        ZkgmLib.updateChannelPath(
                            path, previousDestinationChannelId
                        ),
                        nextSourceChannelId
                    ),
                    instruction: nextInstruction
                })
            )
        );
        // Guaranteed to be unique by the above sendPacket
        bytes32 commitmentKey = IBCCommitment.batchPacketsCommitmentKey(
            IBCPacketLib.commitPacket(sentPacket)
        );
        inFlightPacket[commitmentKey] = ibcPacket;
        return ZkgmLib.ACK_EMPTY;
    }

    function _deployProxyAccount(
        address targetContract,
        uint256 path,
        uint32 channelId,
        bytes calldata sender
    ) internal {
        (bytes32 proxySalt, address proxyAccount) =
            _predictProxyAccount(path, channelId, sender);
        if (targetContract == proxyAccount && !ZkgmLib.isDeployed(proxyAccount))
        {
            CREATE3.deployDeterministic(
                abi.encodePacked(
                    type(ERC1967Proxy).creationCode,
                    abi.encode(
                        ACCOUNT_IMPL,
                        abi.encodeCall(
                            ProxyAccount.initializeRemote,
                            (address(this), path, channelId, sender)
                        )
                    )
                ),
                proxySalt
            );
            emit ZkgmLib.CreateProxyAccount(
                path, channelId, sender, proxyAccount
            );
        }
    }

    function _executeCall(
        address caller,
        IBCPacket calldata ibcPacket,
        address relayer,
        bytes calldata relayerMsg,
        uint256 path,
        bytes32 salt,
        Call calldata call,
        bool intent
    ) internal returns (bytes memory) {
        address contractAddress = address(bytes20(call.contractAddress));
        if (!call.eureka) {
            _deployProxyAccount(
                contractAddress,
                path,
                ibcPacket.destinationChannelId,
                call.sender
            );
            if (intent) {
                IZkgmable(contractAddress).onIntentZkgm(
                    caller,
                    path,
                    ibcPacket.sourceChannelId,
                    ibcPacket.destinationChannelId,
                    call.sender,
                    call.contractCalldata,
                    relayer,
                    relayerMsg
                );
            } else {
                IZkgmable(contractAddress).onZkgm(
                    caller,
                    path,
                    ibcPacket.sourceChannelId,
                    ibcPacket.destinationChannelId,
                    call.sender,
                    call.contractCalldata,
                    relayer,
                    relayerMsg
                );
            }
            return abi.encode(ZkgmLib.ACK_SUCCESS);
        } else {
            IBCPacket memory callIbcPacket = IBCPacket({
                sourceChannelId: ibcPacket.sourceChannelId,
                destinationChannelId: ibcPacket.destinationChannelId,
                data: ZkgmLib.encodeCallCalldata(
                    path, call.sender, call.contractCalldata
                ),
                timeoutHeight: ibcPacket.timeoutHeight,
                timeoutTimestamp: ibcPacket.timeoutTimestamp
            });
            bytes memory acknowledgement;
            if (intent) {
                acknowledgement = IIBCModuleRecv(contractAddress)
                    .onRecvIntentPacket(caller, callIbcPacket, relayer, relayerMsg);
            } else {
                acknowledgement = IIBCModuleRecv(contractAddress).onRecvPacket(
                    caller, callIbcPacket, relayer, relayerMsg
                );
            }
            if (acknowledgement.length == 0) {
                revert ZkgmLib.ErrAsyncCallUnsupported();
            }
            return acknowledgement;
        }
    }

    function onAcknowledgementPacket(
        address caller,
        IBCPacket calldata ibcPacket,
        bytes calldata ack,
        address relayer
    ) external virtual override onlyIBC whenNotPaused {
        ZkgmPacket calldata zkgmPacket = ZkgmLib.decode(ibcPacket.data);
        if (ZkgmLib.isForwardedPacket(zkgmPacket.salt)) {
            bytes32 packetHash = IBCPacketLib.commitPacket(ibcPacket);
            IBCPacket memory parent = inFlightPacket[packetHash];
            if (parent.timeoutTimestamp != 0 || parent.timeoutHeight != 0) {
                delete inFlightPacket[packetHash];
                IBC_HANDLER.writeAcknowledgement(parent, ack);
                return;
            }
        }
        Ack calldata zkgmAck = ZkgmLib.decodeAck(ack);
        _acknowledgeInternal(
            caller,
            ibcPacket,
            relayer,
            zkgmPacket.path,
            zkgmPacket.salt,
            zkgmPacket.instruction,
            zkgmAck.tag == ZkgmLib.ACK_SUCCESS,
            zkgmAck.innerAck
        );
    }

    function _callFAOImpl(
        bytes memory data
    ) internal returns (bytes memory) {
        return FAO_IMPL.delegateCallContract(data);
    }

    function _acknowledgeInternal(
        address caller,
        IBCPacket calldata ibcPacket,
        address relayer,
        uint256 path,
        bytes32 salt,
        Instruction calldata instruction,
        bool successful,
        bytes calldata ack
    ) internal {
        if (instruction.isInst(ZkgmLib.OP_TOKEN_ORDER, ZkgmLib.INSTR_VERSION_1))
        {
            TokenOrderV1 calldata order =
                ZkgmLib.decodeTokenOrderV1(instruction.operand);
            _callFAOImpl(
                abi.encodeCall(
                    UCS03ZkgmTokenOrderImpl.acknowledgeTokenOrderV1,
                    (ibcPacket, relayer, path, salt, order, successful, ack)
                )
            );
        } else if (
            instruction.isInst(ZkgmLib.OP_TOKEN_ORDER, ZkgmLib.INSTR_VERSION_2)
        ) {
            TokenOrderV2 calldata order =
                ZkgmLib.decodeTokenOrderV2(instruction.operand);
            _callFAOImpl(
                abi.encodeCall(
                    UCS03ZkgmTokenOrderImpl.acknowledgeTokenOrderV2,
                    (ibcPacket, relayer, path, salt, order, successful, ack)
                )
            );
        } else if (
            instruction.isInst(ZkgmLib.OP_BATCH, ZkgmLib.INSTR_VERSION_0)
        ) {
            _acknowledgeBatch(
                caller,
                ibcPacket,
                relayer,
                path,
                salt,
                ZkgmLib.decodeBatch(instruction.operand),
                successful,
                ack
            );
        } else if (
            instruction.isInst(ZkgmLib.OP_FORWARD, ZkgmLib.INSTR_VERSION_0)
        ) {
            _acknowledgeForward(
                caller,
                ibcPacket,
                relayer,
                salt,
                ZkgmLib.decodeForward(instruction.operand),
                successful,
                ack
            );
        } else if (instruction.isInst(ZkgmLib.OP_CALL, ZkgmLib.INSTR_VERSION_0))
        {
            _acknowledgeCall(
                caller,
                ibcPacket,
                relayer,
                path,
                salt,
                ZkgmLib.decodeCall(instruction.operand),
                successful,
                ack
            );
        } else {
            revert ZkgmLib.ErrUnknownOpcode();
        }
    }

    function _acknowledgeBatch(
        address caller,
        IBCPacket calldata ibcPacket,
        address relayer,
        uint256 path,
        bytes32 salt,
        Batch calldata batch,
        bool successful,
        bytes calldata ack
    ) internal {
        uint256 l = batch.instructions.length;
        BatchAck 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(
                caller,
                ibcPacket,
                relayer,
                path,
                ZkgmLib.deriveBatchSalt(i, salt),
                batch.instructions[i],
                successful,
                syscallAck
            );
        }
    }

    function _acknowledgeForward(
        address caller,
        IBCPacket calldata ibcPacket,
        address relayer,
        bytes32 salt,
        Forward calldata forward,
        bool successful,
        bytes calldata ack
    ) internal {
        _acknowledgeInternal(
            caller,
            ibcPacket,
            relayer,
            forward.path,
            salt,
            forward.instruction,
            successful,
            ack
        );
    }

    function _acknowledgeCall(
        address caller,
        IBCPacket calldata ibcPacket,
        address relayer,
        uint256 path,
        bytes32 salt,
        Call calldata call,
        bool successful,
        bytes calldata ack
    ) internal {
        if (successful && call.eureka) {
            IBCPacket memory callIbcPacket = IBCPacket({
                sourceChannelId: ibcPacket.sourceChannelId,
                destinationChannelId: ibcPacket.destinationChannelId,
                data: ZkgmLib.encodeCallCalldata(
                    path, call.sender, call.contractCalldata
                ),
                timeoutHeight: ibcPacket.timeoutHeight,
                timeoutTimestamp: ibcPacket.timeoutTimestamp
            });
            IIBCModule(address(bytes20(call.sender))).onAcknowledgementPacket(
                caller, callIbcPacket, ack, relayer
            );
        }
    }

    function onTimeoutPacket(
        address caller,
        IBCPacket calldata ibcPacket,
        address relayer
    ) external virtual override onlyIBC whenNotPaused {
        ZkgmPacket calldata zkgmPacket = ZkgmLib.decode(ibcPacket.data);
        if (ZkgmLib.isForwardedPacket(zkgmPacket.salt)) {
            bytes32 packetHash = IBCPacketLib.commitPacket(ibcPacket);
            IBCPacket memory parent = inFlightPacket[packetHash];
            if (parent.timeoutTimestamp != 0 || parent.timeoutHeight != 0) {
                // If the forwarded packet times out, we write a failure ACK for
                // the parent such that we ensure refund happens as the parent
                // wouldn't timeout itself.
                delete inFlightPacket[packetHash];
                IBC_HANDLER.writeAcknowledgement(
                    parent,
                    ZkgmLib.encodeAck(
                        Ack({
                            tag: ZkgmLib.ACK_FAILURE,
                            innerAck: ZkgmLib.ACK_EMPTY
                        })
                    )
                );
                return;
            }
        }
        _timeoutInternal(
            caller, ibcPacket, relayer, zkgmPacket.path, zkgmPacket.instruction
        );
    }

    function _timeoutInternal(
        address caller,
        IBCPacket calldata ibcPacket,
        address relayer,
        uint256 path,
        Instruction calldata instruction
    ) internal {
        if (instruction.isInst(ZkgmLib.OP_TOKEN_ORDER, ZkgmLib.INSTR_VERSION_1))
        {
            TokenOrderV1 calldata order =
                ZkgmLib.decodeTokenOrderV1(instruction.operand);
            _callFAOImpl(
                abi.encodeCall(
                    UCS03ZkgmTokenOrderImpl.timeoutTokenOrderV1,
                    (ibcPacket, path, order)
                )
            );
        } else if (
            instruction.isInst(ZkgmLib.OP_TOKEN_ORDER, ZkgmLib.INSTR_VERSION_2)
        ) {
            TokenOrderV2 calldata order =
                ZkgmLib.decodeTokenOrderV2(instruction.operand);
            _callFAOImpl(
                abi.encodeCall(
                    UCS03ZkgmTokenOrderImpl.timeoutTokenOrderV2,
                    (ibcPacket, path, order)
                )
            );
        } else if (
            instruction.isInst(ZkgmLib.OP_BATCH, ZkgmLib.INSTR_VERSION_0)
        ) {
            _timeoutBatch(
                caller,
                ibcPacket,
                relayer,
                path,
                ZkgmLib.decodeBatch(instruction.operand)
            );
        } else if (
            instruction.isInst(ZkgmLib.OP_FORWARD, ZkgmLib.INSTR_VERSION_0)
        ) {
            _timeoutForward(
                caller,
                ibcPacket,
                relayer,
                ZkgmLib.decodeForward(instruction.operand)
            );
        } else if (instruction.isInst(ZkgmLib.OP_CALL, ZkgmLib.INSTR_VERSION_0))
        {
            _timeoutCall(
                caller,
                ibcPacket,
                relayer,
                path,
                ZkgmLib.decodeCall(instruction.operand)
            );
        } else {
            revert ZkgmLib.ErrUnknownOpcode();
        }
    }

    function _timeoutBatch(
        address caller,
        IBCPacket calldata ibcPacket,
        address relayer,
        uint256 path,
        Batch calldata batch
    ) internal {
        uint256 l = batch.instructions.length;
        for (uint256 i = 0; i < l; i++) {
            _timeoutInternal(
                caller, ibcPacket, relayer, path, batch.instructions[i]
            );
        }
    }

    function _timeoutForward(
        address caller,
        IBCPacket calldata ibcPacket,
        address relayer,
        Forward calldata forward
    ) internal {
        _timeoutInternal(
            caller, ibcPacket, relayer, forward.path, forward.instruction
        );
    }

    function _timeoutCall(
        address caller,
        IBCPacket calldata ibcPacket,
        address relayer,
        uint256 path,
        Call calldata call
    ) internal {
        if (call.eureka) {
            IBCPacket memory callIbcPacket = IBCPacket({
                sourceChannelId: ibcPacket.sourceChannelId,
                destinationChannelId: ibcPacket.destinationChannelId,
                data: ZkgmLib.encodeCallCalldata(
                    path, call.contractAddress, call.contractCalldata
                ),
                timeoutHeight: ibcPacket.timeoutHeight,
                timeoutTimestamp: ibcPacket.timeoutTimestamp
            });
            IIBCModule(address(bytes20(call.sender))).onTimeoutPacket(
                caller, callIbcPacket, relayer
            );
        }
    }

    function onChanOpenInit(
        address,
        uint32,
        uint32,
        string calldata version,
        address
    ) external virtual override onlyIBC {
        if (EfficientHashLib.hash(bytes(version)) != ZkgmLib.IBC_VERSION) {
            revert ZkgmLib.ErrInvalidIBCVersion();
        }
    }

    function onChanOpenTry(
        address,
        uint32,
        uint32,
        uint32,
        string calldata version,
        string calldata counterpartyVersion,
        address
    ) external virtual override onlyIBC {
        if (EfficientHashLib.hash(bytes(version)) != ZkgmLib.IBC_VERSION) {
            revert ZkgmLib.ErrInvalidIBCVersion();
        }
        if (
            EfficientHashLib.hash(bytes(counterpartyVersion))
                != ZkgmLib.IBC_VERSION
        ) {
            revert ZkgmLib.ErrInvalidIBCVersion();
        }
    }

    function onChanOpenAck(
        address,
        uint32 channelId,
        uint32,
        string calldata counterpartyVersion,
        address
    ) external virtual override onlyIBC {
        if (
            EfficientHashLib.hash(bytes(counterpartyVersion))
                != ZkgmLib.IBC_VERSION
        ) {
            revert ZkgmLib.ErrInvalidIBCVersion();
        }
    }

    function onChanOpenConfirm(
        address,
        uint32 channelId,
        address
    ) external virtual override onlyIBC {}

    function onChanCloseInit(
        address,
        uint32,
        address
    ) external virtual override onlyIBC {
        revert ZkgmLib.ErrInfiniteGame();
    }

    function onChanCloseConfirm(
        address,
        uint32,
        address
    ) external virtual override onlyIBC {
        revert ZkgmLib.ErrInfiniteGame();
    }

    function _authorizeUpgrade(
        address newImplementation
    ) internal override restricted {}

    function pause() public restricted {
        _pause();
    }

    function unpause() public restricted {
        _unpause();
    }

    function setBucketConfig(
        address token,
        uint256 capacity,
        uint256 refillRate,
        bool reset
    ) public restricted {
        _setBucketConfig(token, capacity, refillRate, reset);
    }

    function migrateV1ToV2(
        V1ToV2Migration[] calldata balanceMigrations,
        V1ToV2WrappedTokenMigration[] calldata wrappedMigrations
    ) public restricted {
        for (uint256 i = 0; i < balanceMigrations.length; i++) {
            V1ToV2Migration calldata migration = balanceMigrations[i];
            uint256 balance = _deprecated_channelBalanceV1[migration.channelId][migration
                .path][migration.baseToken];
            if (balance == 0) {
                revert("no balance");
            }
            _deprecated_channelBalanceV1[migration.channelId][migration.path][migration
                .baseToken] = 0;
            _increaseOutstandingV2(
                migration.channelId,
                migration.path,
                migration.baseToken,
                migration.quoteToken,
                balance
            );
        }
        for (uint256 i = 0; i < wrappedMigrations.length; i++) {
            V1ToV2WrappedTokenMigration calldata migration =
                wrappedMigrations[i];
            emit ZkgmLib.CreateWrappedToken(
                migration.path,
                migration.channelId,
                migration.baseToken,
                migration.quoteToken,
                hex"",
                ZkgmLib.WRAPPED_TOKEN_KIND_PROTOCOL
            );
        }
    }

    receive() external payable {}
}
"
    },
    "contracts/apps/ucs/03-zkgm/Send.sol": {
      "content": "pragma solidity ^0.8.27;

import "./Store.sol";

contract UCS03ZkgmSendImpl is Versioned, UCS03ZkgmStore {
    using ZkgmLib for *;
    using LibString for *;
    using LibBytes for *;
    using SafeERC20 for *;
    using Address for *;
    using LibCall for *;

    IIBCModulePacket public immutable IBC_HANDLER;
    IWETH public immutable WETH;
    ZkgmERC20 public immutable ERC20_IMPL;
    bytes32 public immutable NATIVE_TOKEN_NAME_HASH;
    bytes32 public immutable NATIVE_TOKEN_SYMBOL_HASH;
    uint8 public immutable NATIVE_TOKEN_DECIMALS;

    constructor(
        IIBCModulePacket _ibcHandler,
        IWETH _weth,
        ZkgmERC20 _erc20Impl,
        string memory _nativeTokenName,
        string memory _nativeTokenSymbol,
        uint8 _nativeTokenDecimals
    ) {
        IBC_HANDLER = _ibcHandler;
        WETH = _weth;
        ERC20_IMPL = _erc20Impl;
        NATIVE_TOKEN_NAME_HASH = keccak256(bytes(_nativeTokenName));
        NATIVE_TOKEN_SYMBOL_HASH = keccak256(bytes(_nativeTokenSymbol));
        NATIVE_TOKEN_DECIMALS = _nativeTokenDecimals;
    }

    function send(
        uint32 channelId,
        uint64 timeoutHeight,
        uint64 timeoutTimestamp,
        bytes32 salt,
        Instruction calldata instruction
    ) public payable {
        _verifyInternal(channelId, 0, instruction);
        IBC_HANDLER.sendPacket(
            channelId,
            timeoutHeight,
            timeoutTimestamp,
            ZkgmLib.encode(
                ZkgmPacket({
                    salt: EfficientHashLib.hash(abi.encodePacked(msg.sender, salt)),
                    path: 0,
                    instruction: instruction
                })
            )
        );
    }

    function _verifyInternal(
        uint32 channelId,
        uint256 path,
        Instruction calldata instruction
    ) internal {
        if (instruction.isInst(ZkgmLib.OP_TOKEN_ORDER, ZkgmLib.INSTR_VERSION_1))
        {
            TokenOrderV1 calldata order =
                ZkgmLib.decodeTokenOrderV1(instruction.operand);
            _verifyTokenOrderV1(channelId, path, order);
        } else if (
            instruction.isInst(ZkgmLib.OP_TOKEN_ORDER, ZkgmLib.INSTR_VERSION_2)
        ) {
            TokenOrderV2 calldata order =
                ZkgmLib.decodeTokenOrderV2(instruction.operand);
            _verifyTokenOrderV2(channelId, path, order);
        } else if (
            instruction.isInst(ZkgmLib.OP_BATCH, ZkgmLib.INSTR_VERSION_0)
        ) {
            _verifyBatch(
                channelId, path, ZkgmLib.decodeBatch(instruction.operand)
            );
        } else if (
            instruction.isInst(ZkgmLib.OP_FORWARD, ZkgmLib.INSTR_VERSION_0)
        ) {
            _verifyForward(
                channelId, ZkgmLib.decodeForward(instruction.operand)
            );
        } else if (instruction.isInst(ZkgmLib.OP_CALL, ZkgmLib.INSTR_VERSION_0))
        {
            _verifyCall(
                channelId, path, ZkgmLib.decodeCall(instruction.operand)
            );
        } else {
            revert ZkgmLib.ErrUnknownOpcode();
        }
    }

    function _verifyTokenOrderV1(
        uint32 channelId,
        uint256 path,
        TokenOrderV1 calldata order
    ) internal {
        IERC20Metadata baseToken =
            IERC20Metadata(address(bytes20(order.baseToken)));
        if (address(baseToken) != ZkgmLib.NATIVE_TOKEN_ERC_7528_ADDRESS) {
            if (!order.baseTokenName.eq(baseToken.name())) {
                revert ZkgmLib.ErrInvalidAssetName();
            }
            if (!order.baseTokenSymbol.eq(baseToken.symbol())) {
                revert ZkgmLib.ErrInvalidAssetSymbol();
            }
            if (order.baseTokenDecimals != baseToken.decimals()) {
                revert ZkgmLib.ErrInvalidAssetDecimals();
            }
        }
        // The origin is the concatenation of (path, destinationChannelId) where
        // path are the intermediate channels hops, if we send from channel X on
        // A over channel Y on B to channel Z on C, the path would be
        // [(X.destinationChannelId, Y.sourceChannelId)].
        uint256 origin = tokenOrigin[address(baseToken)];
        // Split back the origin as the intermediate path and the destinationChannelId
        (uint256 intermediateChannelPath, uint32 destinationChannelId) =
            ZkgmLib.popChannelFromPath(origin);
        // We compute the wrapped token from the destination to the source. If
        // the base token matches the predicted wrapper, we want to unwrap only
        // if it's being sent back through the same channel/path.
        (address wrappedToken,) = _predictWrappedToken(
            intermediateChannelPath, channelId, order.quoteToken
        );
        bool isInverseIntermediatePath =
            path == ZkgmLib.reverseChannelPath(intermediateChannelPath);
        bool isSendingBackToSameChannel = destinationChannelId == channelId;
        bool isUnwrapping = order.baseToken.eq(abi.encodePacked(wrappedToken));
        // If we take the same path starting from the same channel using the
        // wrapped asset, we unwrap.
        if (
            isInverseIntermediatePath && isSendingBackToSameChannel
                && isUnwrapping
        ) {
            if (order.baseTokenPath != origin) {
                revert ZkgmLib.ErrInvalidAssetOrigin();
            }
            IZkgmERC20(address(baseToken)).burn(msg.sender, order.baseAmount);
        } else {
            if (order.baseTokenPath != 0) {
                revert ZkgmLib.ErrInvalidAssetOrigin();
            }
            _increaseOutstandingV2(
                channelId,
                path,
                address(baseToken),
                order.quoteToken,
                order.baseAmount
            );
            if (
                address(baseToken) == ZkgmLib.NATIVE_TOKEN_ERC_7528_ADDRESS
                    && msg.value >= order.baseAmount
            ) {
                if (
                    keccak256(bytes(order.baseTokenName))
                        != NATIVE_TOKEN_NAME_HASH
                ) {
                    revert ZkgmLib.ErrInvalidAssetName();
                }
                if (
                    keccak256(bytes(order.baseTokenSymbol))
                        != NATIVE_TOKEN_SYMBOL_HASH
                ) {
                    revert ZkgmLib.ErrInvalidAssetSymbol();
                }
                if (order.baseTokenDecimals != NATIVE_TOKEN_DECIMALS) {
                    revert ZkgmLib.ErrInvalidAssetDecimals();
                }
                // Use the deposit as a mechanism to consume the order amount from the msg.value.
                // This avoids issue if multiple native eth orders are present.
                WETH.deposit{value: order.baseAmount}();
            } else {
                baseToken.safeTransferFrom(
                    msg.sender, address(this), order.baseAmount
                );
            }
        }
    }

    function _verifyTokenOrderV2(
        uint32 channelId,
        uint256 path,
        TokenOrderV2 calldata order
    ) internal {
        address baseToken = address(bytes20(order.baseToken));

        if (order.kind == ZkgmLib.TOKEN_ORDER_KIND_UNESCROW) {
            (uint256 intermediateChannelPath, uint32 destinationChannelId) =
                ZkgmLib.popChannelFromPath(tokenOrigin[baseToken]);
            bool isInverseIntermediatePath =
                path == ZkgmLib.reverseChannelPath(intermediateChannelPath);
            bool isSendingBackToSameChannel = destinationChannelId == channelId;

            // Predict V1
            (address wrappedTokenV1,) = _predictWrappedToken(
                intermediateChannelPath, channelId, order.quoteToken
            );

            // Predict V2
            bytes32 metadataImage = metadataImageOf[baseToken];
            (address wrappedTokenV2,) = _predictWrappedTokenFromMetadataImageV2(
                intermediateChannelPath,
                channelId,
                order.quoteToken,
                metadataImage
            );

            bool isUnwrappingV1 =
                order.baseToken.eq(abi.encodePacked(wrappedTokenV1));
            bool isUnwrappingV2 =
                order.baseToken.eq(abi.encodePacked(wrappedTokenV2));
            bool isUnwrapping = isUnwrappingV1 || isUnwrappingV2;

            if (
                !(
                    isUnwrapping && isInverseIntermediatePath
                        && isSendingBackToSameChannel
                )
            ) {
                revert ZkgmLib.ErrInvalidUnescrow();
            }

            IZkgmERC20(baseToken).burn(msg.sender, order.baseAmount);
        } else {
            _increaseOutstandingV2(
                channelId, path, baseToken, order.quoteToken, order.baseAmount
            );
            if (
                baseToken == ZkgmLib.NATIVE_TOKEN_ERC_7528_ADDRESS
                    && msg.value >= order.baseAmount
            ) {
                // Use the deposit as a mechanism to consume the order amount from the msg.value.
                // This avoids issue if multiple native eth orders are present.
                WETH.deposit{value: order.baseAmount}();
            } else {
                IERC20(baseToken).safeTransferFrom(
                    msg.sender, address(this), order.baseAmount
                );
            }
        }
    }

    function _verifyBatch(
        uint32 channelId,
        uint256 path,
        Batch calldata batch
    ) internal {
        uint256 l = batch.instructions.length;
        for (uint256 i = 0; i < l; i++) {
            if (
                !ZkgmLib.isAllowedBatchInstruction(batch.instructions[i].opcode)
            ) {
                revert ZkgmLib.ErrInvalidBatchInstruction();
            }
            _verifyInternal(channelId, path, batch.instructions[i]);
        }
    }

    function _verifyForward(
        uint32 channelId,
        Forward calldata forward
    ) internal {
        if (!ZkgmLib.isAllowedForwardInstruction(forward.instruction.opcode)) {
            revert ZkgmLib.ErrInvalidForwardInstruction();
        }
        _verifyInternal(channelId, forward.path, forward.instruction);
    }

    function _verifyCall(
        uint32 channelId,
        uint256 path,
        Call calldata call
    ) internal {
        if (!call.sender.eq(abi.encodePacked(msg.sender))) {
            revert ZkgmLib.ErrInvalidCallSender();
        }
    }

    function predictWrappedToken(
        uint256 path,
        uint32 channel,
        bytes calldata token
    ) external view returns (address, bytes32) {
        return _predictWrappedToken(path, channel, token);
    }

    function predictWrappedTokenV2(
        uint256 path,
        uint32 channel,
        bytes calldata token,
        TokenMetadata calldata metadata
    ) external returns (address, bytes32) {
        return _predictWrappedTokenV2(path, channel, token, metadata);
    }

    function predictWrappedTokenFromMetadataImageV2(
        uint256 path,
        uint32 channel,
        bytes calldata token,
        bytes32 metadataImage
    ) external returns (address, bytes32) {
        return _predictWrappedTokenFromMetadataImageV2(
            path, channel, token, metadataImage
        );
    }

    function predictProxyAccount(
        uint256 path,
        uint32 channelId,
        bytes calldata sender
    ) external returns (bytes32, address) {
        return _predictProxyAccount(path, channelId, sender);
    }
}
"
    },
    "contracts/apps/ucs/03-zkgm/TokenOrder.sol": {
      "content": "pragma solidity ^0.8.27;

import "./Store.sol";

contract UCS03ZkgmTokenOrderImpl is Versioned, TokenBucket, UCS03ZkgmStore {
    using ZkgmLib for *;
    using LibString for *;
    using LibBytes for *;
    using SafeERC20 for *;
    using Address for *;
    using LibCall for *;

    IWETH public immutable WETH;
    ZkgmERC20 public immutable ERC20_IMPL;
    bool public immutable RATE_LIMIT_ENABLED;

    constructor(IWETH _weth, ZkgmERC20 _erc20Impl, bool _rateLimitEnabled) {
        WETH = _weth;
        ERC20_IMPL = _erc20Impl;
        RATE_LIMIT_ENABLED = _rateLimitEnabled;
    }

    function _protocolFillMint(
        uint32 channelId,
        uint256 path,
        address wrappedToken,
        address receiver,
        address relayer,
        uint256 baseAmount,
        uint256 quoteAmount
    ) internal returns (bytes memory) {
        uint256 fee = baseAmount - quoteAmount;
        if (quoteAmount > 0) {
            IZkgmERC20(wrappedToken).mint(receiver, quoteAmount);
        }
        if (fee > 0) {
            IZkgmERC20(wrappedToken).mint(relayer, fee);
        }
        return ZkgmLib.encodeTokenOrderAck(
            TokenOrderAck({
                fillType: ZkgmLib.FILL_TYPE_PROTOCOL,
                marketMaker: ZkgmLib.ACK_EMPTY
            })
        );
    }

    function _protocolFillUnescrowV2(
        uint32 channelId,
        uint256 path,
        bytes calldata baseToken,
        address quoteToken,
        address receiver,
        address relayer,
        uint256 baseAmount,
        uint256 quoteAmount
    ) internal returns (bytes memory) {
        uint256 fee = baseAmount - quoteAmount;
        // If the base token path is being unwrapped, it's escrowed balance will be non zero.
        _decreaseOutstandingV2(
            channelId,
            ZkgmLib.reverseChannelPath(path),
            quoteToken,
            baseToken,
            baseAmount
        );
        // Specific case for native token.
        if (quoteToken == ZkgmLib.NATIVE_TOKEN_ERC_7528_ADDRESS) {
            if (quoteAmount + fee > 0) {
                WETH.withdraw(baseAmount);
            }
            if (quoteAmount > 0) {
                payable(receiver).sendValue(quoteAmount);
            }
            if (fee > 0) {
                if (
                    !SafeTransferLib.trySafeTransferETH(
                        relayer,
                        fee,
                        SafeTransferLib.GAS_STIPEND_NO_STORAGE_WRITES
                    )
                ) {
                    return ZkgmLib.ACK_ERR_ONLYMAKER;
                }
            }
        } else {
            if (quoteAmount > 0) {
                IERC20(quoteToken).safeTransfer(receiver, quoteAmount);
            }
            if (fee > 0) {
                IERC20(quoteToken).safeTransfer(relayer, fee);
            }
        }
        return ZkgmLib.encodeTokenOrderAck(
            TokenOrderAck({
                fillType: ZkgmLib.FILL_TYPE_PROTOCOL,
                marketMaker: ZkgmLib.ACK_EMPTY
            })
        );
    }

    function _marketMakerFill(
        address caller,
        bytes calldata relayerMsg,
        address quoteToken,
        address payable receiver,
        uint256 quoteAmount
    ) internal returns (bytes memory) {
        if (quoteAmount != 0) {
            // We want the top level handler in onRecvPacket to know we need to
            // revert for another MM to get a chance to fill. If we revert now
            // the entire packet would be considered to be "failed" and refunded
            // at origin, which we want to avoid.
            // Hence, in case of transfer failure, we yield the ack to notify the onRecvPacket.

            // Special case for gas station where the user is asking for native
            // gas token. The MM has to provide WETH funds that will be
            // unwrapped, avoiding us from having to manage msg.value accross
            // the stack.
            if (quoteToken == ZkgmLib.NATIVE_TOKEN_ERC_7528_ADDRESS) {
                // Transfert to protocol.
                if (
                    !WETH.trySafeTransferFrom(caller, address(this), quoteAmount)
                ) {
                    return ZkgmLib.ACK_ERR_ONLYMAKER;
                }
                // Unwrap and send.
                WETH.withdraw(quoteAmount);
                // We allow this call to fail because in such case the MM was
                // able to provide the funds. A failure ACK will be written and
                // refund will happen.
                receiver.sendValue(quoteAmount);
            } else if (
                !IERC20(quoteToken).trySafeTransferFrom(
                    caller, receiver, quoteAmount
                )
            ) {
                return ZkgmLib.ACK_ERR_ONLYMAKER;
            }
        }
        return ZkgmLib.encodeTokenOrderAck(
            TokenOrderAck({
                fillType: ZkgmLib.FILL_TYPE_MARKETMAKER,
                // The relayer has to provide it's maker address using the
                // relayerMsg. This address is specific to the counterparty
                // chain and is where the protocol will pay back the base amount
                // on acknowledgement.
                marketMaker: relayerMsg
            })
        );
    }

    function _solverFill(
        IBCPacket calldata packet,
        address caller,
        address relayer,
        bytes calldata relayerMsg,
        uint256 path,
        TokenOrderV2 calldata order,
        bool intent
    ) internal returns (bytes memory) {
        uint256 quoteAmount = order.quoteAmount;

        SolverMetadata calldata metadata =
            ZkgmLib.decodeSolverMetadata(order.metadata);

        address solver = address(bytes20(metadata.solverAddress));

        (bool solverFilled,, bytes memory solverReturnData) = solver.tryCall(
            0,
            gasleft(),
            type(uint16).max,
            abi.encodeCall(
                ISolver.solve,
                (packet, order, path, caller, relayer, relayerMsg, intent)
            )
        );
        if (solverFilled) {
            return ZkgmLib.encodeTokenOrderAck(
                TokenOrderAck({
                    fillType: ZkgmLib.FILL_TYPE_MARKETMAKER,
                    // The solver has to provide it's maker addresss that the
                    // counterparty chain will repay on acknowledgement with the
                    // base token.
                    marketMaker: abi.decode(solverReturnData, (bytes))
                })
            );
        } else {
            return ZkgmLib.ACK_ERR_ONLYMAKER;
        }
    }

    function _marketMakerFillV2(
        IBCPacket calldata packet,
        address caller,
        address relayer,
        bytes calldata relayerMsg,
        uint256 path,
        address quoteToken,
        address payable receiver,
        TokenOrderV2 calldata order,
        bool intent
    ) internal returns (bytes memory) {
        if (order.kind == ZkgmLib.TOKEN_ORDER_KIND_SOLVE) {
            return _solverFill(
                packet, caller, relayer, relayerMsg, path, order, intent
            );
        } else {
            uint256 quoteAmount = order.quoteAmount;

            // We want the top level handler in onRecvPacket to know we need to
            // revert for another MM to get a chance to fill. If we revert now
            // the entire packet would be considered to be "failed" and refunded
            // at origin, which we want to avoid.
            // Hence, in case of transfer failure, we yield the ack to notify the onRecvPacket.

            // Special case for gas station where the user is asking for native
            // gas token. The MM has to provide WETH funds that will be
            // unwrapped, avoiding us from having to manage msg.value accross
            // the stack.
            if (quoteToken == ZkgmLib.NATIVE_TOKEN_ERC_7528_ADDRESS) {
                if (quoteAmount > 0) {
                    // Transfert to protocol.
                    if (
                        !WETH.trySafeTransferFrom(
                            caller, address(this), quoteAmount
                        )
                    ) {
                        return ZkgmLib.ACK_ERR_ONLYMAKER;
                    }
                    // Unwrap and send.
                    WETH.withdraw(quoteAmount);
                    // We allow this call to fail because in such case the MM was
                    // able to provide the funds. A failure ACK will be written and
                    // refund will happen.
                    receiver.sendValue(quoteAmount);
                }
            } else {
                if (quoteAmount > 0) {
                    if (
                        !IERC20(quoteToken).trySafeTransferFrom(
                            caller, receiver, quoteAmount
                        )
                    ) {
                        return ZkgmLib.ACK_ERR_ONLYMAKER;
                    }
                }
            }

            return ZkgmLib.encodeTokenOrderAck(
                TokenOrderAck({
                    fillType: ZkgmLib.FILL_TYPE_MARKETMAKER,
                    // The relayer has to provide it's maker address using the
                    // relayerMsg. This address is specific to the counterparty
                    // chain and is where the protocol will pay back the base amount
                    // on acknowledgement.
                    marketMaker: relayerMsg
                })
            );
        }
    }

    function _deployWrappedTokenV2(
        uint32 channelId,
        uint256 path,
        bytes calldata unwrappedToken,
        address wrappedToken,
        bytes32 wrappedTokenSalt,
        TokenMetadata memory metadata,
        bool canDeploy
    ) internal {
        if (!ZkgmLib.isDeployed(wrappedToken)) {
            if (!canDeploy) {
                revert ZkgmLib.ErrCannotDeploy();
            }
            address implementation = address(bytes20(metadata.implementation));
            CREATE3.deployDeterministic(
                abi.encodePacked(
                    type(ERC1967Proxy).creationCode,
                    abi.encode(implementation, metadata.initializer)
                ),
                wrappedTokenSalt
            );
            tokenOrigin[wrappedToken] =
                ZkgmLib.updateChannelPath(path, channelId);

            bytes memory encodedMetadata = ZkgmLib.encodeTokenMetadata(metadata);
            metadataImageOf[wrappedToken] =
                EfficientHashLib.hash(encodedMetadata);

            uint8 kind = ZkgmLib.WRAPPED_TOKEN_KIND_THIRD_PARTY;
            if (implementation == address(ERC20_IMPL)) {
                try this.decodeZkgmERC20InitializeCall(metadata.initializer)
                returns (
                    address tokenAuthority,
                    address tokenMinter,
                    string memory,
                    string memory,
                    uint8
                ) {
                    if (
                        tokenAuthority == authority()
                            && tokenMinter == address(this)
                    ) {
                        kind = ZkgmLib.WRAPPED_TOKEN_KIND_PROTOCOL;
                    }
                } catch {}
            }

            emit ZkgmLib.CreateWrappedToken(
                path,
                channelId,
                unwrappedToken,
                wrappedToken,
                encodedMetadata,
                kind
            );
        }
    }

    function _makeDefaultTokenMetadata(
        TokenOrderV1 calldata order
    ) internal view returns (TokenMetadata memory) {
        return TokenMetadata({
            implementation: abi.encodePacked(ERC20_IMPL),
            initializer: abi.encodeCall(
                ZkgmERC20.initialize,
                (
                    authority(),
                    address(this),
                    order.baseTokenName,
                    order.baseTokenSymbol,
                    order.baseTokenDecimals
                )
            )
        });
    }

    function _optionalRateLimit(address token, uint256 amount) internal {
        if (RATE_LIMIT_ENABLED) {
            _rateLimit(token, amount);
        }
    }

    function executeTokenOrderV1(
        address caller,
        IBCPacket calldata ibcPacket,
        address relayer,
        bytes calldata relayerMsg,
        uint256 path,
        TokenOrderV1 calldata order,
        bool intent
    ) public returns (bytes memory) {
        address quoteToken = address(bytes20(order.quoteToken));
        address payable receiver = payable(address(bytes20(order.receiver)));

        // For intent packets, the protocol is not allowed to provide any fund
        // as the packet has not been checked for membership poof. Instead, we
        // know the market maker will be repaid on the source chain, if and only
        // if the currently executing packet hash had been registered as sent on
        // the source. In other words, the market maker is unable to lie.
        if (intent) {
            return _marketMakerFill(
                caller, relayerMsg, quoteToken, receiver, order.quoteAmount
            );
        }

        (address wrappedToken, bytes32 wrappedTokenSalt) = _predictWrappedToken(
            path, ibcPacket.destinationChannelId, order.baseToken
        );

        bool baseAmountCoversQuoteAmount = order.baseAmount >= order.quoteAmount;
        if (quoteToken == wrappedToken && baseAmountCoversQuoteAmount) {
            _optionalRateLimit(quoteToken, order.quoteAmount);
            TokenMetadata memory metadata = _makeDefaultTokenMetadata(order);
            _deployWrappedTokenV2(
                ibcPacket.destinationChannelId,
                path,
                order.baseToken,
                wrappedToken,
                wrappedTokenSalt,
                metadata,
                false
            );
            return _protocolFillMint(
                ibcPacket.destinationChannelId,
                path,
                wrappedToken,
                receiver,
                relayer,
                order.baseAmount,
                order.quoteAmount
            );
        } else if (order.baseTokenPath != 0 && baseAmountCoversQuoteAmount) {
            _optionalRateLimit(quoteToken, order.quoteAmount);
            return _protocolFillUnescrowV2(
                ibcPacket.destinationChannelId,
                path,
                order.baseToken,
                quoteToken,
                receiver,
                relayer,
                order.baseAmount,
                order.quoteAmount
            );
        } else {
            // We also allow market makers to fill orders after finality. This
            // allow orders that combines protocol and mm filling (wrapped vs
            // non wrapped assets).
            return _marketMakerFill(
                caller, relayerMsg, quoteToken, receiver, order.quoteAmount
            );
        }
    }

    function executeTokenOrderV2(
        address caller,
        IBCPacket calldata ibcPacket,
        address relayer,
        bytes calldata relayerMsg,
        uint256 path,
        TokenOrderV2 calldata order,
        bool intent
    ) public returns (bytes memory) {
        address quoteToken = address(bytes20(order.quoteToken));
        address payable receiver = payable(address(bytes20(order.receiver)));

        // For intent packets, the protocol is not allowed to provide any fund
        // as the packet has not been checked for membership poof. Instead, we
        // know the market maker will be repaid on the source chain, if and only
        // if the currently executing packet hash had been registered as sent on
        // the source. In other words, the market maker is unable to lie.
        if (intent || order.kind == ZkgmLib.TOKEN_ORDER_KIND_SOLVE) {
            return _marketMakerFillV2(
                ibcPacket,
                caller,
                relayer,
                relayerMsg,
                path,
                quoteToken,
                receiver,
                order,
                intent
            );
        }

        bool baseAmountCoversQuoteAmount = order.baseAmount >= order.quoteAmount;

        if (
            order.kind == ZkgmLib.TOKEN_ORDER_KIND_UNESCROW
                && baseAmountCoversQuoteAmount
        ) {
            _optionalRateLimit(quoteToken, order.quoteAmount);
            return _protocolFillUnescrowV2(
                ibcPacket.destinationChannelId,
                path,
                order.baseToken,
                quoteToken,
                receiver,
                relayer,
                order.baseAmount,
                order.quoteAmount
            );
        } else {
            address wrappedToken;
            bytes32 wrappedTokenSalt;
            if (order.kind == ZkgmLib.TOKEN_ORDER_KIND_ESCROW) {
                bytes32 metadataImage = metadataImageOf[quoteToken];
                if (metadataImage == 0) {
                    // V1
                    (wrappedToken, wrappedTokenSalt) = _predictWrappedToken(
                        path, ibcPacket.destinationChannelId, order.baseToken
                    );
                } else {
                    // V2
                    (wrappedToken, wrappedTokenSalt) =
                    _predictWrappedTokenFromMetadataImageV2(
                        path,
                        ibcPacket.destinationChannelId,
                        order.baseToken,
                        metadataImage
                    );
                }
            } else if (order.kind == ZkgmLib.TOKEN_ORDER_KIND_INITIALIZE) {
                TokenMetadata calldata metadata =
                    ZkgmLib.decodeTokenMetadata(order.metadata);
                (wrappedToken, wrappedTokenSalt) = _predictWrappedTokenV2(
                    path,
                    ibcPacket.destinationChannelId,
                    order.baseToken,
                    metadata
                );
                if (quoteToken != wrappedToken) {
                    revert ZkgmLib.ErrInvalidTokenOrderKind();
                }
                _deployWrappedTokenV2(
                    ibcPacket.destinationChannelId,
                    path,
                    order.baseToken,
                    wrappedToken,
                    wrappedTokenSalt,
                    metadata,
                    true
                );
            }

            if (quoteToken == wrappedToken && baseAmountCoversQuoteAmount) {
                _optionalRateLimit(quoteToken, order.quoteAmount);
                return _protocolFillMint(
                    ibcPacket.destinationChannelId,
                    path,
                    wrappedToken,
                    receiver,
                    relayer,
                    order.baseAmount,
                    order.quoteAmount
                );
            } else {
                // We also allow market makers to fill orders after finality. This
                // allow orders that combines protocol and mm filling (wrapped vs
                // non wrapped assets).
                return _marketMakerFillV2(
                    ibcPacket,
                    caller,
                    relayer,
                    relayerMsg,
                    path,
                    quoteToken,
                    receiver,
                    order,
                    intent
                );
            }
        }
    }

    function _acknowledgeTokenOrderV1(
        IBCPacket calldata ibcPacket,
        address relayer,
        uint256 path,
 

Tags:
ERC20, ERC721, ERC165, Multisig, Mintable, Burnable, Pausable, Non-Fungible, Staking, Yield, Voting, Upgradeable, Multi-Signature, Factory|addr:0x384932cb4bd2a41263713cef81b642d76eb7e5a2|verified:true|block:23382688|tx:0xc07a6920d68ef30e9b041a9fd7613459eb58bcaa52f84f88fbdadf3e420b16fb|first_check:1758121621

Submitted on: 2025-09-17 17:07:02

Comments

Log in to comment.

No comments yet.