-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Bruno Campos
committed
Jun 17, 2024
1 parent
19ca0e4
commit a43683d
Showing
4 changed files
with
326 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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]; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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]; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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'); | ||
// }); | ||
}); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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'); | ||
}); | ||
}); | ||
}); | ||
}); |