Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 25 additions & 2 deletions base/src/libraries/TokenLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ import {Pubkey} from "./SVMLib.sol";
///
/// @custom:field localToken Address of the ERC20 token on this chain.
/// @custom:field remoteToken Pubkey of the remote token on Solana.
/// @custom:field to Address of the recipient on the target chain. EVM address on Base, Solana pubkey on Solana.
/// @custom:field to Recipient on the target chain. For Base recipients, encode the EVM address with
/// `TokenLib.encodeEvmRecipient`, i.e. left-aligned as `bytes32(bytes20(recipient))`.
/// For Solana recipients, use the Solana pubkey.
/// @custom:field remoteAmount Amount of tokens being bridged (expressed in Solana units).
struct Transfer {
address localToken;
Expand Down Expand Up @@ -114,6 +116,27 @@ library TokenLib {
}
}

/// @notice Encodes an EVM recipient for a Solana-to-Base transfer.
///
/// @dev The Solana message format stores recipients in a bytes32 field, but Base finalization reads the first
/// 20 bytes. This must be left-aligned, not ABI-style right-aligned.
///
/// @param recipient Address to receive tokens on Base.
///
/// @return encoded The recipient encoded as left-aligned bytes20 inside bytes32.
function encodeEvmRecipient(address recipient) internal pure returns (bytes32 encoded) {
return bytes32(bytes20(recipient));
}

/// @notice Decodes a Base recipient from a Solana-to-Base transfer.
///
/// @param encoded Recipient encoded with `encodeEvmRecipient`.
///
/// @return recipient Address to receive tokens on Base.
function decodeEvmRecipient(bytes32 encoded) internal pure returns (address recipient) {
return address(bytes20(encoded));
}

/// @notice Initializes a token transfer.
///
/// @dev IMPORTANT: For native ERC20 tokens with transfer fees, the `transfer.remoteAmount` field might be modified
Expand Down Expand Up @@ -224,7 +247,7 @@ library TokenLib {
function finalizeTransfer(Transfer memory transfer, address crossChainErc20Factory) internal {
TokenLibStorage storage $ = getTokenLibStorage();

address to = address(bytes20(transfer.to));
address to = decodeEvmRecipient(transfer.to);
uint256 localAmount;

if (transfer.localToken == ETH_ADDRESS) {
Expand Down
17 changes: 17 additions & 0 deletions base/test/libraries/TokenLib.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -647,6 +647,23 @@ contract TokenLibTest is CommonTest {
);
}

function test_evmRecipientEncoding_usesLeftAlignedBytes20() public pure {
address recipient = address(uint160(0x1234567890abcdef1234567890abcdef12345678));

bytes32 encoded = TokenLib.encodeEvmRecipient(recipient);
bytes32 rightAligned = bytes32(uint256(uint160(recipient)));

assertEq(encoded, bytes32(bytes20(recipient)), "EVM recipient should be left-aligned");
assertEq(TokenLib.decodeEvmRecipient(encoded), recipient, "Left-aligned recipient should round trip");

assertNotEq(rightAligned, encoded, "ABI-style right-aligned encoding should differ");
assertNotEq(
TokenLib.decodeEvmRecipient(rightAligned),
recipient,
"Right-aligned encoding decodes to the wrong Base recipient"
);
}

//////////////////////////////////////////////////////////////
/// Integration Tests ///
//////////////////////////////////////////////////////////////
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ pub trait MessageSpace {
#[derive(Debug, Clone, Eq, PartialEq, AnchorSerialize, AnchorDeserialize)]
pub struct Transfer {
/// The recipient address on Base that will receive the bridged tokens.
/// When relayed into the Solidity `Transfer.to` bytes32 field, these 20 bytes
/// occupy the first 20 bytes and are decoded by `TokenLib.decodeEvmRecipient`.
pub to: [u8; 20],

/// The token mint address on Solana that is being bridged.
Expand Down