diff --git a/contracts/chain-adapters/Solana_Adapter.sol b/contracts/chain-adapters/Solana_Adapter.sol index 8708889e7..429500e83 100644 --- a/contracts/chain-adapters/Solana_Adapter.sol +++ b/contracts/chain-adapters/Solana_Adapter.sol @@ -5,6 +5,7 @@ import { IMessageTransmitter, ITokenMessenger } from "../external/interfaces/CCT import { SpokePoolInterface } from "../interfaces/SpokePoolInterface.sol"; import { AdapterInterface } from "./interfaces/AdapterInterface.sol"; import { CircleCCTPAdapter, CircleDomainIds } from "../libraries/CircleCCTPAdapter.sol"; +import { Bytes32ToAddress } from "../libraries/AddressConverters.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; @@ -19,6 +20,14 @@ import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; // solhint-disable-next-line contract-name-camelcase contract Solana_Adapter is AdapterInterface, CircleCCTPAdapter { + /** + * @notice We use Bytes32ToAddress library to map a Solana address to an Ethereum address representation. + * @dev The Ethereum address is derived from the Solana address by truncating it to its lowest 20 bytes. This same + * conversion must be done by the HubPool owner when adding Solana spoke pool and setting the corresponding pool + * rebalance and deposit routes. + */ + using Bytes32ToAddress for bytes32; + /** * @notice The official Circle CCTP MessageTransmitter contract endpoint. * @dev Posted officially here: https://developers.circle.com/stablecoins/docs/evm-smart-contracts @@ -84,10 +93,10 @@ contract Solana_Adapter is AdapterInterface, CircleCCTPAdapter { cctpMessageTransmitter = _cctpMessageTransmitter; SOLANA_SPOKE_POOL_BYTES32 = solanaSpokePool; - SOLANA_SPOKE_POOL_ADDRESS = _trimSolanaAddress(solanaSpokePool); + SOLANA_SPOKE_POOL_ADDRESS = solanaSpokePool.toAddressUnchecked(); SOLANA_USDC_BYTES32 = solanaUsdc; - SOLANA_USDC_ADDRESS = _trimSolanaAddress(solanaUsdc); + SOLANA_USDC_ADDRESS = solanaUsdc.toAddressUnchecked(); SOLANA_SPOKE_POOL_USDC_VAULT = solanaSpokePoolUsdcVault; } @@ -151,18 +160,6 @@ contract Solana_Adapter is AdapterInterface, CircleCCTPAdapter { emit TokensRelayed(l1Token, l2Token, amount, to); } - /** - * @notice Helper to map a Solana address to an Ethereum address representation. - * @dev The Ethereum address is derived from the Solana address by truncating it to its lowest 20 bytes. This same - * conversion must be done by the HubPool owner when adding Solana spoke pool and setting the corresponding pool - * rebalance and deposit routes. - * @param solanaAddress Solana address (Base58 decoded to bytes32) to map to its Ethereum address representation. - * @return Ethereum address representation of the Solana address. - */ - function _trimSolanaAddress(bytes32 solanaAddress) internal pure returns (address) { - return address(uint160(uint256(solanaAddress))); - } - /** * @notice Translates a message to enable/disable a route on Solana spoke pool. * @param message Message to translate, expecting setEnableRoute(address,uint256,bool). diff --git a/contracts/libraries/AddressConverters.sol b/contracts/libraries/AddressConverters.sol index 888629e17..624fd9aa3 100644 --- a/contracts/libraries/AddressConverters.sol +++ b/contracts/libraries/AddressConverters.sol @@ -2,8 +2,19 @@ pragma solidity ^0.8.0; library Bytes32ToAddress { + /************************************** + * ERRORS * + **************************************/ + error InvalidBytes32(); + function toAddress(bytes32 _bytes32) internal pure returns (address) { - require(uint256(_bytes32) >> 192 == 0, "Invalid bytes32: highest 12 bytes must be 0"); + if (uint256(_bytes32) >> 192 != 0) { + revert InvalidBytes32(); + } + return address(uint160(uint256(_bytes32))); + } + + function toAddressUnchecked(bytes32 _bytes32) internal pure returns (address) { return address(uint160(uint256(_bytes32))); } } diff --git a/test/evm/hardhat/chain-adapters/Solana_Adapter.ts b/test/evm/hardhat/chain-adapters/Solana_Adapter.ts index a86a8fda8..86dcb576e 100644 --- a/test/evm/hardhat/chain-adapters/Solana_Adapter.ts +++ b/test/evm/hardhat/chain-adapters/Solana_Adapter.ts @@ -87,7 +87,7 @@ describe("Solana Chain Adapter", function () { const functionCallData = mockSpoke.interface.encodeFunctionData("setCrossDomainAdmin", [newAdmin]); expect(await hubPool.relaySpokePoolAdminFunction(solanaChainId, functionCallData)) .to.emit(solanaAdapter.attach(hubPool.address), "MessageRelayed") - .withArgs(solanaSpokePoolAddress, functionCallData); + .withArgs(solanaSpokePoolAddress.toLowerCase(), functionCallData); expect(cctpMessageTransmitter.sendMessage).to.have.been.calledWith( solanaDomainId, solanaSpokePoolBytes32,