Skip to content

Commit

Permalink
added exchange factory
Browse files Browse the repository at this point in the history
  • Loading branch information
Bruno Campos committed Jun 17, 2024
1 parent 19ca0e4 commit a43683d
Show file tree
Hide file tree
Showing 4 changed files with 326 additions and 0 deletions.
90 changes: 90 additions & 0 deletions contracts/orderbook/ExchangeFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

import {Exchange} from "./Exchange.sol";

/**
* @title Exchange Factory
*
* The contract which allows to deploy Exchanges with different token pairs
* and track contract addresses.
*/
contract ExchangeFactory {
// Available Exchanges
mapping(address exchange => bool) public availableExchanges;

// Used salt => deployed Exchange
mapping(bytes32 => address) public exchangeDeployed;

// emited when an exchagne is deployed
event ExchangeDeployed(address exchange, address tokenA, address tokenB, address deployer);

/**
* @dev Deploys an Exchange using CREATE2 opcode.
*
* @param tokenA address of source token.
* @param tokenB address of target token
* @return exchange address of the deployed Exchange.
*/
function deployExchange(
address tokenA,
address tokenB
) external returns (address exchange) {
bytes32 salt = bytes32(keccak256(abi.encodePacked(msg.sender, tokenA, tokenB)));
require(exchangeDeployed[salt] == address(0), "Exchange already deployed");

exchange = _deployExchange(salt, tokenA, tokenB);

exchangeDeployed[salt] = exchange;
availableExchanges[exchange] = true;

emit ExchangeDeployed(exchange, tokenA, tokenB, msg.sender);
}

/**
* @dev Creates deployment data for the CREATE2 opcode.
*
* @return The the address of the contract created.
*/
function _deployExchange(
bytes32 salt,
address tokenA,
address tokenB
) private returns (address) {
bytes memory _code = type(Exchange).creationCode;
bytes memory _constructData = abi.encode(
tokenA,
tokenB
);
bytes memory deploymentData = abi.encodePacked(_code, _constructData);
return _deploy(salt, deploymentData);
}

/**
* @dev Deploy function with create2 opcode call.
*
* @return The the address of the contract created.
*/
function _deploy(bytes32 salt, bytes memory bytecode) private returns (address) {
address addr;
// solhint-disable-next-line no-inline-assembly
assembly {
let encoded_data := add(0x20, bytecode) // load initialization code.
let encoded_size := mload(bytecode) // load init code's length.
addr := create2(callvalue(), encoded_data, encoded_size, salt)
if iszero(extcodesize(addr)) {
revert(0, 0)
}
}
return addr;
}

/**
* @dev Checks if Exchange is available.
*
* @return The bool flag of exchanges's availability.
*/
function isExchangeAvailable(address exchange) external view returns (bool) {
return availableExchanges[exchange];
}
}
90 changes: 90 additions & 0 deletions contracts/orderbook/hts/ExchangeFactoryHTS.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

import {ExchangeHTS} from "./ExchangeHTS.sol";

/**
* @title Exchange Factory
*
* The contract which allows to deploy Exchanges with different token pairs
* and track contract addresses.
*/
contract ExchangeFactoryHTS {
// Available Exchanges
mapping(address exchange => bool) public availableExchanges;

// Used salt => deployed Exchange
mapping(bytes32 => address) public exchangeDeployed;

// emited when an exchagne is deployed
event ExchangeDeployed(address exchange, address tokenA, address tokenB, address deployer);

/**
* @dev Deploys an Exchange using CREATE2 opcode.
*
* @param tokenA address of source token.
* @param tokenB address of target token
* @return exchange address of the deployed Exchange.
*/
function deployExchange(
address tokenA,
address tokenB
) external returns (address exchange) {
bytes32 salt = bytes32(keccak256(abi.encodePacked(msg.sender, tokenA, tokenB)));
require(exchangeDeployed[salt] == address(0), "Exchange already deployed");

exchange = _deployExchange(salt, tokenA, tokenB);

exchangeDeployed[salt] = exchange;
availableExchanges[exchange] = true;

emit ExchangeDeployed(exchange, tokenA, tokenB, msg.sender);
}

/**
* @dev Creates deployment data for the CREATE2 opcode.
*
* @return The the address of the contract created.
*/
function _deployExchange(
bytes32 salt,
address tokenA,
address tokenB
) private returns (address) {
bytes memory _code = type(ExchangeHTS).creationCode;
bytes memory _constructData = abi.encode(
tokenA,
tokenB
);
bytes memory deploymentData = abi.encodePacked(_code, _constructData);
return _deploy(salt, deploymentData);
}

/**
* @dev Deploy function with create2 opcode call.
*
* @return The the address of the contract created.
*/
function _deploy(bytes32 salt, bytes memory bytecode) private returns (address) {
address addr;
// solhint-disable-next-line no-inline-assembly
assembly {
let encoded_data := add(0x20, bytecode) // load initialization code.
let encoded_size := mload(bytecode) // load init code's length.
addr := create2(callvalue(), encoded_data, encoded_size, salt)
if iszero(extcodesize(addr)) {
revert(0, 0)
}
}
return addr;
}

/**
* @dev Checks if Exchange is available.
*
* @return The bool flag of exchanges's availability.
*/
function isExchangeAvailable(address exchange) external view returns (bool) {
return availableExchanges[exchange];
}
}
74 changes: 74 additions & 0 deletions test/orderbook/exchange-factory-hts.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { ethers } from 'hardhat';
import { expect } from 'chai';
import { PrivateKey, Client, AccountId } from "@hashgraph/sdk";

// Tests
describe("ExchangeFactory", function () {
async function deployFixture() {
const [owner] = await ethers.getSigners();

let client = Client.forTestnet();

const operatorPrKey = PrivateKey.fromStringECDSA(process.env.PRIVATE_KEY || '');
const operatorAccountId = AccountId.fromString(process.env.ACCOUNT_ID || '');

client.setOperator(
operatorAccountId,
operatorPrKey
);

const exchangeFactoryFactory = await ethers.getContractFactory("ExchangeFactoryHTS", owner);
const exchangeFactory = await exchangeFactoryFactory.deploy();
await exchangeFactory.waitForDeployment();

return {
exchangeFactory,
client,
owner,
};
}

describe("deployExchange", function () {
describe("when there is no exchange created for the pair", () => {
it("should deploy exchange", async function () {
const { exchangeFactory, owner } = await deployFixture();
const exchangeDetails = {
tokenA: "0x000000000000000000000000000000000042cf0f",
tokenB: "0x000000000000000000000000000000000042cf11",
}

const tx = await exchangeFactory.deployExchange(
exchangeDetails.tokenA,
exchangeDetails.tokenB,
{ from: owner.address, gasLimit: 3000000 }
);

await expect(tx).to.emit(exchangeFactory, "ExchangeDeployed");
});
});

describe("when there is already an exchange created", () => {
it("should revert", async function () {
// @notice: revertedWith feature is not working with hedera

// const { exchangeFactory, owner } = await deployFixture();
// const exchangeDetails = {
// tokenA: "0x000000000000000000000000000000000042cf0f",
// tokenB: "0x000000000000000000000000000000000042cf11",
// }

// await exchangeFactory.deployExchange(
// exchangeDetails.tokenA,
// exchangeDetails.tokenB,
// { from: owner.address, gasLimit: 3000000 }
// );

// await expect(exchangeFactory.deployExchange(
// exchangeDetails.tokenA,
// exchangeDetails.tokenB,
// { from: owner.address, gasLimit: 3000000 })).to.be.revertedWith('Exchange already deployed');
// });
});
});
});
});
72 changes: 72 additions & 0 deletions test/orderbook/exchange-factory.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { ethers } from 'hardhat';
import { expect } from 'chai';
import { PrivateKey, Client, AccountId } from "@hashgraph/sdk";

// Tests
describe("ExchangeFactory", function () {
async function deployFixture() {
const [owner] = await ethers.getSigners();

let client = Client.forTestnet();

const operatorPrKey = PrivateKey.fromStringECDSA(process.env.PRIVATE_KEY || '');
const operatorAccountId = AccountId.fromString(process.env.ACCOUNT_ID || '');

client.setOperator(
operatorAccountId,
operatorPrKey
);

const exchangeFactoryFactory = await ethers.getContractFactory("ExchangeFactory", owner);
const exchangeFactory = await exchangeFactoryFactory.deploy();
await exchangeFactory.waitForDeployment();

return {
exchangeFactory,
client,
owner,
};
}

describe("deployExchange", function () {
describe("when there is no exchange created for the pair", () => {
it("should deploy exchange", async function () {
const { exchangeFactory, owner } = await deployFixture();
const exchangeDetails = {
tokenA: "0x000000000000000000000000000000000042cf0f",
tokenB: "0x000000000000000000000000000000000042cf11",
}

const tx = await exchangeFactory.deployExchange(
exchangeDetails.tokenA,
exchangeDetails.tokenB,
{ from: owner.address, gasLimit: 3000000 }
);

await expect(tx).to.emit(exchangeFactory, "ExchangeDeployed");
});
});

describe("when there is already an exchange created", () => {
it("should revert", async function () {
const { exchangeFactory, owner } = await deployFixture();
const exchangeDetails = {
tokenA: "0x000000000000000000000000000000000042cf0f",
tokenB: "0x000000000000000000000000000000000042cf11",
}

await exchangeFactory.deployExchange(
exchangeDetails.tokenA,
exchangeDetails.tokenB,
{ from: owner.address, gasLimit: 3000000 }
)

await expect(exchangeFactory.deployExchange(
exchangeDetails.tokenA,
exchangeDetails.tokenB,
{ from: owner.address, gasLimit: 3000000 }
)).to.be.revertedWith('Exchange already deployed');
});
});
});
});

0 comments on commit a43683d

Please sign in to comment.