Skip to content

feat: UnionTransfer EVM library #323

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
4 changes: 3 additions & 1 deletion contracts/encoders/evm-encoder/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use alloy_sol_types::SolValue;
use cosmwasm_std::{Binary, StdError, StdResult};
use libraries::{
aave_position_manager, balancer_v2_swap, cctp_transfer, forwarder, ibc_eureka_transfer,
standard_bridge_transfer, stargate_transfer,
standard_bridge_transfer, stargate_transfer, union_transfer,
};
use strum::EnumString;
use valence_authorization_utils::authorization::Subroutine;
Expand All @@ -31,6 +31,7 @@ pub enum EVMLibrary {
BalancerV2Swap,
StandardBridgeTransfer,
IbcEurekaTransfer,
UnionTransfer,
}

impl EVMLibrary {
Expand Down Expand Up @@ -64,6 +65,7 @@ impl EVMLibrary {
EVMLibrary::BalancerV2Swap => balancer_v2_swap::encode(msg),
EVMLibrary::StandardBridgeTransfer => standard_bridge_transfer::encode(msg),
EVMLibrary::IbcEurekaTransfer => ibc_eureka_transfer::encode(msg),
EVMLibrary::UnionTransfer => union_transfer::encode(msg),
}
}
}
Expand Down
1 change: 1 addition & 0 deletions contracts/encoders/evm-encoder/src/libraries/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pub mod forwarder;
pub mod ibc_eureka_transfer;
pub mod standard_bridge_transfer;
pub mod stargate_transfer;
pub mod union_transfer;

// Function calls that are common to all libraries

Expand Down
110 changes: 110 additions & 0 deletions contracts/encoders/evm-encoder/src/libraries/union_transfer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
use alloy_primitives::Bytes;
use alloy_sol_types::{SolCall, SolValue};
use cosmwasm_schema::cw_serde;
use cosmwasm_std::{Binary, StdError, StdResult, Uint256};
use valence_encoder_utils::libraries::{
union_transfer::solidity_types::transferCall, updateConfigCall,
};
use valence_library_utils::{msg::ExecuteMsg, LibraryAccountType};

use crate::parse_address;

use super::{get_update_ownership_call, get_update_processor_call};

// We need to define a config and functions for this library as we don't have a CosmWasm equivalent
#[cw_serde]
/// Struct representing the library configuration.
pub struct LibraryConfig {
/// The input address for the library.
pub input_addr: LibraryAccountType,
/// The recipient of the transfer on the destination chain (for bech32 addresses the bytes conversion of the entire bech32 address string).
pub recipient: Binary,
/// Amount to transfer. Setting this to 0 will transfer the entire balance.
pub amount: Uint256,
/// Address of the zkGM contract.
pub zk_gm: String,
/// The address of the ERC20 token.
pub transfer_token: String,
/// The name of the transfer token.
pub transfer_token_name: String,
/// The symbol of the transfer token.
pub transfer_token_symbol: String,
/// The decimals of the transfer token.
pub transfer_token_decimals: u8,
/// The token requested in return on destination chain. Bytes conversion of the token denom / address for Native Cosmos Tokens / CW-20 tokens.
pub quote_token: Binary,
/// The amount of the quote token.
pub quote_token_amount: Uint256,
/// The path to unwrap the transfer token.
pub transfer_token_unwrapping_path: Uint256,
/// The channel ID for the transfer.
pub channel_id: u32,
/// The timeout for the transfer.
pub timeout: u64,
/// The protocol version for the transfer.
pub protocol_version: u8,
}

#[cw_serde]
/// Enum representing the different function messages that can be sent.
pub enum FunctionMsgs {
/// Message to transfer tokens.
/// If the quote amount is not passed, the value in the config will be used.
Transfer { quote_amount: Option<Uint256> },
}

type UnionTransferConfig = ExecuteMsg<FunctionMsgs, LibraryConfig>;

pub fn encode(msg: &Binary) -> StdResult<Vec<u8>> {
// Extract the message from the binary and verify that it parses into a valid json for the library
let msg: UnionTransferConfig = serde_json::from_slice(msg.as_slice()).map_err(|_| {
StdError::generic_err("Message sent is not a valid message for this library!".to_string())
})?;

match msg {
ExecuteMsg::ProcessFunction(function) => match function {
FunctionMsgs::Transfer { quote_amount } => {
let transfer_call = transferCall {
_quoteAmount: alloy_primitives::U256::from_be_bytes(
quote_amount.unwrap_or_default().to_be_bytes(),
),
};
Ok(transfer_call.abi_encode())
}
},
ExecuteMsg::UpdateConfig { new_config } => {
// Parse addresses
let input_account = parse_address(&new_config.input_addr.to_string()?)?;
let zk_gm = parse_address(&new_config.zk_gm)?;
// Convert to address to verify it is a valid address
let transfer_token = parse_address(&new_config.transfer_token)?;

// Build config struct
let config =
valence_encoder_utils::libraries::union_transfer::solidity_types::UnionTransferConfig {
protocolVersion: new_config.protocol_version,
transferTokenDecimals: new_config.transfer_token_decimals,
channelId: new_config.channel_id,
timeout: new_config.timeout,
inputAccount: input_account,
zkGM: zk_gm,
amount: alloy_primitives::U256::from_be_bytes(new_config.amount.to_be_bytes()),
quoteTokenAmount: alloy_primitives::U256::from_be_bytes(new_config.quote_token_amount.to_be_bytes()),
transferTokenUnwrappingPath: alloy_primitives::U256::from_be_bytes(new_config.transfer_token_unwrapping_path.to_be_bytes()),
recipient: new_config.recipient.to_vec().into(),
transferToken: Bytes::from(transfer_token.to_vec()),
quoteToken: new_config.quote_token.to_vec().into(),
transferTokenName: new_config.transfer_token_name,
transferTokenSymbol: new_config.transfer_token_symbol,
};

// Create the encoded call with the encoded config
let call = updateConfigCall {
_config: config.abi_encode().into(),
};
Ok(call.abi_encode())
}
ExecuteMsg::UpdateProcessor { processor } => get_update_processor_call(&processor),
ExecuteMsg::UpdateOwnership(action) => get_update_ownership_call(action),
}
}
39 changes: 39 additions & 0 deletions contracts/encoders/evm-encoder/src/tests.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::str::FromStr;

use alloy_primitives::{Address, U256};
use alloy_sol_types::SolValue;
use cosmwasm_std::{
Expand Down Expand Up @@ -530,3 +532,40 @@ fn test_decode_callback_message() {

assert_eq!(expected, expected_callback);
}

#[test]
fn test_alloy_from_cosmwasm_uint256() {
use alloy_primitives::U256 as AlloyU256;
use cosmwasm_std::Uint256 as CosmwasmUint256;

// Create a CosmWasm Uint256
let cosmwasm_uint = CosmwasmUint256::from(12345u128);

// Get the bytes from the CosmWasm Uint256
let bytes = cosmwasm_uint.to_be_bytes();

// Create an Alloy U256 from those same bytes
let alloy_uint = AlloyU256::from_be_bytes(bytes);

// Verify the values match by comparing their string representations
assert_eq!(cosmwasm_uint.to_string(), alloy_uint.to_string());

// Test with a larger value
let cosmwasm_uint = CosmwasmUint256::from(u128::MAX);
let bytes = cosmwasm_uint.to_be_bytes();
let alloy_uint = AlloyU256::from_be_bytes(bytes);
assert_eq!(cosmwasm_uint.to_string(), alloy_uint.to_string());

// Test with a value from string that's larger than u128
let large_value = "340282366920938463463374607431768211456"; // > u128::MAX
let cosmwasm_uint = CosmwasmUint256::from_str(large_value).unwrap();
let bytes = cosmwasm_uint.to_be_bytes();
let alloy_uint = AlloyU256::from_be_bytes(bytes);
assert_eq!(cosmwasm_uint.to_string(), alloy_uint.to_string());

// Test the reverse direction (Alloy → CosmWasm)
let alloy_uint = AlloyU256::from(9876543210u64);
let bytes = alloy_uint.to_be_bytes();
let cosmwasm_uint = CosmwasmUint256::from_be_bytes(bytes);
assert_eq!(alloy_uint.to_string(), cosmwasm_uint.to_string());
}
1 change: 1 addition & 0 deletions docs/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
- [Stargate Transfer](./libraries/evm/stargate_transfer.md)
- [Standard Bridge Transfer](./libraries/evm/standard_bridge_transfer.md)
- [IBC Eureka Transfer](./libraries/evm/ibc_eureka_transfer.md)
- [Union Transfer](./libraries/evm/union_transfer.md)
- [AAVE Position Manager](./libraries/evm/aave_position_manager.md)
- [Balancer V2 Swap](./libraries/evm/balancer_v2_swap.md)
- [Middleware](./middleware/_overview.md)
Expand Down
97 changes: 97 additions & 0 deletions docs/src/libraries/evm/union_transfer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# Valence Union Transfer library

The **Valence Union Transfer** library allows to **transfer funds** from an **input account** to a **recipient** using the [Union UCS03-ZKGM protocol](https://docs.union.build/ucs/03/), which allows arbitrary filling of orders by any party. It is typically used as part of a **Valence Program**. In that context, a **Processor** contract will be the main contract interacting with the Union Transfer library.

## High-level flow

```mermaid
---
title: Union Transfer Library
---
graph LR
IA((Input Account))
ZG((zkGM))
R((Recipient))
P[Processor]
U[Union Transfer
Library]
UTM[Union Token
Protocol]

subgraph DEST[ Destination Chain ]
UTM -- 6/Send tokens --> R
end

subgraph EVM[ EVM Domain ]
P -- 1/Call
transfer(quoteAmount) --> U
U -- 2/Query ERC20 balance --> IA
U -- 3/Call approve and send --> IA
IA -- 4/Approve ERC20 --> ZG
IA -- 5/Call send with instruction --> ZG
end

EVM --- DEST
```

## Functions

| Function | Parameters | Description |
| ------------ | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Transfer** | quoteAmount | Transfer funds from the configured **input account** to the **recipient** on the **destination chain**. The quoteAmount parameter can override the configured quote token amount. If 0 is passed, the amount in the config is used. |

## Configuration

The library is configured on deployment using the `UnionTransferConfig` type. A list of supported chains and their channels can be found [here](https://docs.union.build/protocol/chains/overview/). Additional information of parameters used in the configuration can be found [here](https://docs.union.build/ucs/03/). This library allows any party to fill orders, therefore the `quoteTokenAmount` value should take into consideration the amount of tokens that the filling party will receive.

All current deployed Union UCS03 contracts can be found in the [deployment section](https://docs.union.build/protocol/deployments/) under the name `ucs03`.

```solidity
/**
* @dev Configuration struct for Union transfer parameters.
*
* -- Transfer core parameters --
* @param amount The number of tokens to transfer. If set to 0, the entire balance is transferred.
* @param inputAccount The account from which tokens will be debited.
* @param recipient The recipient (in Bytes format) on the destination chain where tokens will be received.
* For bech32 addresses, it just converts the entire address to bytes. For example the bytes representation
* of `bbn14mlpd48k5vkeset4x7f78myz3m47jcaxz9gpnl` would be
* `0x62626e31346d6c706434386b35766b657365743478376637386d797a336d34376a6361787a3967706e6c`
*
* -- Transfer token details --
* @param transferToken The ERC20 token address that will be transferred.
* @param transferTokenName The name of the token being transferred (e.g., "Babylon")
* @param transferTokenSymbol The symbol of the token being transferred. (e.g., "BABY")
* @param transferTokenDecimals The number of decimals for the token being transferred. (e.g., 6)
* @param transferTokenUnwrappingPath Origin path for unwrapping, (e.g., 0 for WETH, 1 for BABY...). Related to the origin chain of these tokens.
*
* -- Quote token details --
* @param quoteToken The token requested in return on destination chain. Bytes conversion of the token.
* For example, the quote Token for WETH on Babylon would be `0x62626e31333030736530767775653737686e36733877706836346579366435357a616634386a72766567397761667371756e636e33653473637373677664`
* which bytes conversion of "bbn1300se0vwue77hn6s8wph64ey6d55zaf48jrveg9wafsquncn3e4scssgvd" because WETH is a CW20 token on Babylon.
* For BABY, on the other side, it would be `0x7562626e` which is the bytes conversion of "ubbn".
* @param quoteTokenAmount The amount of the quote token requested in return on the destination chain. If set to 0, the same amount as the transferred token is requested.
*
* -- Protocol parameters --
* @param zkGM The zkGM contract.
* @param protocolVersion The protocol version to be used. Required for backward compatibility. Allows dispatching between different versions.
* @param channelId The channel ID for the transfer. This is used to identify the specific transfer channel.
* @param timeout The timeout in seconds for the transfer. For reference, 3 days is being used on btc.union.build (259200 seconds).
*/
struct UnionTransferConfig {
uint8 protocolVersion;
uint8 transferTokenDecimals;
uint32 channelId;
uint64 timeout;
BaseAccount inputAccount;
IUnion zkGM;
uint256 amount;
uint256 quoteTokenAmount;
uint256 transferTokenUnwrappingPath;
bytes recipient;
bytes transferToken;
bytes quoteToken;
string transferTokenName;
string transferTokenSymbol;
}
```
1 change: 1 addition & 0 deletions packages/encoder-utils/src/libraries/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pub mod forwarder;
pub mod ibc_eureka_transfer;
pub mod standard_bridge_transfer;
pub mod stargate_transfer;
pub mod union_transfer;

#[cw_serde]
pub struct Bytes32Address(Binary);
Expand Down
1 change: 1 addition & 0 deletions packages/encoder-utils/src/libraries/union_transfer/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod solidity_types;
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
use alloy_sol_types::sol;

sol! {
struct UnionTransferConfig {
uint8 protocolVersion;
uint8 transferTokenDecimals;
uint32 channelId;
uint64 timeout;
address inputAccount;
address zkGM;
uint256 amount;
uint256 quoteTokenAmount;
uint256 transferTokenUnwrappingPath;
bytes recipient;
bytes transferToken;
bytes quoteToken;
string transferTokenName;
string transferTokenSymbol;
}

function transfer(uint256 _quoteAmount) external;
}
Loading
Loading