pyth-crosschain/ethereum/contracts/Wormhole.sol

177 lines
5.1 KiB
Solidity

// contracts/Wormhole.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
pragma experimental ABIEncoderV2;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import "@openzeppelin/contracts/utils/EnumerableSet.sol";
contract Wormhole {
using SafeERC20 for IERC20;
using EnumerableSet for EnumerableSet.AddressSet;
address constant WETHAddress = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
uint256 CHAIN_ID = 2;
struct Signature {
uint8 v;
bytes32 r;
bytes32 s;
}
event LogGuardianKeyChanged(
address indexed oldGuardian,
address indexed newGuardian
);
event LogTokensLocked(
address indexed token,
bytes32 indexed recipient,
uint256 amount
);
event LogTokensUnlocked(
address indexed token,
bytes32 indexed sender,
address indexed recipient,
uint256 amount
);
EnumerableSet.AddressSet private guardians;
mapping(address => address) pendingGuardianTransfers;
// Mappings guardian <=> authorized signer
mapping(address => address) guardianToAuthorizedSigner;
mapping(address => address) authorizedSignerToGuardian;
// Mapping of already completed transactions
mapping(bytes32 => bool) completedTransactions;
constructor(address[] memory _guardians) public {
require(_guardians.length > 0, "no guardians specified");
for (uint i = 0; i < _guardians.length; i++) {
guardians.add(_guardians[i]);
}
}
function changeAuthorizedSigner(address newSigner) public {
require(guardians.contains(msg.sender), "sender is not a guardian");
require(authorizedSignerToGuardian[msg.sender] == address(0), "new signer is already a signer");
// Unset old mapping
address oldAuthorizedSigner = guardianToAuthorizedSigner[msg.sender];
authorizedSignerToGuardian[oldAuthorizedSigner] = address(0);
// Add new mapping
authorizedSignerToGuardian[newSigner] = msg.sender;
guardianToAuthorizedSigner[msg.sender] = newSigner;
}
function unlockERC20(
address asset,
uint256 amount,
uint256 height,
bytes32 sender,
address recipient,
Signature[] calldata signatures
) public {
require(recipient != address(0), "assets should not be burned");
// unlock data structure
// asset 32bytes
// height uint256
// amount uint256
// target_chain 32bytes
// sender 32bytes
// recipient 32bytes
bytes32 hash = keccak256(
abi.encodePacked(
bytes32(uint256(asset)),
amount,
height,
bytes32(CHAIN_ID),
sender,
bytes32(uint256(recipient))
)
);
require(!completedTransactions[hash], "transfer was already executed");
uint nSignatures = 0;
address[] memory alreadySigned = new address[](signatures.length);
for (uint256 i = 0; i < signatures.length; i++) {
address signer = ecrecover(
hash,
signatures[i].v,
signatures[i].r,
signatures[i].s
);
address guardian = authorizedSignerToGuardian[signer];
require(
guardians.contains(authorizedSignerToGuardian[signer]),
"signature of non-guardian included"
);
for (uint j = 0; j < alreadySigned.length; j++) {
require(guardian != alreadySigned[j], "multiple signatures of the same guardian included");
}
alreadySigned[i] = guardian;
nSignatures++;
}
// Check whether the threshold was met
require(
nSignatures > 5,
"not enough valid signatures attached to unlock funds"
);
// Safely transfer tokens out
IERC20(asset).safeTransfer(recipient, amount);
// Set the transfer as completed
completedTransactions[hash] = true;
emit LogTokensUnlocked(asset, sender, recipient, amount);
}
function lockAssets(
address asset,
uint256 amount,
bytes32 recipient
) public {
require(amount != 0, "amount must not be 0");
// TODO handle tokens that subtract fees
IERC20(asset).safeTransferFrom(msg.sender, address(this), amount);
emit LogTokensLocked(asset, recipient, amount);
}
function lockETH(
bytes32 recipient
) public payable {
require(msg.value != 0, "amount must not be 0");
// Wrap tx value in WETH
WETH(WETHAddress).deposit{value : msg.value}();
// Log deposit of WETH
emit LogTokensLocked(WETHAddress, recipient, msg.value);
}
fallback() external payable {revert("please use lockETH to transfer ETH to Solana");}
receive() external payable {revert("please use lockETH to transfer ETH to Solana");}
}
interface WETH is IERC20 {
function deposit() external payable;
function withdraw(uint256 amount) external;
}