Skip to content

Commit

Permalink
Merge pull request #35 from helix-bridge/xiaoch05-eth2arbi
Browse files Browse the repository at this point in the history
eth2arb ln bridge
  • Loading branch information
xiaoch05 authored Aug 7, 2023
2 parents da21651 + e7a46e3 commit f26c984
Show file tree
Hide file tree
Showing 13 changed files with 5,216 additions and 72 deletions.
4 changes: 3 additions & 1 deletion helix-contract/address/arbi2eth-ln-dev.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@
"LnBridgeLogic": "0x0BA214a9Ab958C1A19D913f2Ac00119d27f196bB",
"LnBridgeProxy": "0x3B1A953bFa72Af4ae3494b08e453BFF30a06A550",
"Ring": "0x1836BAFa3016Dd5Ce543D0F7199cB858ec69F41E",
"USDC": "0xd35CCeEAD182dcee0F148EbaC9447DA2c4D449c4",
"Inbox": "0x6BEbC4925716945D46F0Ec336D5C2564F419682C"
},
"arbitrum2ethereumLnV2-arbitrum-goerli": {
"LnBridgeProxyAdmin": "0x66d86a686e50c98bac236105efafb99ee7605dc5",
"LnBridgeLogic": "0xBFA90e358a9B2218ceb900afD9ac78691C92ABa6",
"LnBridgeProxy": "0x7B8413FA1c1033844ac813A2E6475E15FB0fb3BA",
"Ring": "0xFBAD806Bdf9cEC2943be281FB355Da05068DE925"
"Ring": "0xFBAD806Bdf9cEC2943be281FB355Da05068DE925",
"USDC": "0xEA70a40Df1432A1b38b916A51Fb81A4cc805a963"
}
}
17 changes: 17 additions & 0 deletions helix-contract/address/eth2arbi-ln-dev.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"ethereum2arbitrumLnV2-goerli": {
"LnBridgeProxyAdmin": "0x3F3eDBda6124462a09E071c5D90e072E0d5d4ed4",
"LnBridgeLogic": "0x26F3925F0cbcf79bF8Ca9041347327b82c55916b",
"LnBridgeProxy": "0xcD86cf37a4Dc6f78B4899232E7dD1b5c8130EFDA",
"Ring": "0x1836BAFa3016Dd5Ce543D0F7199cB858ec69F41E",
"USDC": "0xd35CCeEAD182dcee0F148EbaC9447DA2c4D449c4",
"Inbox": "0x6BEbC4925716945D46F0Ec336D5C2564F419682C"
},
"ethereum2arbitrumLnV2-arbitrum-goerli": {
"LnBridgeProxyAdmin": "0x66d86a686e50c98bac236105efafb99ee7605dc5",
"LnBridgeLogic": "0xC91aff6adA5e743Ae89589126AE4521eB2ec47f2",
"LnBridgeProxy": "0x4112c9d474951246fBC2B4D868D247e714698aE1",
"Ring": "0xFBAD806Bdf9cEC2943be281FB355Da05068DE925",
"USDC": "0xEA70a40Df1432A1b38b916A51Fb81A4cc805a963"
}
}
2 changes: 2 additions & 0 deletions helix-contract/contracts/ln/Eth2ArbSource.sol
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ contract Eth2ArbSource is Initializable, LnAccessController, LnDefaultBridgeSour
uint64 withdrawNonce,
address provider,
address sourceToken,
address targetToken,
uint112 amount,
uint256 percentIncrease
) external view returns(uint256) {
Expand All @@ -78,6 +79,7 @@ contract Eth2ArbSource is Initializable, LnAccessController, LnDefaultBridgeSour
withdrawNonce,
provider,
sourceToken,
targetToken,
amount
);
uint256 fee = inbox.calculateRetryableSubmissionFee(withdrawCall.length, baseFee);
Expand Down
8 changes: 5 additions & 3 deletions helix-contract/contracts/ln/Eth2ArbTarget.sol
Original file line number Diff line number Diff line change
Expand Up @@ -40,17 +40,19 @@ contract Eth2ArbTarget is Initializable, LnAccessController, LnDefaultBridgeTarg
params,
slasher,
fee,
penalty);
penalty
);
}

function withdrawMargin(
function withdraw(
bytes32 lastTransferId,
uint64 withdrawNonce,
address provider,
address sourceToken,
address targetToken,
uint112 amount
) external onlyRemoteBridge whenNotPaused {
_withdraw(lastTransferId, withdrawNonce, provider, sourceToken, amount);
_withdraw(lastTransferId, withdrawNonce, provider, sourceToken, targetToken, amount);
}
}

12 changes: 10 additions & 2 deletions helix-contract/contracts/ln/base/LnBridgeHelper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,18 @@ contract LnBridgeHelper {
require(success && (data.length == 0 || abi.decode(data, (bool))), "lnBridgeHelper:transferFrom token failed");
}

function getProviderKey(address provider, address token) pure public returns(bytes32) {
function getProviderKey(address provider, address sourceToken) pure public returns(bytes32) {
return keccak256(abi.encodePacked(
provider,
token
sourceToken
));
}

function getDefaultProviderKey(address provider, address sourceToken, address targetToken) pure public returns(bytes32) {
return keccak256(abi.encodePacked(
provider,
sourceToken,
targetToken
));
}
}
Expand Down
107 changes: 73 additions & 34 deletions helix-contract/contracts/ln/base/LnDefaultBridgeSource.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,23 @@ import "../interface/ILnDefaultBridgeTarget.sol";
/// @title LnPositiveBridgeSource
/// @notice LnPositiveBridgeSource is a contract to help user transfer token to liquidity node and generate proof,
/// then the liquidity node must transfer the same amount of the token to the user on target chain.
/// Otherwise if timeout the slasher can paid for relayer and slash the transfer, then request refund from lnProvider's margin.
/// Otherwise if timeout the slasher can send a slash request message to target chain, then force transfer from lnProvider's margin to the user.
/// @dev See https://github.com/helix-bridge/contracts/tree/master/helix-contract
contract LnDefaultBridgeSource is LnBridgeHelper {
// the time(seconds) for liquidity provider to delivery message
// if timeout, slasher can work.
uint256 constant public MIN_SLASH_TIMESTAMP = 30 * 60;
// liquidity fee base rate
// liquidityFee = liquidityFeeRate / LIQUIDITY_FEE_RATE_BASE * sendAmount
uint256 constant public LIQUIDITY_FEE_RATE_BASE = 100000;
// max transfer amount one time
uint256 constant public MAX_TRANSFER_AMOUNT = type(uint112).max;
// the registered token info
// sourceToken and targetToken is the pair of erc20 token addresses
// if sourceToken == address(0), then it's native token
// if targetToken == address(0), then remote is native token
// * `protocolFee` is the protocol fee charged by system
// * `penaltyLnCollateral` is penalty from lnProvider when the transfer refund, if we adjust this value, it'll not affect the old transfers.
// * `penaltyLnCollateral` is penalty from lnProvider when the transfer slashed, if we adjust this value, it'll not affect the old transfers.
struct TokenInfo {
address targetToken;
uint112 protocolFee;
Expand All @@ -29,6 +34,9 @@ contract LnDefaultBridgeSource is LnBridgeHelper {
bool isRegistered;
}

// provider fee is paid to liquidity node's account
// the fee is charged by the same token that user transfered
// providerFee = baseFee + liquidityFeeRate/LIQUIDITY_FEE_RATE_BASE * sendAmount
struct LnProviderFee {
uint112 baseFee;
uint8 liquidityFeeRate;
Expand All @@ -42,9 +50,9 @@ contract LnDefaultBridgeSource is LnBridgeHelper {
}
// the Snapshot is the state of the token bridge when user prepare to transfer across chains.
// If the snapshot updated when the across chain transfer confirmed, it will
// 1. if lastTransferId updated, revert
// 2. if margin decrease or totalFee increase, revert
// 3. if margin increase or totalFee decrease, success
// 1. if lastTransferId or withdrawNonce updated, revert
// 2. if totalFee increase, revert
// 3. if totalFee decrease, success
struct Snapshot {
address provider;
address sourceToken;
Expand All @@ -53,6 +61,8 @@ contract LnDefaultBridgeSource is LnBridgeHelper {
uint64 withdrawNonce;
}

// lock info
// the fee and penalty is the state of the transfer confirmed
struct LockInfo {
uint112 fee;
uint112 penalty;
Expand All @@ -63,7 +73,6 @@ contract LnDefaultBridgeSource is LnBridgeHelper {
// providerKey => provider info
mapping(bytes32=>LnProviderInfo) public lnProviders;
// transferId => lock info
// hash(provider, token, amount, lastId, recipient, timestamp) => timestamp
mapping(bytes32=>LockInfo) public lockInfos;

address public protocolFeeReceiver;
Expand All @@ -75,16 +84,16 @@ contract LnDefaultBridgeSource is LnBridgeHelper {
uint112 amount,
uint112 fee,
address receiver);
event LiquidityWithdrawn(address provider, address sourceToken, uint112 amount);
event Refund(bytes32 transferId, uint64 providerKey, address provider, uint112 margin, address slasher);
// relayer
event LnProviderUpdated(address provider, address sourceToken, uint112 baseFee, uint8 liquidityfeeRate);

// protocolFeeReceiver is the protocol fee reciever, we don't use the contract itself as the receiver
function _setFeeReceiver(address _feeReceiver) internal {
require(_feeReceiver != address(this), "lnBridgeSource:invalid system fee receiver");
require(_feeReceiver != address(this), "invalid system fee receiver");
protocolFeeReceiver = _feeReceiver;
}

// register or update token info, it can be only called by contract owner
// source token can only map a unique target token on target chain
function _setTokenInfo(
address _sourceToken,
address _targetToken,
Expand All @@ -103,14 +112,17 @@ contract LnDefaultBridgeSource is LnBridgeHelper {
);
}

// lnProvider can set provider fee on source chain
// and it must register on target chain to deposit margin
// lnProvider register
// 1. set fee on source chain
// 2. deposit margin on target chain
function setProviderFee(
address sourceToken,
uint112 baseFee,
uint8 liquidityFeeRate
) external {
bytes32 providerKey = getProviderKey(msg.sender, sourceToken);
TokenInfo memory tokenInfo = tokenInfos[sourceToken];
require(tokenInfo.isRegistered, "token not registered");
bytes32 providerKey = getDefaultProviderKey(msg.sender, sourceToken, tokenInfo.targetToken);
LnProviderFee memory providerFee = LnProviderFee(baseFee, liquidityFeeRate);

// we only update the field fee of the provider info
Expand All @@ -128,7 +140,7 @@ contract LnDefaultBridgeSource is LnBridgeHelper {
// totalFee = providerFee + protocolFee
function totalFee(address provider, address sourceToken, uint112 amount) external view returns(uint256) {
TokenInfo memory tokenInfo = tokenInfos[sourceToken];
bytes32 providerKey = getProviderKey(provider, sourceToken);
bytes32 providerKey = getDefaultProviderKey(provider, sourceToken, tokenInfo.targetToken);
LnProviderInfo memory providerInfo = lnProviders[providerKey];
uint256 providerFee = calculateProviderFee(providerInfo.fee, amount);
return providerFee + tokenInfo.protocolFee;
Expand All @@ -137,38 +149,39 @@ contract LnDefaultBridgeSource is LnBridgeHelper {
// This function transfers tokens from the user to LnProvider and generates a proof on the source chain.
// The snapshot represents the state of the LN bridge for this LnProvider, obtained by the off-chain indexer.
// If the chain state is updated and does not match the snapshot state, the transaction will be reverted.
// 1. the state(lastTransferId, fee, margin) must match snapshot
// 1. the state(lastTransferId, fee, withdrawNonce) must match snapshot
// 2. transferId not exist
function transferAndLockMargin(
Snapshot calldata snapshot,
uint112 amount,
address receiver
) external payable {
require(amount > 0, "lnBridgeSource:invalid amount");
bytes32 providerKey = getProviderKey(snapshot.provider, snapshot.sourceToken);

LnProviderInfo memory providerInfo = lnProviders[providerKey];
uint256 providerFee = calculateProviderFee(providerInfo.fee, amount);
require(amount > 0, "invalid amount");

TokenInfo memory tokenInfo = tokenInfos[snapshot.sourceToken];
require(tokenInfo.isRegistered, "token not registered");

bytes32 providerKey = getDefaultProviderKey(snapshot.provider, snapshot.sourceToken, tokenInfo.targetToken);

LnProviderInfo memory providerInfo = lnProviders[providerKey];
uint256 providerFee = calculateProviderFee(providerInfo.fee, amount);

// the chain state not match snapshot
require(providerInfo.lastTransferId == snapshot.transferId, "snapshot expired:transfer");
require(snapshot.withdrawNonce == providerInfo.withdrawNonce, "snapshot expired:withdraw");
require(snapshot.totalFee >= providerFee + tokenInfo.protocolFee && providerFee > 0, "fee is invalid");

uint256 targetAmount = uint256(amount) * 10**tokenInfo.targetDecimals / 10**tokenInfo.sourceDecimals;
require(targetAmount < MAX_TRANSFER_AMOUNT, "lnBridgeSource:overflow amount");
uint112 targetAmount = _sourceAmountToTargetAmount(tokenInfo, uint256(amount));
bytes32 transferId = keccak256(abi.encodePacked(
snapshot.transferId,
snapshot.provider,
snapshot.sourceToken,
tokenInfo.targetToken,
receiver,
uint64(block.timestamp),
uint112(targetAmount)));
require(!lockInfos[transferId].isLocked, "lnBridgeSource:transferId exist");
targetAmount
));
require(!lockInfos[transferId].isLocked, "transferId exist");
// if the transfer refund, then the fee and penalty should be given to slasher, but the protocol fee is ignored
// and we use the penalty value configure at the moment transfer confirmed
lockInfos[transferId] = LockInfo(snapshot.totalFee, tokenInfo.penaltyLnCollateral, true);
Expand All @@ -177,11 +190,15 @@ contract LnDefaultBridgeSource is LnBridgeHelper {
lnProviders[providerKey].lastTransferId = transferId;

if (snapshot.sourceToken == address(0)) {
require(amount + snapshot.totalFee == msg.value, "lnBridgeSource:amount unmatched");
require(amount + snapshot.totalFee == msg.value, "amount unmatched");
payable(snapshot.provider).transfer(amount + providerFee);
if (tokenInfo.protocolFee > 0) {
payable(protocolFeeReceiver).transfer(tokenInfo.protocolFee);
}
uint256 refund = snapshot.totalFee - tokenInfo.protocolFee - providerFee;
if ( refund > 0 ) {
payable(msg.sender).transfer(refund);
}
} else {
_safeTransferFrom(
snapshot.sourceToken,
Expand All @@ -202,16 +219,28 @@ contract LnDefaultBridgeSource is LnBridgeHelper {
transferId,
snapshot.provider,
snapshot.sourceToken,
amount,
targetAmount,
uint112(providerFee),
receiver);
}

function _sourceAmountToTargetAmount(
TokenInfo memory tokenInfo,
uint256 amount
) internal pure returns(uint112) {
uint256 targetAmount = amount * 10**tokenInfo.targetDecimals / 10**tokenInfo.sourceDecimals;
require(targetAmount < MAX_TRANSFER_AMOUNT, "overflow amount");
return uint112(targetAmount);
}

function _slashAndRemoteRelease(
TransferParameter memory params,
bytes32 expectedTransferId
) internal view returns(bytes memory message) {
require(block.timestamp > params.timestamp + MIN_SLASH_TIMESTAMP, "invalid timestamp");
TokenInfo memory tokenInfo = tokenInfos[params.sourceToken];
require(tokenInfo.isRegistered, "token not registered");
uint112 targetAmount = _sourceAmountToTargetAmount(tokenInfo, uint256(params.amount));

bytes32 transferId = keccak256(abi.encodePacked(
params.previousTransferId,
Expand All @@ -220,32 +249,40 @@ contract LnDefaultBridgeSource is LnBridgeHelper {
params.targetToken,
params.receiver,
params.timestamp,
params.amount));
targetAmount
));
require(expectedTransferId == transferId, "expected transfer id not match");
LockInfo memory lockInfo = lockInfos[transferId];
require(lockInfo.isLocked && params.timestamp > 0, "lock info not match");
require(lockInfo.isLocked, "lock info not match");
uint112 targetFee = _sourceAmountToTargetAmount(tokenInfo, lockInfo.fee);
uint112 targetPenalty = _sourceAmountToTargetAmount(tokenInfo, lockInfo.penalty);

message = _encodeSlashCall(
params,
msg.sender,
lockInfo.fee,
lockInfo.penalty
targetFee,
targetPenalty
);
}

function _withdrawMargin(
address sourceToken,
uint112 amount
) internal returns(bytes memory message) {
bytes32 providerKey = getProviderKey(msg.sender, sourceToken);
TokenInfo memory tokenInfo = tokenInfos[sourceToken];
require(tokenInfo.isRegistered, "token not registered");

bytes32 providerKey = getDefaultProviderKey(msg.sender, sourceToken, tokenInfo.targetToken);
LnProviderInfo memory providerInfo = lnProviders[providerKey];
lnProviders[providerKey].withdrawNonce += 1;
lnProviders[providerKey].withdrawNonce = providerInfo.withdrawNonce + 1;
uint112 targetAmount = _sourceAmountToTargetAmount(tokenInfo, amount);
message = _encodeWithdrawCall(
providerInfo.lastTransferId,
providerInfo.withdrawNonce,
providerInfo.withdrawNonce + 1,
msg.sender,
sourceToken,
amount
tokenInfo.targetToken,
targetAmount
);
}

Expand All @@ -269,6 +306,7 @@ contract LnDefaultBridgeSource is LnBridgeHelper {
uint64 withdrawNonce,
address provider,
address sourceToken,
address targetToken,
uint112 amount
) internal pure returns(bytes memory message) {
return abi.encodeWithSelector(
Expand All @@ -277,6 +315,7 @@ contract LnDefaultBridgeSource is LnBridgeHelper {
withdrawNonce,
provider,
sourceToken,
targetToken,
amount
);
}
Expand Down
Loading

0 comments on commit f26c984

Please sign in to comment.