Skip to content
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

Add eNear to the omni bridge #77

Draft
wants to merge 1 commit into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
File renamed without changes.
3 changes: 2 additions & 1 deletion evm/bridge-token-factory/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ cache/
coverage/
coverage.json
.openzeppelin/
node_modules/
node_modules/
.env
36 changes: 33 additions & 3 deletions evm/bridge-token-factory/contracts/BridgeTokenFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ pragma solidity ^0.8.24;
import {AccessControlUpgradeable} from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import {ICustomMinter} from "./ICustomMinter.sol";

import "./BridgeToken.sol";
import "./SelectivePausableUpgradable.sol";
Expand Down Expand Up @@ -42,6 +44,8 @@ contract BridgeTokenFactory is
mapping(uint128 => bool) public claimedFee;
uint128 public initTransferNonce;

mapping(address => address) public customMinters;

bytes32 public constant PAUSABLE_ADMIN_ROLE = keccak256("PAUSABLE_ADMIN_ROLE");
uint constant UNPAUSED_ALL = 0;
uint constant PAUSED_INIT_TRANSFER = 1 << 0;
Expand Down Expand Up @@ -138,6 +142,20 @@ contract BridgeTokenFactory is
return _nearToEthToken[nearTokenId];
}

function addCustomToken(string calldata nearTokenId, address tokenAddress, address customMinter) external onlyRole(DEFAULT_ADMIN_ROLE) {
_isBridgeToken[tokenAddress] = true;
_ethToNearToken[tokenAddress] = nearTokenId;
_nearToEthToken[nearTokenId] = tokenAddress;
customMinters[tokenAddress] = customMinter;
}

function removeCustomToken(address tokenAddress) external onlyRole(DEFAULT_ADMIN_ROLE) {
delete _isBridgeToken[tokenAddress];
delete _nearToEthToken[_ethToNearToken[tokenAddress]];
delete _ethToNearToken[tokenAddress];
delete customMinters[tokenAddress];
}

function deployToken(bytes calldata signatureData, MetadataPayload calldata metadata) payable external returns (address) {
bytes memory borshEncoded = bytes.concat(
Borsh.encodeString(metadata.token),
Expand Down Expand Up @@ -225,8 +243,15 @@ contract BridgeTokenFactory is
revert InvalidSignature();
}

require(_isBridgeToken[_nearToEthToken[payload.token]], "ERR_NOT_BRIDGE_TOKEN");
BridgeToken(_nearToEthToken[payload.token]).mint(payload.recipient, payload.amount);
address tokenAddress = _nearToEthToken[payload.token];

require(_isBridgeToken[tokenAddress], "ERR_NOT_BRIDGE_TOKEN");

if (customMinters[tokenAddress] != address(0)) {
ICustomMinter(customMinters[tokenAddress]).mint(tokenAddress, payload.recipient, payload.amount);
} else {
BridgeToken(tokenAddress).mint(payload.recipient, payload.amount);
}

completedTransfers[payload.nonce] = true;

Expand Down Expand Up @@ -259,7 +284,12 @@ contract BridgeTokenFactory is

address tokenAddress = _nearToEthToken[token];

BridgeToken(tokenAddress).burn(msg.sender, amount);
if (customMinters[tokenAddress] != address(0)) {
IERC20(tokenAddress).transferFrom(msg.sender, customMinters[tokenAddress], amount);
ICustomMinter(customMinters[tokenAddress]).burn(tokenAddress, amount);
} else {
BridgeToken(tokenAddress).burn(msg.sender, amount);
}

uint256 extensionValue = msg.value - nativeFee;
initTransferExtension(initTransferNonce, token, amount, fee, nativeFee, recipient, msg.sender, extensionValue);
Expand Down
7 changes: 7 additions & 0 deletions evm/bridge-token-factory/contracts/ICustomMinter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.24;

interface ICustomMinter {
function mint(address token, address to, uint128 amount) external;
function burn(address token, uint128 amount) external;
}
Empty file added evm/eNear/.env.example
Empty file.
7 changes: 7 additions & 0 deletions evm/eNear/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
build/
cache/
coverage/
coverage.json
.openzeppelin/
node_modules/
.env
10 changes: 10 additions & 0 deletions evm/eNear/contracts/ENear.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.27;

interface ENear {
function transferToNear(uint256 _amount, string calldata _nearReceiverAccountId) external;
function finaliseNearToEthTransfer(bytes calldata proofData, uint64 proofBlockHeight) external;
function nominateAdmin(address newAdmin) external;
function acceptAdmin(address newAdmin) external;
function adminSstore(uint key, uint value) external;
}
54 changes: 54 additions & 0 deletions evm/eNear/contracts/ENearProxy.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.27;

import {AccessControlUpgradeable} from '@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol';
import {UUPSUpgradeable} from '@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol';
import {ENear} from './ENear.sol';

interface ICustomMinter {
function mint(address token, address to, uint128 amount) external;
function burn(address token, uint128 amount) external;
}

contract ENearProxy is UUPSUpgradeable, AccessControlUpgradeable {
ENear public eNear;

bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");

/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}

function initialize(address _eNear) public initializer {
__UUPSUpgradeable_init();
__AccessControl_init();
eNear = ENear(_eNear);
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
}

function mint(address, address to, uint128 amount) public onlyRole(MINTER_ROLE) {
bytes memory fakeProofData = bytes.concat(
hex"0200000053593a7027c577e14a0a9d47a7ac9222f0814bf6b23c60ac0ee88d80f67901e001bcd53bd4d1ee35c84053491f0d0b602daeae081236372b4bf803d9abd9330706007045c733bac5f0ac28501a9151d96eadd1670978fa84fb6dfdb579c87a1f6dedffdd108715b118508da431070b9372389d410b487549bd7d49b31e23b5dbfc310000000001000000d9f971f15e045e690c1d9bf6e87b73805668ba421350384f1e75bd34e23ecc6437721362b8010000005705790a4ce3400a000000000000000b000000652d6e6561722e6e656172022500000000",
abi.encodePacked(swapBytes16(amount)),
abi.encodePacked(to),
hex"030000004cea846819dd1bf56c738dfacc5cebdf1eb7dd22f54a676cd1cf0b22bf5393f9011c35dbdc79e7950d88ebf8924f5ec4592eb020e9a9df31e5ed0a13dbdd8927b70050362022b2e7dbdc940f61f21b778881f1d479f20d0dcba36c09557f6cd12e2f01d58e75496f1a2b77a10316a279af2adcbb6c2d90b99978bddbb9c7413542cd81f5556b0f286bc591a39a036a64bb1cbd57324b4c4f1822668906508c8969f31b3681c8070000000098bac7912163236d9dd2de729d808cbd7c7b359e85402cb6965257831c7a991d692e59a520079f4d1d3cb8654a0fd4987a62cbcf2ffd111efeb3aed99c4b44bd8de68860ebfc573df63fd50c004da20aa2dce1d00d72ba8003e8fffcdc19fe71f55aded0dc337ba56e84e50fdc1270a4cb6888690f08344f19029830086f5cb9a7ce9d774d57ff179b20f1d688b324a6a6f62e60a5ab493498f6ee8550b00acb7413f6bc3cc92e487f9c2daa345580e8a42d4272e859fc294ddcaf572164623991c0025132f5dfc8160000008cab4f7917658c0ea3265b0423e52cc2022dfbd0b62024004de90d351932b2e101c31a3820444d0ae3acee5b7c4d99d4eba49dcb44ec154d75c1217f54fbc2eb3c0109bc84704c5cfe6ba698b465cba3df8a8d7c5595027837a040b8be4c827450cd0176ededcf963f25ee1f8e53e46503a0d7a764d7ce76b8e0f6f756bf29c0ade5130111274bbd148fca204baa857aad66db092c89fdd2254d1565250d0fa1307af0bd01639b386ef85d52165cd91c0b0d7ff3b72545e6ef652030c9271ab6807b6bcd3b01d13445f2ea94746501e2044ea06f22111fe718788615318b50439789575cf3f601198df8d64579dbe8ccedf43a29b93af50ebf4af0d808cda6b8dee39764f3b03301c39900c6d93efe1755b283cd01a5f4717f6bb6c989617a0fb990c3a2b900796f0083b7cad2577c5a1d3be71aa6f56f75544327569119dc7d0df0ab7e3299b6070700fb2726881c7fa27650003873eb777a1dc21a7c0e63e227da32e64e5160a0425b0062f5c0b2a58d6cae9e53770c93a2eed9685d1c5892a7e1d05b383b68e781d90601120e09be45535aff6e794e78a45ade30f541c23369225e13b441eb58016e0e3301945a5d758ccd4d686c486c210d2ab4e5f4fa15e208d118298f3de87a1e361b4b0160f2e83a0bb4d2cd1cba6ba50c8d66208a90c08bd694e9b2c33f229e9e9a9a03000dd2a5f4062d9a92130cede984a03f81121667024cc4967d002bd889587fc6de0054ce0c34d981837f3fa9f1d99eb5905be18876bba23f6e1016603c505099403e005c0fb550cec2d64f12b537f2c2a00aa339412b39a7c6b7e9506acdcee19b43af00ea6693e8d743a36fa44009abd3261f1b484e339c01383fe684cc054b3f04384000e3829b656ca60088ebdd88090d46f1d39a2f6c0744512c5a38614c86e4bf586200273a218152b554401ec79fab2e5606126e01f3c560f2040abe4487c3bba8180100124bb839a5b9c44856054c6d80bd5229ac2b40fa8a0cd44911629f295b11f08200"
);
eNear.finaliseNearToEthTransfer(fakeProofData, 0);
}

function burn(address, uint128 amount) public onlyRole(MINTER_ROLE) {
eNear.transferToNear(amount, string(''));
}

function swapBytes16(uint128 v) internal pure returns (uint128) {
v = ((v & 0x00ff00ff00ff00ff00ff00ff00ff00ff) << 8) | ((v & 0xff00ff00ff00ff00ff00ff00ff00ff00) >> 8);
v = ((v & 0x0000ffff0000ffff0000ffff0000ffff) << 16) | ((v & 0xffff0000ffff0000ffff0000ffff0000) >> 16);
v = ((v & 0x00000000ffffffff00000000ffffffff) << 32) | ((v & 0xffffffff00000000ffffffff00000000) >> 32);
return (v << 64) | (v >> 64);
}

function _authorizeUpgrade(
address newImplementation
) internal override onlyRole(DEFAULT_ADMIN_ROLE) {}
}
12 changes: 12 additions & 0 deletions evm/eNear/contracts/FakeProver.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.27;

interface INearProver {
function proveOutcome(bytes calldata proofData, uint64 blockHeight) external view returns (bool);
}

contract FakeProver is INearProver {
function proveOutcome(bytes calldata, uint64) external pure returns (bool) {
return true;
}
}
86 changes: 86 additions & 0 deletions evm/eNear/hardhat.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
require('dotenv').config();

const ALCHEMY_API_KEY = process.env.ALCHEMY_API_KEY;
const INFURA_API_KEY = process.env.INFURA_API_KEY;
const ETH_PRIVATE_KEY = process.env.ETH_PRIVATE_KEY || '11'.repeat(32);
const ETHERSCAN_API_KEY = process.env.ETHERSCAN_API_KEY;

task('deploy-e-near-proxy', 'Deploys the ENearProxy contract')
.setAction(async () => {
const { ethers, upgrades } = hre;

const eNearProxyContract =
await ethers.getContractFactory("ENearProxy");
const eNearProxy = await upgrades.deployProxy(
eNearProxyContract,
[
taskArgs.eNear,
],
{
initializer: "initialize",
timeout: 0,
},
);

await eNearProxy.waitForDeployment();
console.log(`eNearProxy deployed at ${await eNearProxy.getAddress()}`);
console.log(
"Implementation address:",
await upgrades.erc1967.getImplementationAddress(
await eNearProxy.getAddress(),
),
);
});

task('set-proxy-as-admin', 'Set the proxy as admin for eNear')
.addParam('proxy', 'Address of the proxy to set as admin')
.addParam('eNear', 'Address of the eNear contract')
.setAction(async (taskArgs, hre) => {
const { ethers } = hre;
const eNear = await ethers.getContractAt('ENear', taskArgs.eNear);
await eNear.nominateAdmin(taskArgs.proxy);
await eNear.acceptAdmin(taskArgs.proxy);
});

task('set-fake-prover', 'Set the fake prover for eNear')
.addParam('eNear', 'Address of the eNear contract')
.setAction(async (taskArgs, hre) => {
const { ethers } = hre;

const FakeProverContractFactory = await ethers.getContractFactory("FakeProver");
const FakeProverContract = await FakeProverContractFactory.deploy();
await FakeProverContract.waitForDeployment();

const eNear = await ethers.getContractAt('ENear', taskArgs.eNear);
eNear.adminSstore(5, uint256(await FakeProverContract.getAddress()));
});

/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
solidity: {
version: "0.8.27",
settings: {
optimizer: {
enabled: true,
runs: 200
}
}
},
networks: {
sepolia: {
url: INFURA_API_KEY
? `https://sepolia.infura.io/v3/${INFURA_API_KEY}`
: `https://eth-sepolia.g.alchemy.com/v2/${ALCHEMY_API_KEY}`,
accounts: [`${ETH_PRIVATE_KEY}`]
},
mainnet: {
url: INFURA_API_KEY
? `https://mainnet.infura.io/v3/${INFURA_API_KEY}`
: `https://eth-mainnet.alchemyapi.io/v2/${ALCHEMY_API_KEY}`,
accounts: [`${ETH_PRIVATE_KEY}`]
},
},
etherscan: {
apiKey: ETHERSCAN_API_KEY || ''
}
};
16 changes: 16 additions & 0 deletions evm/eNear/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "hardhat-project",
"dependencies": {
"@openzeppelin/contracts": "^5.0.2",
"@openzeppelin/contracts-upgradeable": "^5.0.2",
"dotenv": "^16.0.3"
},
"scripts": {
"build": "yarn hardhat compile",
"test": "yarn hardhat test",
"coverage": "yarn hardhat coverage"
},
"devDependencies": {
"hardhat": "^2.22.13"
}
}
Loading