initial commit
This commit is contained in:
commit
82d07db5e6
|
@ -0,0 +1,5 @@
|
||||||
|
build
|
||||||
|
node_modules
|
||||||
|
.idea
|
||||||
|
.arcconfig
|
||||||
|
*.iml
|
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"manifestVersion": "2.2",
|
||||||
|
"contracts": {},
|
||||||
|
"dependencies": {},
|
||||||
|
"name": "wormhole",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"compiler": {
|
||||||
|
"compilerSettings": {
|
||||||
|
"optimizer": {
|
||||||
|
"enabled": false,
|
||||||
|
"runs": "200"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"typechain": {
|
||||||
|
"enabled": false
|
||||||
|
},
|
||||||
|
"manager": "openzeppelin",
|
||||||
|
"solcVersion": "0.6.12",
|
||||||
|
"artifactsDir": "build/contracts",
|
||||||
|
"contractsDir": "contracts"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,197 @@
|
||||||
|
// 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() public payable {
|
||||||
|
revert("please use lockETH to transfer ETH to Solana");
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeGuardianAdmin(address newAddress) public {
|
||||||
|
require(guardians.contains(msg.sender), "sender is not a guardian");
|
||||||
|
|
||||||
|
pendingGuardianTransfers[msg.sender] = newAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
function confirmGuardianAdminChange(address oldAddress) public {
|
||||||
|
require(pendingGuardianTransfers[oldAddress] == msg.sender, "no pending key change to this account");
|
||||||
|
|
||||||
|
// Swap guardian
|
||||||
|
require(guardians.remove(oldAddress), "account oldAddress is not a guardian");
|
||||||
|
require(guardians.add(msg.sender), "sender is already a guardian");
|
||||||
|
|
||||||
|
// Migrate authorizedSigner
|
||||||
|
address oldAuthorizedSigner = guardianToAuthorizedSigner[oldAddress];
|
||||||
|
authorizedSignerToGuardian[oldAuthorizedSigner] = msg.sender;
|
||||||
|
guardianToAuthorizedSigner[msg.sender] = oldAuthorizedSigner;
|
||||||
|
|
||||||
|
// Remove pending transfer
|
||||||
|
pendingGuardianTransfers[oldAddress] = address(0);
|
||||||
|
|
||||||
|
emit LogGuardianKeyChanged(
|
||||||
|
oldAddress,
|
||||||
|
msg.sender
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
);
|
||||||
|
require(
|
||||||
|
guardians.contains(authorizedSignerToGuardian[signer]),
|
||||||
|
"signature of non-guardian included"
|
||||||
|
);
|
||||||
|
|
||||||
|
for (uint j = 0; j < alreadySigned.length; j++) {
|
||||||
|
require(signer != alreadySigned[j], "multiple signatures of the same guardian included");
|
||||||
|
}
|
||||||
|
|
||||||
|
alreadySigned[i] = signer;
|
||||||
|
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");
|
||||||
|
|
||||||
|
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");
|
||||||
|
|
||||||
|
WETH(WETHAddress).deposit(msg.value);
|
||||||
|
emit LogTokensLocked(WETHAddress, recipient, msg.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
contract WETH is IERC20 {
|
||||||
|
function deposit() public payable {}
|
||||||
|
|
||||||
|
function withdraw(uint256 amount) public {}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
// contracts/Wormhole.sol
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
pragma solidity ^0.6.0;
|
||||||
|
pragma experimental ABIEncoderV2;
|
||||||
|
|
||||||
|
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
||||||
|
|
||||||
|
contract WrappedSPLToken is ERC20 {
|
||||||
|
constructor(address[] memory _guardians) public {
|
||||||
|
require(_guardians.length > 0, "no guardians specified");
|
||||||
|
|
||||||
|
for (uint i = 0; i < _guardians.length; i++) {
|
||||||
|
guardians.add(_guardians[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
module.exports = {
|
||||||
|
networks: {
|
||||||
|
development: {
|
||||||
|
protocol: 'http',
|
||||||
|
host: 'localhost',
|
||||||
|
port: 8545,
|
||||||
|
gas: 5000000,
|
||||||
|
gasPrice: 5e9,
|
||||||
|
networkId: '*',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"name": "wormhole",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "networks.js",
|
||||||
|
"dependencies": {},
|
||||||
|
"devDependencies": {
|
||||||
|
"@openzeppelin/cli": "^2.8.2",
|
||||||
|
"@openzeppelin/contracts": "^3.1.0"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC"
|
||||||
|
}
|
Loading…
Reference in New Issue