Skip to content

Commit 819ce14

Browse files
feat(zkgm): introduce partial market maker filling on finality
1 parent 2fa3703 commit 819ce14

File tree

7 files changed

+152
-52
lines changed

7 files changed

+152
-52
lines changed

cosmwasm/ibc-union/app/ucs03-zkgm/src/contract.rs

+94-28
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ use crate::{
3232
msg::{EurekaMsg, ExecuteMsg, InitMsg, PredictWrappedTokenResponse, QueryMsg},
3333
state::{
3434
BATCH_EXECUTION_ACKS, CHANNEL_BALANCE, CONFIG, EXECUTING_PACKET, EXECUTING_PACKET_IS_BATCH,
35-
EXECUTION_ACK, HASH_TO_FOREIGN_TOKEN, IN_FLIGHT_PACKET, TOKEN_MINTER, TOKEN_ORIGIN,
35+
EXECUTION_ACK, HASH_TO_FOREIGN_TOKEN, IN_FLIGHT_PACKET, MARKET_MAKER, TOKEN_MINTER,
36+
TOKEN_ORIGIN,
3637
},
3738
ContractError,
3839
};
@@ -44,6 +45,7 @@ pub const TOKEN_INIT_REPLY_ID: u64 = 0xbeef;
4445
pub const ESCROW_REPLY_ID: u64 = 0xcafe;
4546
pub const FORWARD_REPLY_ID: u64 = 0xbabe;
4647
pub const MULTIPLEX_REPLY_ID: u64 = 0xface;
48+
pub const MM_FILL_REPLY_ID: u64 = 0xdead;
4749

4850
pub const ZKGM_TOKEN_MINTER_LABEL: &str = "zkgm-token-minter";
4951

@@ -177,6 +179,13 @@ pub fn execute(
177179
),
178180
}
179181
}
182+
ExecuteMsg::InternalBatch { messages } => {
183+
if info.sender != env.contract.address {
184+
Err(ContractError::OnlySelf)
185+
} else {
186+
Ok(Response::new().add_messages(messages))
187+
}
188+
}
180189
ExecuteMsg::InternalExecutePacket {
181190
caller,
182191
packet,
@@ -777,6 +786,7 @@ fn execute_internal(
777786
deps,
778787
env,
779788
info,
789+
caller,
780790
packet,
781791
relayer,
782792
relayer_msg,
@@ -1072,10 +1082,11 @@ fn execute_batch(
10721082
fn execute_fungible_asset_order(
10731083
deps: DepsMut,
10741084
env: Env,
1075-
_info: MessageInfo,
1085+
info: MessageInfo,
1086+
caller: Addr,
10761087
packet: Packet,
10771088
relayer: Addr,
1078-
_relayer_msg: Bytes,
1089+
relayer_msg: Bytes,
10791090
_salt: H256,
10801091
path: U256,
10811092
order: FungibleAssetOrder,
@@ -1112,7 +1123,7 @@ fn execute_fungible_asset_order(
11121123
let base_covers_quote = base_amount >= quote_amount;
11131124
let fee_amount = base_amount.saturating_sub(quote_amount);
11141125

1115-
let mut messages = Vec::<SubMsg>::new();
1126+
let mut messages = Vec::new();
11161127

11171128
// Protocol Fill - If the quote token matches the wrapped version of the base token and base amount >= quote amount
11181129
if quote_token_str == wrapped_denom && base_covers_quote {
@@ -1126,7 +1137,7 @@ fn execute_fungible_asset_order(
11261137
)?;
11271138

11281139
// Create the token with metadata
1129-
messages.push(SubMsg::new(make_wasm_msg(
1140+
messages.push(SubMsg::reply_never(make_wasm_msg(
11301141
WrappedTokenMsg::CreateDenom {
11311142
subdenom: wrapped_denom.clone(),
11321143
metadata: Metadata {
@@ -1154,7 +1165,7 @@ fn execute_fungible_asset_order(
11541165

11551166
// Mint the quote amount to the receiver
11561167
if quote_amount > 0 {
1157-
messages.push(SubMsg::new(make_wasm_msg(
1168+
messages.push(SubMsg::reply_never(make_wasm_msg(
11581169
WrappedTokenMsg::MintTokens {
11591170
denom: wrapped_denom.clone(),
11601171
amount: quote_amount.into(),
@@ -1167,7 +1178,7 @@ fn execute_fungible_asset_order(
11671178

11681179
// Mint any fee to the relayer
11691180
if fee_amount > 0 {
1170-
messages.push(SubMsg::new(make_wasm_msg(
1181+
messages.push(SubMsg::reply_never(make_wasm_msg(
11711182
WrappedTokenMsg::MintTokens {
11721183
denom: wrapped_denom,
11731184
amount: fee_amount.into(),
@@ -1191,7 +1202,7 @@ fn execute_fungible_asset_order(
11911202

11921203
// Transfer the quote amount to the receiver
11931204
if quote_amount > 0 {
1194-
messages.push(SubMsg::new(make_wasm_msg(
1205+
messages.push(SubMsg::reply_never(make_wasm_msg(
11951206
LocalTokenMsg::Unescrow {
11961207
denom: quote_token_str.clone(),
11971208
recipient: receiver.into_string(),
@@ -1204,7 +1215,7 @@ fn execute_fungible_asset_order(
12041215

12051216
// Transfer any fee to the relayer
12061217
if fee_amount > 0 {
1207-
messages.push(SubMsg::new(make_wasm_msg(
1218+
messages.push(SubMsg::reply_never(make_wasm_msg(
12081219
LocalTokenMsg::Unescrow {
12091220
denom: quote_token_str,
12101221
recipient: relayer.into_string(),
@@ -1217,14 +1228,42 @@ fn execute_fungible_asset_order(
12171228
}
12181229
// Market Maker Fill - Any party can fill the order by providing the quote token
12191230
else {
1220-
// Return a special acknowledgement that indicates this order needs a market maker
1221-
return Ok(Response::new().add_message(wasm_execute(
1222-
env.contract.address,
1223-
&ExecuteMsg::InternalWriteAck {
1224-
ack: ACK_ERR_ONLY_MAKER.into(),
1225-
},
1226-
vec![],
1227-
)?));
1231+
if quote_amount > 0 {
1232+
MARKET_MAKER.save(deps.storage, &relayer_msg)?;
1233+
messages.push(SubMsg::reply_always(
1234+
wasm_execute(
1235+
&env.contract.address,
1236+
&ExecuteMsg::InternalBatch {
1237+
messages: vec![
1238+
// Make sure the marker provide the funds
1239+
make_wasm_msg(
1240+
LocalTokenMsg::Escrow {
1241+
from: caller.to_string(),
1242+
denom: quote_token_str.clone(),
1243+
recipient: minter.to_string(),
1244+
amount: quote_amount.into(),
1245+
},
1246+
&minter,
1247+
info.funds,
1248+
)?,
1249+
// Release the funds to the user
1250+
make_wasm_msg(
1251+
LocalTokenMsg::Unescrow {
1252+
denom: quote_token_str,
1253+
recipient: receiver.to_string(),
1254+
amount: quote_amount.into(),
1255+
},
1256+
minter,
1257+
vec![],
1258+
)?,
1259+
],
1260+
},
1261+
vec![],
1262+
)?,
1263+
MM_FILL_REPLY_ID,
1264+
));
1265+
}
1266+
return Ok(Response::new().add_submessages(messages));
12281267
}
12291268

12301269
// Return success acknowledgement with protocol fill type
@@ -1411,26 +1450,53 @@ pub fn reply(deps: DepsMut, env: Env, reply: Reply) -> Result<Response, Contract
14111450
// eureka mode of multiplex operations.
14121451
MULTIPLEX_REPLY_ID => {
14131452
// Extract the acknowledgement from the reply
1414-
if let SubMsgResult::Ok(reply_data) = reply.result {
1415-
#[allow(deprecated)]
1416-
let acknowledgement = reply_data.data.unwrap_or_default();
1453+
match reply.result {
1454+
SubMsgResult::Ok(reply_data) => {
1455+
#[allow(deprecated)]
1456+
let acknowledgement = reply_data.data.unwrap_or_default();
14171457

1418-
// If the acknowledgement is empty, we can't proceed
1419-
if acknowledgement.is_empty() {
1420-
return Err(ContractError::AsyncMultiplexUnsupported);
1421-
}
1458+
// If the acknowledgement is empty, we can't proceed
1459+
if acknowledgement.is_empty() {
1460+
return Err(ContractError::AsyncMultiplexUnsupported);
1461+
}
14221462

1463+
Ok(Response::new().add_message(wasm_execute(
1464+
env.contract.address,
1465+
&ExecuteMsg::InternalWriteAck {
1466+
ack: Vec::from(acknowledgement).into(),
1467+
},
1468+
vec![],
1469+
)?))
1470+
}
1471+
SubMsgResult::Err(error) => Err(ContractError::MultiplexError { error }),
1472+
}
1473+
}
1474+
MM_FILL_REPLY_ID => match reply.result {
1475+
SubMsgResult::Ok(_) => {
1476+
let market_maker = MARKET_MAKER.load(deps.storage)?;
1477+
MARKET_MAKER.remove(deps.storage);
14231478
Ok(Response::new().add_message(wasm_execute(
14241479
env.contract.address,
14251480
&ExecuteMsg::InternalWriteAck {
1426-
ack: Vec::from(acknowledgement).into(),
1481+
ack: FungibleAssetOrderAck {
1482+
fill_type: FILL_TYPE_MARKETMAKER,
1483+
market_maker: Vec::from(market_maker).into(),
1484+
}
1485+
.abi_encode_params()
1486+
.into(),
14271487
},
14281488
vec![],
14291489
)?))
1430-
} else {
1431-
Err(ContractError::AsyncMultiplexUnsupported)
14321490
}
1433-
}
1491+
// Leave a chance for another MM to fill by telling the top level handler to revert.
1492+
SubMsgResult::Err(_) => Ok(Response::new().add_message(wasm_execute(
1493+
env.contract.address,
1494+
&ExecuteMsg::InternalWriteAck {
1495+
ack: ACK_ERR_ONLY_MAKER.into(),
1496+
},
1497+
vec![],
1498+
)?)),
1499+
},
14341500
// For any other reply ID, we don't know how to handle it, so we return an error.
14351501
// This is a safety measure to ensure we don't silently ignore unexpected replies,
14361502
// which could indicate a bug in the contract or an attempt to exploit it.

cosmwasm/ibc-union/app/ucs03-zkgm/src/lib.rs

+4
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ pub enum ContractError {
3333
UnknownReply { id: u64 },
3434
#[error("invalid operation, can only be executed by a market maker")]
3535
OnlyMaker,
36+
#[error("market maker failed to fill: {error}")]
37+
MarketMakerFailed { error: String },
3638
#[error("packet execution reentrancy not allowed")]
3739
AlreadyExecuting,
3840
#[error("order amount must be u128")]
@@ -92,6 +94,8 @@ pub enum ContractError {
9294
},
9395
#[error("asynchronous multiplexing is not supported")]
9496
AsyncMultiplexUnsupported,
97+
#[error("an error happened while calling the destination contract: {error}")]
98+
MultiplexError { error: String },
9599
#[error("channel path is full and can't be updated, too many hops? path: {path}, next_hop_index: {next_hop_index}")]
96100
ChannelPathIsFull { path: U256, next_hop_index: usize },
97101
#[error("invalid asset origin path")]

cosmwasm/ibc-union/app/ucs03-zkgm/src/msg.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use cosmwasm_schema::cw_serde;
2-
use cosmwasm_std::{Addr, Uint256};
2+
use cosmwasm_std::{Addr, CosmosMsg, Uint256};
33
use ibc_union_spec::{ChannelId, Packet};
44
use unionlabs::primitives::{Bytes, H256};
55

@@ -63,6 +63,7 @@ pub enum ExecuteMsg {
6363
/// Write an acknowledgement for an Zkgm packet.
6464
/// Can only be called by the contract itself after packet execution.
6565
InternalWriteAck { ack: Bytes },
66+
InternalBatch { messages: Vec<CosmosMsg>}
6667
}
6768

6869
#[derive(serde::Serialize, serde::Deserialize)]

cosmwasm/ibc-union/app/ucs03-zkgm/src/state.rs

+3
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,6 @@ pub const HASH_TO_FOREIGN_TOKEN: Map<String, Bytes> = Map::new("hash_to_foreign_
4848
/// we need to find the original packet that initiated the forward to properly
4949
/// propagate the acknowledgement or timeout back to the source.
5050
pub const IN_FLIGHT_PACKET: Map<Vec<u8>, Packet> = Map::new("in_flight_packet");
51+
52+
53+
pub const MARKET_MAKER: Item<Bytes> = Item::new("market_maker");

evm/contracts/apps/ucs/03-zkgm/Zkgm.sol

+45-15
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ contract UCS03Zkgm is
5252
using ZkgmLib for *;
5353
using LibString for *;
5454
using LibBytes for *;
55+
using SafeERC20 for *;
5556

5657
IIBCModulePacket public ibcHandler;
5758
mapping(bytes32 => IBCPacket) public inFlightPacket;
@@ -210,8 +211,8 @@ contract UCS03Zkgm is
210211
increaseOutstanding(
211212
channelId, path, address(baseToken), order.baseAmount
212213
);
213-
SafeERC20.safeTransferFrom(
214-
baseToken, msg.sender, address(this), order.baseAmount
214+
baseToken.safeTransferFrom(
215+
msg.sender, address(this), order.baseAmount
215216
);
216217
}
217218
}
@@ -324,7 +325,9 @@ contract UCS03Zkgm is
324325
}
325326
FungibleAssetOrder calldata order =
326327
ZkgmLib.decodeFungibleAssetOrder(instruction.operand);
327-
return executeFungibleAssetOrder(ibcPacket, relayer, path, order);
328+
return executeFungibleAssetOrder(
329+
caller, ibcPacket, relayer, relayerMsg, path, order
330+
);
328331
} else if (instruction.opcode == ZkgmLib.OP_BATCH) {
329332
if (instruction.version > ZkgmLib.INSTR_VERSION_0) {
330333
revert ZkgmLib.ErrUnsupportedVersion();
@@ -551,15 +554,13 @@ contract UCS03Zkgm is
551554
channelId,
552555
ZkgmLib.reverseChannelPath(path),
553556
quoteToken,
554-
baseAmount
557+
quoteAmount + fee
555558
);
556559
if (quoteAmount > 0) {
557-
SafeERC20.safeTransfer(
558-
IERC20(quoteToken), receiver, quoteAmount
559-
);
560+
IERC20(quoteToken).safeTransfer(receiver, quoteAmount);
560561
}
561562
if (fee > 0) {
562-
SafeERC20.safeTransfer(IERC20(quoteToken), relayer, fee);
563+
IERC20(quoteToken).safeTransfer(relayer, fee);
563564
}
564565
}
565566
return ZkgmLib.encodeFungibleAssetOrderAck(
@@ -570,6 +571,35 @@ contract UCS03Zkgm is
570571
);
571572
}
572573

574+
function internalMarketMakerFill(
575+
address caller,
576+
bytes calldata relayerMsg,
577+
address quoteToken,
578+
address receiver,
579+
uint256 quoteAmount
580+
) internal returns (bytes memory) {
581+
if (quoteAmount != 0) {
582+
// We want the top level handler in onRecvPacket to know we need to
583+
// revert for another MM to get a chance to fill. If we revert now
584+
// the entire packet would be considered to be "failed" and refunded
585+
// at origin, which we want to avoid.
586+
if (!IERC20(quoteToken).transferFrom(caller, receiver, quoteAmount))
587+
{
588+
return ZkgmLib.ACK_ERR_ONLYMAKER;
589+
}
590+
}
591+
return ZkgmLib.encodeFungibleAssetOrderAck(
592+
FungibleAssetOrderAck({
593+
fillType: ZkgmLib.FILL_TYPE_MARKETMAKER,
594+
// The relayer has to provide it's maker address using the
595+
// relayerMsg. This address is specific to the counterparty
596+
// chain and is where the protocol will pay back the base amount
597+
// on acknowledgement.
598+
marketMaker: relayerMsg
599+
})
600+
);
601+
}
602+
573603
function internalDeployWrappedToken(
574604
uint32 channelId,
575605
uint256 path,
@@ -598,8 +628,10 @@ contract UCS03Zkgm is
598628
}
599629

600630
function executeFungibleAssetOrder(
631+
address caller,
601632
IBCPacket calldata ibcPacket,
602633
address relayer,
634+
bytes calldata relayerMsg,
603635
uint256 path,
604636
FungibleAssetOrder calldata order
605637
) internal returns (bytes memory) {
@@ -644,9 +676,9 @@ contract UCS03Zkgm is
644676
false
645677
);
646678
} else {
647-
// TODO: allow for MM filling after having added the caller to the
648-
// interface (from which we extract funds)
649-
return ZkgmLib.ACK_ERR_ONLYMAKER;
679+
return internalMarketMakerFill(
680+
caller, relayerMsg, quoteToken, receiver, order.quoteAmount
681+
);
650682
}
651683
}
652684

@@ -865,9 +897,7 @@ contract UCS03Zkgm is
865897
baseToken,
866898
orderBaseAmount
867899
);
868-
SafeERC20.safeTransfer(
869-
IERC20(baseToken), marketMaker, orderBaseAmount
870-
);
900+
IERC20(baseToken).safeTransfer(marketMaker, orderBaseAmount);
871901
}
872902
} else {
873903
revert ZkgmLib.ErrInvalidFillType();
@@ -1043,7 +1073,7 @@ contract UCS03Zkgm is
10431073
decreaseOutstanding(
10441074
sourceChannelId, path, baseToken, orderBaseAmount
10451075
);
1046-
SafeERC20.safeTransfer(IERC20(baseToken), sender, orderBaseAmount);
1076+
IERC20(baseToken).safeTransfer(sender, orderBaseAmount);
10471077
}
10481078
}
10491079

0 commit comments

Comments
 (0)