-
Notifications
You must be signed in to change notification settings - Fork 23
feat: TokenBridge #771
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
base: main
Are you sure you want to change the base?
feat: TokenBridge #771
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,96 @@ | ||||||
| // SPDX-License-Identifier: BUSL-1.1 | ||||||
| pragma solidity 0.8.28; | ||||||
|
|
||||||
| import {ITokenBridge} from "./interfaces/ITokenBridge.sol"; | ||||||
|
|
||||||
| import {Auth} from "../misc/Auth.sol"; | ||||||
| import {IERC20} from "../misc/interfaces/IERC20.sol"; | ||||||
| import {SafeTransferLib} from "../misc/libraries/SafeTransferLib.sol"; | ||||||
|
|
||||||
| import {PoolId} from "../core/types/PoolId.sol"; | ||||||
| import {ISpoke} from "../core/spoke/interfaces/ISpoke.sol"; | ||||||
| import {ShareClassId} from "../core/types/ShareClassId.sol"; | ||||||
| import {ITrustedContractUpdate} from "../core/utils/interfaces/IContractUpdate.sol"; | ||||||
|
|
||||||
| /// @title TokenBridge | ||||||
| /// @notice Wrapper contract for cross-chain token transfers compatible with Glacis Airlift | ||||||
| contract TokenBridge is Auth, ITokenBridge { | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Q for all of us: Should this contract implement
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks like it's never used as a target for receiving tokens. I would say not (?) |
||||||
| ISpoke public immutable spoke; | ||||||
|
|
||||||
| address public relayer; | ||||||
| mapping(PoolId => mapping(ShareClassId => GasLimits)) public gasLimits; | ||||||
| mapping(uint256 evmChainId => uint16 centrifugeId) public chainIdToCentrifugeId; | ||||||
|
|
||||||
| constructor(ISpoke spoke_, address deployer) Auth(deployer) { | ||||||
| spoke = spoke_; | ||||||
| } | ||||||
|
|
||||||
| //---------------------------------------------------------------------------------------------- | ||||||
| // Administration | ||||||
| //---------------------------------------------------------------------------------------------- | ||||||
|
|
||||||
| /// @inheritdoc ITokenBridge | ||||||
| function file(bytes32 what, address data) external auth { | ||||||
| if (what == "relayer") relayer = data; | ||||||
| else revert FileUnrecognizedParam(); | ||||||
| emit File(what, data); | ||||||
| } | ||||||
|
|
||||||
| /// @inheritdoc ITokenBridge | ||||||
| function file(bytes32 what, uint256 evmChainId, uint16 centrifugeId) external auth { | ||||||
| if (what == "centrifugeId") chainIdToCentrifugeId[evmChainId] = centrifugeId; | ||||||
| else revert FileUnrecognizedParam(); | ||||||
| emit File(what, evmChainId, centrifugeId); | ||||||
| } | ||||||
|
Comment on lines
+39
to
+44
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. From my experience, |
||||||
|
|
||||||
| /// @inheritdoc ITrustedContractUpdate | ||||||
| function trustedCall(PoolId poolId, ShareClassId scId, bytes memory payload) external auth { | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: We can use
Suggested change
|
||||||
| uint8 kindValue = abi.decode(payload, (uint8)); | ||||||
| require(kindValue <= uint8(type(TrustedCall).max), UnknownTrustedCall()); | ||||||
|
|
||||||
| TrustedCall kind = TrustedCall(kindValue); | ||||||
| if (kind == TrustedCall.SetGasLimits) { | ||||||
| (, uint128 extraGasLimit, uint128 remoteExtraGasLimit) = abi.decode(payload, (uint8, uint128, uint128)); | ||||||
|
|
||||||
| require(address(spoke.shareToken(poolId, scId)) != address(0), ShareTokenDoesNotExist()); | ||||||
|
|
||||||
| gasLimits[poolId][scId] = GasLimits(extraGasLimit, remoteExtraGasLimit); | ||||||
| emit UpdateGasLimits(poolId, scId, extraGasLimit, remoteExtraGasLimit); | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| //---------------------------------------------------------------------------------------------- | ||||||
| // Bridging | ||||||
| //---------------------------------------------------------------------------------------------- | ||||||
|
|
||||||
| /// @inheritdoc ITokenBridge | ||||||
| function send(address token, uint256 amount, bytes32 receiver, uint256 destinationChainId, address refundAddress) | ||||||
| public | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this really be |
||||||
| payable | ||||||
| returns (bytes memory) | ||||||
| { | ||||||
| uint16 centrifugeId = chainIdToCentrifugeId[destinationChainId]; | ||||||
| require(centrifugeId != 0, InvalidChainId()); | ||||||
|
|
||||||
| SafeTransferLib.safeTransferFrom(token, msg.sender, address(this), amount); | ||||||
| if (IERC20(token).allowance(address(this), address(spoke)) == 0) { | ||||||
| SafeTransferLib.safeApprove(token, address(spoke), type(uint256).max); | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am curious why this is necessary. I might be missing something but AFAICT the Spoke moves TokenBridge's tokens only using its ward privilege not via a standard ERC20 allowance and because it passes
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, I also think the allowance is never checked. I think approval is something the user of |
||||||
| } | ||||||
|
|
||||||
| (PoolId poolId, ShareClassId scId) = spoke.shareTokenDetails(token); | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: We should move this up before the actual transfer because it is theoretically fallible such that we can save costs in case this check fails. |
||||||
| GasLimits memory limits = gasLimits[poolId][scId]; | ||||||
|
|
||||||
| spoke.crosschainTransferShares{value: msg.value}( | ||||||
| centrifugeId, | ||||||
| poolId, | ||||||
| scId, | ||||||
| receiver, | ||||||
| uint128(amount), | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. CR: Should use safe method from Mathlib to improve debugging in case this edge case occurs
Suggested change
|
||||||
| limits.extraGasLimit, | ||||||
| limits.remoteExtraGasLimit, | ||||||
| relayer != address(0) ? relayer : refundAddress // Transfer remaining ETH to relayer if set | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not always to the |
||||||
| ); | ||||||
|
|
||||||
| return bytes(""); | ||||||
| } | ||||||
| } | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||
| pragma solidity >=0.5.0; | ||
|
|
||
| import {PoolId} from "../../core/types/PoolId.sol"; | ||
| import {ShareClassId} from "../../core/types/ShareClassId.sol"; | ||
| import {ITrustedContractUpdate} from "../../core/utils/interfaces/IContractUpdate.sol"; | ||
|
|
||
| interface ITokenBridge is ITrustedContractUpdate { | ||
| event File(bytes32 indexed what, address data); | ||
| event File(bytes32 indexed what, uint256 evmChainId, uint16 centrifugeId); | ||
| event UpdateGasLimits( | ||
| PoolId indexed poolId, ShareClassId indexed scId, uint128 extraGasLimit, uint128 remoteExtraGasLimit | ||
| ); | ||
|
|
||
| error FileUnrecognizedParam(); | ||
| error InvalidChainId(); | ||
| error InvalidRelayer(); | ||
|
Comment on lines
+16
to
+17
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wanted to flag these are not yet used in the codebase |
||
| error InvalidToken(); | ||
| error UnknownTrustedCall(); | ||
| error ShareTokenDoesNotExist(); | ||
| error FailedToTransferToRelayer(); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here |
||
|
|
||
| enum TrustedCall { | ||
| SetGasLimits | ||
| } | ||
|
|
||
| struct GasLimits { | ||
| uint128 extraGasLimit; | ||
| uint128 remoteExtraGasLimit; | ||
| } | ||
|
|
||
| //---------------------------------------------------------------------------------------------- | ||
| // Administration | ||
| //---------------------------------------------------------------------------------------------- | ||
|
|
||
| /// @notice Configure contract parameters | ||
| /// @param what The parameter name to configure | ||
| /// @param data The address value to set | ||
| function file(bytes32 what, address data) external; | ||
|
|
||
| /// @notice Configure chain ID mapping | ||
| /// @param what Must be "centrifugeId" | ||
| /// @param evmChainId The EVM chain ID | ||
| /// @param centrifugeId The corresponding Centrifuge chain ID | ||
| function file(bytes32 what, uint256 evmChainId, uint16 centrifugeId) external; | ||
|
|
||
| //---------------------------------------------------------------------------------------------- | ||
| // Bridging | ||
| //---------------------------------------------------------------------------------------------- | ||
|
|
||
| /// @notice Send a token from chain A to chain B after approving this contract with the token | ||
| /// @dev This function transfers tokens from the caller and initiates a cross-chain transfer | ||
| /// @dev These methods match the expected interface from Glacis Airlift for cross-chain token transfers | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe then we should move Do you have a reference for where this method is defined? |
||
| /// @param token The address of the token sending across chains | ||
| /// @param amount The amount of the token to send across chains | ||
| /// @param receiver The target address that should receive the funds on the destination chain | ||
| /// @param destinationChainId The Ethereum chain ID of the destination chain | ||
| /// @param refundAddress The address that should receive any funds if the cross-chain gas value is too high | ||
| /// @return sendResponse The response from the token's handler function (not standardized) | ||
| function send(address token, uint256 amount, bytes32 receiver, uint256 destinationChainId, address refundAddress) | ||
| external | ||
| payable | ||
| returns (bytes memory); | ||
|
|
||
| //---------------------------------------------------------------------------------------------- | ||
| // View methods | ||
| //---------------------------------------------------------------------------------------------- | ||
|
|
||
| /// @notice Returns the relayer address | ||
| /// @return The relayer address | ||
| function relayer() external view returns (address); | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice idea to reduce bytecode!