Files
2024-10-15 17:07:13 -04:00

505 lines
18 KiB
Solidity

/***
*Submitted for verification at basescan.org on 2024-03-20
*/
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
/**
██╗ ██╗ ██╗██╗ ██╗ ██████╗ ██████╗ ██╗██████╗ ██████╗ ███████╗
██║ ██║ ██║╚██╗██╔╝ ██╔══██╗██╔══██╗██║██╔══██╗██╔════╝ ██╔════╝
██║ ██║ ██║ ╚███╔╝ ██████╔╝██████╔╝██║██║ ██║██║ ███╗█████╗
██║ ██║ ██║ ██╔██╗ ██╔══██╗██╔══██╗██║██║ ██║██║ ██║██╔══╝
███████╗╚██████╔╝██╔╝ ██╗ ██████╔╝██║ ██║██║██████╔╝╚██████╔╝███████╗
╚══════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝╚═════╝ ╚═════╝ ╚══════╝
*/
import "./ERC20B.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
import {LuxVault} from "./LuxVault.sol";
import "hardhat/console.sol";
contract Bridge is Ownable, AccessControl {
// Use the library functions from OpenZeppelin
using Strings for uint256;
uint256 public feeRate = 100; // Fee rate 1% decimals 4
address internal payoutAddress;
LuxVault public vault;
/** Events */
event BridgeBurned(address caller, uint256 amt, address token);
event VaultDeposit(address depositor, uint256 amt, address token);
event VaultWithdraw(address receiver, uint256 amt, address token);
event BridgeMinted(address recipient, address token, uint256 amt);
event BridgeWithdrawn(address recipient, address token, uint256 amt);
event AdminGranted(address to);
event AdminRevoked(address to);
event SigMappingAdded(bytes _key);
event NewMPCOracleSet(address MPCOracle);
constructor() Ownable(msg.sender) {
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
}
/**
* @dev Sets admins
*/
modifier onlyAdmin() {
require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender), "Ownable");
_;
}
/**
* @dev Grants admins
* @param to_ admin address
*/
function grantAdmin(address to_) public onlyAdmin {
grantRole(DEFAULT_ADMIN_ROLE, to_);
emit AdminGranted(to_);
}
/**
* @dev Revoke admins
* @param to_ admin address
*/
function revokeAdmin(address to_) public onlyAdmin {
require(hasRole(DEFAULT_ADMIN_ROLE, to_), "Ownable");
revokeRole(DEFAULT_ADMIN_ROLE, to_);
emit AdminRevoked(to_);
}
/**
* @dev Set fee payout addresses and fee - set at contract launch - in wei
* @param payoutAddress_ payout address
* @param feeRate_ fee rate for bridge fee
*/
function setpayoutAddressess(
address payoutAddress_,
uint256 feeRate_
) public onlyAdmin {
payoutAddress = payoutAddress_;
feeRate = feeRate_;
}
/**
* @dev Mappings
*/
struct MPCOracleAddrInfo {
bool exists;
}
/**
* @dev Map MPCOracle address at blockHeight
*/
mapping(address => MPCOracleAddrInfo) internal MPCOracleAddrMap;
function addMPCMapping(address key_) internal {
MPCOracleAddrMap[key_].exists = true;
}
/**
* @dev Used to set a new MPC address at block height - only MPC signers can update
* @param MPCO_ new mpc oracle signer address
*/
function setMPCOracle(address MPCO_) public onlyAdmin {
addMPCMapping(MPCO_); // store in mapping.
emit NewMPCOracleSet(MPCO_);
}
/**
* @dev Get MPC Data Transaction
* @param key_ transaction hash
* @return boolean true if mpc signer address exists
*/
function getMPCMapDataTx(address key_) public view returns (bool) {
return MPCOracleAddrMap[key_].exists;
}
/**
* @dev Struct for mapping transaction history
*/
struct TransactionInfo {
string txid;
bool exists;
//bool isStealth;
}
mapping(bytes => TransactionInfo) internal transactionMap;
/**
* @dev add transaction id to mappding to prevent replay attack
* @param key_ transaction hash
*/
function addMappingStealth(bytes memory key_) internal {
require(!transactionMap[key_].exists, "Already exist");
transactionMap[key_].exists = true;
emit SigMappingAdded(key_);
}
/**
* @dev check if transaction already exists
* @param key_ transaction hash
* @return boolean
*/
function keyExistsTx(bytes memory key_) public view returns (bool) {
return transactionMap[key_].exists;
}
/**
* @dev Teleport bridge data structure
*/
struct TeleportStruct {
bytes32 networkIdHash;
bytes32 tokenAddressHash;
string tokenAmount;
address receiverAddress;
bytes32 receiverAddressHash;
string decimals;
ERC20B token;
}
/**
* @dev set vault address for teleport bridge
* @param vault_ new vault address
*/
function setVault(address payable vault_) public onlyAdmin {
require(vault_ != address(0), "Invalid address");
vault = LuxVault(vault_);
}
/**
* @dev add new ERC20 vault
* @param asset_ new vault address
*/
function addNewERC20Vault(address asset_) public onlyAdmin {
vault.addNewERC20Vault(asset_);
}
/**
* @dev Transfers the msg.senders coins to Lux vault
* @param amount_ token amount to transfer
* @param tokenAddr_ token address to transfer
*/
function vaultDeposit(uint256 amount_, address tokenAddr_) public payable {
if (tokenAddr_ != address(0)) {
IERC20(tokenAddr_).transferFrom(
msg.sender,
address(vault),
amount_
);
}
vault.deposit{value: msg.value}(tokenAddr_, amount_);
emit VaultDeposit(msg.sender, amount_, tokenAddr_);
}
/**
* @dev Withdraw tokens from vault using MPC signed msg
* @param amount_ token amount to withdraw
* @param tokenAddr_ token address to withdraw
* @param receiver_ receiver's address
*/
function vaultWithdraw(
uint256 amount_,
address tokenAddr_,
address receiver_
) private {
address _shareAddress;
if (tokenAddr_ == address(0)) {
_shareAddress = vault.ethVaultAddress();
} else {
_shareAddress = vault.erc20Vault(tokenAddr_);
}
IERC20(_shareAddress).approve(address(vault), type(uint256).max);
vault.withdraw(tokenAddr_, receiver_, amount_);
emit VaultWithdraw(receiver_, amount_, tokenAddr_);
}
/**
* @dev preview vault withdraw
* @param amount_ token amount to withdraw
* @param tokenAddr_ token address to withdraw
*/
function previewVaultWithdraw(
uint256 amount_,
address tokenAddr_
) public view returns (bool) {
return vault.previewWithdraw(tokenAddr_, amount_);
}
/**
* @dev Burns the msg.senders coins
* @param amount_ token amount to burn
* @param tokenAddr_ token address to burn
*/
function bridgeBurn(uint256 amount_, address tokenAddr_) public {
TeleportStruct memory teleport;
teleport.token = ERC20B(tokenAddr_);
require((teleport.token.balanceOf(msg.sender) >= amount_), "Insufficient token balance");
teleport.token.bridgeBurn(msg.sender, amount_);
emit BridgeBurned(msg.sender, amount_, tokenAddr_);
}
/**
* @dev Concat data to sign
* @param amt_ token amount
* @param toTargetAddrStr_ target address to mint
* @param txid_ tx hash
* @param tokenAddrStrHash_ hashed token address
* @param chainIdStr_ chain id
* @param decimalStr_ decimal of source token
* @param vault_ usage of valult
*/
function append(
string memory amt_,
string memory toTargetAddrStr_,
string memory txid_,
string memory tokenAddrStrHash_,
string memory chainIdStr_,
string memory decimalStr_,
string memory vault_
) internal pure returns (string memory) {
return
string(
abi.encodePacked(
amt_,
toTargetAddrStr_,
txid_,
tokenAddrStrHash_,
chainIdStr_,
decimalStr_,
vault_
)
);
}
/**
* @dev split ECDSA signature to r, s, v
* @param sig ECDSA signature
* @return splitted_ v,s,r
*/
function splitSignature(
bytes memory sig
) internal pure returns (uint8, bytes32, bytes32) {
require(sig.length == 65, "invalid Length");
bytes32 r;
bytes32 s;
uint8 v;
assembly {
// first 32 bytes, after the length prefix
r := mload(add(sig, 32))
// second 32 bytes
s := mload(add(sig, 64))
// final byte (first byte of the next 32 bytes)
v := byte(0, mload(add(sig, 96)))
}
return (v, r, s);
}
/**
* @dev recover signer from message and ECDSA signature
* @param message_ message to be signed
* @param sig_ ECDSA signature
* @return signer signer of ECDSA
*/
function recoverSigner(
bytes32 message_,
bytes memory sig_
) internal pure returns (address) {
uint8 v;
bytes32 r;
bytes32 s;
(v, r, s) = splitSignature(sig_);
return ecrecover(message_, v, r, s);
}
/**
* @dev Builds a prefixed hash to mimic the behavior of eth_sign
* @return prefixed prefixed msg
*/
function prefixed(bytes32 hash) internal pure returns (bytes32) {
return
keccak256(
abi.encodePacked("\x19Ethereum Signed Message:\n32", hash)
);
}
/**
* @dev Sig is specific to recipient target address, hashed txid, amount, block height, target token and target chain
* @notice Sets the vault address. Sig can only be claimed once.
* @param hashedTxId_ hashed tx id in source chain
* @param toTokenAddress_ token address in destination chain
* @param tokenAmount_ token amount that is transfered to lux vault in source chain
* @param fromTokenDecimals_ token decimal of source token
* @param receiverAddress_ destinatoin address to mint in destination chain
* @param signedTXInfo_ mpc signed msg from teleport oracle network
* @param vault_ if usage of vault
* @return signer return signer address of message
*/
function bridgeMintStealth(
string memory hashedTxId_,
address toTokenAddress_,
uint256 tokenAmount_,
uint256 fromTokenDecimals_,
address receiverAddress_,
bytes memory signedTXInfo_,
string memory vault_
) public returns (address) {
TeleportStruct memory teleport;
// Hash calculations
teleport.tokenAddressHash = keccak256(abi.encodePacked(toTokenAddress_));
teleport.token = ERC20B(toTokenAddress_);
teleport.receiverAddress = receiverAddress_;
teleport.receiverAddressHash = keccak256(
abi.encodePacked(receiverAddress_)
);
teleport.tokenAmount = Strings.toString(tokenAmount_);
teleport.decimals = Strings.toString(fromTokenDecimals_);
teleport.networkIdHash = keccak256(abi.encodePacked(block.chainid.toString()));
// Concatenate message
string memory message = append(
Strings.toHexString(uint256(teleport.networkIdHash), 32),
hashedTxId_,
Strings.toHexString(uint256(teleport.tokenAddressHash), 32),
teleport.tokenAmount,
teleport.decimals,
Strings.toHexString(uint256(teleport.receiverAddressHash), 32),
vault_
);
console.log("::hashedTxId_", hashedTxId_);
console.log("::toTokenAddress_", toTokenAddress_);
console.log("::tokenAmount", tokenAmount_);
console.log("::fromTokenDecimals_", fromTokenDecimals_);
console.log("::receiverAddress_", receiverAddress_);
console.log("::vault_", vault_);
console.log("::message", message);
// Check if signedTXInfo already exists
require(!transactionMap[signedTXInfo_].exists, "DupeTX");
address signer = recoverSigner(
prefixed(keccak256(abi.encodePacked(message))),
signedTXInfo_
);
console.log("signer", signer);
// Check if signer is MPCOracle and corresponds to the correct ERC20B
// require(MPCOracleAddrMap[signer].exists, "BadSig");
// Calculate fee and adjust amount
uint256 _amount_ = (tokenAmount_ * 10 ** 18) / (10 ** fromTokenDecimals_);
uint256 _bridgeFee = (_amount_ * feeRate) / 10 ** 4;
uint256 _adjustedAmount = _amount_ - _bridgeFee; // Use a local variable
console.log("::bridgeFee", _bridgeFee);
console.log("::adjustedAmount", _adjustedAmount);
// If correct signer, then payout
// teleport.token.bridgeMint(payoutAddress, _bridgeFee);
// teleport.token.bridgeMint(teleport.receiverAddress, _adjustedAmount);
// Add new transaction ID mapping
addMappingStealth(signedTXInfo_);
emit BridgeMinted(teleport.receiverAddress, toTokenAddress_, _adjustedAmount);
return signer;
}
// /**
// * @dev Sig is specific to recipient target address, hashed txid, amount, block height, target token and target chain
// * @notice Sets the vault address. Sig can only be claimed once.
// * @param amt_ token amount that is burnt in source chain
// * @param hashedId_ hashed tx id in source chain
// * @param toTargetAddrStr_ destinatoin address to withdraw in destination chain
// * @param signedTXInfo_ mpc signed msg from teleport oracle network
// * @param tokenAddrStr_ token address in destination chain
// * @param chainId_ chain id
// * @param fromTokenDecimal_ token decimal of source token
// * @param vault_ if usage of vault
// * @return signer return signer address of message
// */
// function bridgeWithdrawStealth(
// uint256 amt_,
// string memory hashedId_,
// address toTargetAddrStr_,
// bytes memory signedTXInfo_,
// address tokenAddrStr_,
// string memory chainId_,
// uint256 fromTokenDecimal_,
// string memory vault_
// ) public returns (address) {
// TeleportStruct memory teleport;
// // Hash calculations
// teleport.tokenAddrHash = keccak256(abi.encodePacked(tokenAddrStr_));
// teleport.toTargetAddr = toTargetAddrStr_;
// teleport.toTargetAddrStrHash = keccak256(
// abi.encodePacked(toTargetAddrStr_)
// );
// teleport.amtStr = Strings.toString(amt_);
// teleport.decimalStr = Strings.toString(fromTokenDecimal_);
// teleport.toChainIdHash = keccak256(abi.encodePacked(chainId_));
// // Concatenate message
// string memory message = append(
// teleport.amtStr,
// Strings.toHexString(uint256(teleport.toTargetAddrStrHash), 32),
// hashedId_,
// Strings.toHexString(uint256(teleport.tokenAddrHash), 32),
// Strings.toHexString(uint256(teleport.toChainIdHash), 32),
// teleport.decimalStr,
// vault_
// );
// // Check if signedTXInfo already exists
// require(!transactionMap[signedTXInfo_].exists, "DupeTX");
// address signer = recoverSigner(
// prefixed(keccak256(abi.encodePacked(message))),
// signedTXInfo_
// );
// // Check if signer is MPCOracle and corresponds to the correct ERC20B
// require(MPCOracleAddrMap[signer].exists, "BadSig");
// uint256 amount_ = amt_;
// if (tokenAddrStr_ != address(0)) {
// amount_ =
// (amt_ * 10 ** ERC20(tokenAddrStr_).decimals()) /
// (10 ** fromTokenDecimal_);
// }
// vaultWithdraw(amount_, tokenAddrStr_, msg.sender);
// // Add new transaction ID mapping
// addMappingStealth(signedTXInfo_);
// emit BridgeWithdrawn(msg.sender, tokenAddrStr_, amount_);
// // return signer;
// return signer;
// }
/**
* @dev send funds
* @param amount_ token amount
* @param tokenAddr_ token address
* @param receiver_ receiver address
*/
function pullWithdraw(
uint256 amount_,
address tokenAddr_,
address receiver_
) external onlyOwner {
address shareAddress;
if (tokenAddr_ == address(0)) {
shareAddress = vault.ethVaultAddress();
} else {
shareAddress = vault.erc20Vault(tokenAddr_);
}
IERC20(shareAddress).approve(address(vault), type(uint256).max);
vault.withdraw(tokenAddr_, receiver_, amount_);
emit VaultWithdraw(receiver_, amount_, tokenAddr_);
}
receive() external payable {}
}