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,
Submitted on: 2025-09-17 17:07:02
Comments
Log in to comment.
No comments yet.