diff --git a/contracts/neutron_interchain_txs/schema/execute_msg.json b/contracts/neutron_interchain_txs/schema/execute_msg.json index ff07416..4f4e672 100644 --- a/contracts/neutron_interchain_txs/schema/execute_msg.json +++ b/contracts/neutron_interchain_txs/schema/execute_msg.json @@ -106,6 +106,48 @@ }, "additionalProperties": false }, + { + "type": "object", + "required": [ + "delegate_double_ack" + ], + "properties": { + "delegate_double_ack": { + "type": "object", + "required": [ + "amount", + "denom", + "interchain_account_id", + "validator" + ], + "properties": { + "amount": { + "type": "integer", + "format": "uint128", + "minimum": 0.0 + }, + "denom": { + "type": "string" + }, + "interchain_account_id": { + "type": "string" + }, + "timeout": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, { "type": "object", "required": [ diff --git a/contracts/neutron_interchain_txs/src/contract.rs b/contracts/neutron_interchain_txs/src/contract.rs index c4b5ca3..9a2f5b0 100644 --- a/contracts/neutron_interchain_txs/src/contract.rs +++ b/contracts/neutron_interchain_txs/src/contract.rs @@ -45,9 +45,9 @@ use neutron_sdk::NeutronResult; use crate::storage::{ add_error_to_queue, read_errors_from_queue, read_reply_payload, read_sudo_payload, - save_reply_payload, save_sudo_payload, AcknowledgementResult, IntegrationTestsSudoMock, - IntegrationTestsSudoSubmsgMock, SudoPayload, ACKNOWLEDGEMENT_RESULTS, IBC_FEE, - INTEGRATION_TESTS_SUDO_FAILURE_MOCK, INTEGRATION_TESTS_SUDO_SUBMSG_FAILURE_MOCK, + save_reply_payload, save_sudo_payload, AcknowledgementResult, DoubleDelegateInfo, + IntegrationTestsSudoMock, IntegrationTestsSudoSubmsgMock, SudoPayload, ACKNOWLEDGEMENT_RESULTS, + IBC_FEE, INTEGRATION_TESTS_SUDO_FAILURE_MOCK, INTEGRATION_TESTS_SUDO_SUBMSG_FAILURE_MOCK, INTERCHAIN_ACCOUNTS, SUDO_FAILING_SUBMSG_REPLY_ID, SUDO_PAYLOAD_REPLY_ID, }; @@ -68,6 +68,17 @@ struct OpenAckVersion { tx_type: String, } +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +struct ExecuteDelegateInfo { + pub interchain_account_id: String, + pub validator: String, + pub amount: u128, + pub denom: String, + pub timeout: Option, + pub info: Option, +} + #[cfg_attr(not(feature = "library"), entry_point)] pub fn instantiate( deps: DepsMut, @@ -103,11 +114,37 @@ pub fn execute( } => execute_delegate( deps, env, - interchain_account_id, + ExecuteDelegateInfo { + interchain_account_id, + validator, + amount, + denom, + timeout, + info: None, + }, + ), + ExecuteMsg::DelegateDoubleAck { validator, + interchain_account_id, amount, denom, timeout, + } => execute_delegate_double_ack( + deps, + env, + ExecuteDelegateInfo { + interchain_account_id: interchain_account_id.clone(), + validator: validator.clone(), + amount, + denom: denom.clone(), + timeout, + info: Some(DoubleDelegateInfo { + interchain_account_id, + validator, + denom, + amount, + }), + }, ), ExecuteMsg::Undelegate { validator, @@ -268,6 +305,14 @@ fn execute_register_ica( } fn execute_delegate( + deps: DepsMut, + env: Env, + info: ExecuteDelegateInfo, +) -> StdResult> { + do_delegate(deps, env, info) +} + +fn execute_undelegate( mut deps: DepsMut, env: Env, interchain_account_id: String, @@ -278,7 +323,7 @@ fn execute_delegate( ) -> StdResult> { let fee = IBC_FEE.load(deps.storage)?; let (delegator, connection_id) = get_ica(deps.as_ref(), &env, &interchain_account_id)?; - let delegate_msg = MsgDelegate { + let delegate_msg = MsgUndelegate { delegator_address: delegator, validator_address: validator, amount: Some(Coin { @@ -294,7 +339,7 @@ fn execute_delegate( } let any_msg = ProtobufAny { - type_url: "/cosmos.staking.v1beta1.MsgDelegate".to_string(), + type_url: "/cosmos.staking.v1beta1.MsgUndelegate".to_string(), value: Binary::from(buf), }; @@ -307,37 +352,40 @@ fn execute_delegate( fee, ); - // We use a submessage here because we need the process message reply to save - // the outgoing IBC packet identifier for later. let submsg = msg_with_sudo_callback( deps.branch(), cosmos_msg, SudoPayload { port_id: get_port_id(env.contract.address.as_str(), &interchain_account_id), message: "message".to_string(), + info: None, }, )?; Ok(Response::default().add_submessages(vec![submsg])) } -fn execute_undelegate( +fn execute_delegate_double_ack( + deps: DepsMut, + env: Env, + info: ExecuteDelegateInfo, +) -> StdResult> { + do_delegate(deps, env, info) +} + +fn do_delegate( mut deps: DepsMut, env: Env, - interchain_account_id: String, - validator: String, - amount: u128, - denom: String, - timeout: Option, + info: ExecuteDelegateInfo, ) -> StdResult> { let fee = IBC_FEE.load(deps.storage)?; - let (delegator, connection_id) = get_ica(deps.as_ref(), &env, &interchain_account_id)?; - let delegate_msg = MsgUndelegate { + let (delegator, connection_id) = get_ica(deps.as_ref(), &env, &info.interchain_account_id)?; + let delegate_msg = MsgDelegate { delegator_address: delegator, - validator_address: validator, + validator_address: info.validator, amount: Some(Coin { - denom, - amount: amount.to_string(), + denom: info.denom, + amount: info.amount.to_string(), }), }; let mut buf = Vec::new(); @@ -348,25 +396,28 @@ fn execute_undelegate( } let any_msg = ProtobufAny { - type_url: "/cosmos.staking.v1beta1.MsgUndelegate".to_string(), + type_url: "/cosmos.staking.v1beta1.MsgDelegate".to_string(), value: Binary::from(buf), }; let cosmos_msg = NeutronMsg::submit_tx( connection_id, - interchain_account_id.clone(), + info.interchain_account_id.clone(), vec![any_msg], "".to_string(), - timeout.unwrap_or(DEFAULT_TIMEOUT_SECONDS), + info.timeout.unwrap_or(DEFAULT_TIMEOUT_SECONDS), fee, ); + // We use a submessage here because we need the process message reply to save + // the outgoing IBC packet identifier for later. let submsg = msg_with_sudo_callback( deps.branch(), cosmos_msg, SudoPayload { - port_id: get_port_id(env.contract.address.as_str(), &interchain_account_id), + port_id: get_port_id(env.contract.address.as_str(), &info.interchain_account_id), message: "message".to_string(), + info: info.info, }, )?; @@ -399,7 +450,7 @@ fn integration_tests_sudo_submsg(deps: DepsMut) -> StdResult StdResult { +pub fn sudo(deps: DepsMut, env: Env, msg: SudoMsg) -> StdResult> { let api = deps.api; api.debug(format!("WASMDEBUG: sudo: received sudo msg: {:?}", msg).as_str()); @@ -411,8 +462,8 @@ pub fn sudo(deps: DepsMut, env: Env, msg: SudoMsg) -> StdResult { || m == Some(IntegrationTestsSudoSubmsgMock::EnabledInReply {}) }; - let mut resp: Response = match msg { - SudoMsg::Response { request, data } => sudo_response(deps, request, data)?, + let mut resp: Response = match msg { + SudoMsg::Response { request, data } => sudo_response(deps, env.clone(), request, data)?, SudoMsg::Error { request, details } => sudo_error(deps, request, details)?, SudoMsg::Timeout { request } => sudo_timeout(deps, env.clone(), request)?, SudoMsg::OpenAck { @@ -469,7 +520,7 @@ fn sudo_open_ack( _channel_id: String, _counterparty_channel_id: String, counterparty_version: String, -) -> StdResult { +) -> StdResult> { let parsed_version: Result = serde_json_wasm::from_str(counterparty_version.as_str()); if let Ok(parsed_version) = parsed_version { @@ -486,7 +537,12 @@ fn sudo_open_ack( Err(StdError::generic_err("Can't parse counterparty_version")) } -fn sudo_response(deps: DepsMut, request: RequestPacket, data: Binary) -> StdResult { +fn sudo_response( + mut deps: DepsMut, + env: Env, + request: RequestPacket, + data: Binary, +) -> StdResult> { deps.api.debug( format!( "WASMDEBUG: sudo_response: sudo received: {:?} {:?}", @@ -588,7 +644,7 @@ fn sudo_response(deps: DepsMut, request: RequestPacket, data: Binary) -> StdResu // update but also check that we don't update same seq_id twice ACKNOWLEDGEMENT_RESULTS.update( deps.storage, - (payload.port_id, seq_id), + (payload.clone().port_id, seq_id), |maybe_ack| -> StdResult { match maybe_ack { Some(_ack) => Err(StdError::generic_err("trying to update same seq_id")), @@ -596,12 +652,48 @@ fn sudo_response(deps: DepsMut, request: RequestPacket, data: Binary) -> StdResu } }, )?; + + deps.api + .debug(format!("WASMDEBUG: payload received: {:?}", payload).as_str()); + + if let Some(info) = payload.info { + let res = { + do_delegate( + deps.branch(), + env, + ExecuteDelegateInfo { + interchain_account_id: info.interchain_account_id, + validator: info.validator, + amount: info.amount, + denom: info.denom, + timeout: None, + info: None, + }, + ) + }; + + if let Err(err) = res { + deps.api.debug( + format!( + "WASMDEBUG: error constructing delegate from sudo: {:?}", + err.to_string() + ) + .as_str(), + ); + } else { + return res; + } + } } Ok(Response::default()) } -fn sudo_timeout(deps: DepsMut, _env: Env, request: RequestPacket) -> StdResult { +fn sudo_timeout( + deps: DepsMut, + _env: Env, + request: RequestPacket, +) -> StdResult> { deps.api .debug(format!("WASMDEBUG: sudo timeout request: {:?}", request).as_str()); @@ -654,7 +746,11 @@ fn sudo_timeout(deps: DepsMut, _env: Env, request: RequestPacket) -> StdResult StdResult { +fn sudo_error( + deps: DepsMut, + request: RequestPacket, + details: String, +) -> StdResult> { deps.api .debug(format!("WASMDEBUG: sudo error: {}", details).as_str()); deps.api diff --git a/contracts/neutron_interchain_txs/src/msg.rs b/contracts/neutron_interchain_txs/src/msg.rs index 125aeee..b63ffbe 100644 --- a/contracts/neutron_interchain_txs/src/msg.rs +++ b/contracts/neutron_interchain_txs/src/msg.rs @@ -51,6 +51,13 @@ pub enum ExecuteMsg { denom: String, timeout: Option, }, + DelegateDoubleAck { + interchain_account_id: String, + validator: String, + amount: u128, + denom: String, + timeout: Option, + }, Undelegate { interchain_account_id: String, validator: String, diff --git a/contracts/neutron_interchain_txs/src/storage.rs b/contracts/neutron_interchain_txs/src/storage.rs index e87ea2d..87a1959 100644 --- a/contracts/neutron_interchain_txs/src/storage.rs +++ b/contracts/neutron_interchain_txs/src/storage.rs @@ -9,6 +9,16 @@ use serde::{Deserialize, Serialize}; pub struct SudoPayload { pub message: String, pub port_id: String, + pub info: Option, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct DoubleDelegateInfo { + pub interchain_account_id: String, + pub validator: String, + pub denom: String, + pub amount: u128, } pub const SUDO_PAYLOAD_REPLY_ID: u64 = 1;