diff --git a/contracts/FluiDex.sol b/contracts/FluiDex.sol index 87e87bf..c8f8907 100644 --- a/contracts/FluiDex.sol +++ b/contracts/FluiDex.sol @@ -9,37 +9,18 @@ import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "hardhat/console.sol"; // for debugging -import "./Events.sol"; import "./IFluiDex.sol"; import "./IVerifier.sol"; /** * @title FluiDexDemo */ -contract FluiDexDemo is - AccessControl, - Events, - IFluiDex, - Ownable, - ReentrancyGuard -{ +contract FluiDexDemo is AccessControl, IFluiDex, Ownable, ReentrancyGuard { using SafeERC20 for IERC20; bytes32 public constant PLUGIN_ADMIN_ROLE = keccak256("PLUGIN_ADMIN_ROLE"); - bytes32 public constant TOKEN_ADMIN_ROLE = keccak256("TOKEN_ADMIN_ROLE"); bytes32 public constant DELEGATE_ROLE = keccak256("DELEGATE_ROLE"); - enum BlockState { - Empty, - Committed, - Verified - } - - struct UserInfo { - address ethAddr; - bytes32 bjjPubkey; - } - /// hard limit for ERC20 tokens uint16 constant TOKEN_NUM_LIMIT = 65535; /// hard limit for users @@ -66,10 +47,8 @@ contract FluiDexDemo is GENESIS_ROOT = _genesis_root; verifier = _verifier; _setupRole(DEFAULT_ADMIN_ROLE, msg.sender); - _setRoleAdmin(TOKEN_ADMIN_ROLE, DEFAULT_ADMIN_ROLE); _setRoleAdmin(PLUGIN_ADMIN_ROLE, DEFAULT_ADMIN_ROLE); _setRoleAdmin(DELEGATE_ROLE, DEFAULT_ADMIN_ROLE); - grantRole(TOKEN_ADMIN_ROLE, msg.sender); grantRole(PLUGIN_ADMIN_ROLE, msg.sender); grantRole(DELEGATE_ROLE, msg.sender); } @@ -91,14 +70,13 @@ contract FluiDexDemo is * @param tokenAddr the ERC20 token address * @return the new ERC20 token tokenId */ - function addToken(address origin, address tokenAddr) + function addToken(address tokenAddr) external override nonReentrant onlyRole(DELEGATE_ROLE) returns (uint16) { - require(hasRole(TOKEN_ADMIN_ROLE, origin)); require(tokenAddrToId[tokenAddr] == 0, "token existed"); tokenNum++; require(tokenNum < TOKEN_NUM_LIMIT, "token num limit reached"); @@ -107,40 +85,36 @@ contract FluiDexDemo is tokenIdToAddr[tokenId] = tokenAddr; tokenAddrToId[tokenAddr] = tokenId; - emit NewToken(origin, tokenAddr, tokenId); return tokenId; } /** * @param to the L2 address (bjjPubkey) of the deposit target. */ - function depositETH(address origin, bytes32 to) - external - payable - override - orCreateUser(origin, to) - onlyRole(DELEGATE_ROLE) - { - emit Deposit(ETH_ID, to, msg.value); - } + function depositETH(bytes32 to) + external + payable + override + onlyRole(DELEGATE_ROLE) + {} /** - * @param to the L2 address (bjjPubkey) of the deposit target. * @param amount the deposit amount. */ - function depositERC20( - address origin, - IERC20 token, - bytes32 to, - uint128 amount - ) external override nonReentrant tokenExist(token) orCreateUser(origin, to) { - uint16 tokenId = tokenAddrToId[address(token)]; + function depositERC20(IERC20 token, uint256 amount) + external + override + nonReentrant + tokenExist(token) + onlyRole(DELEGATE_ROLE) + returns (uint16 tokenId, uint256 realAmount) + { + tokenId = tokenAddrToId[address(token)]; uint256 balanceBeforeDeposit = token.balanceOf(address(this)); - token.safeTransferFrom(origin, address(this), amount); + token.safeTransferFrom(msg.sender, address(this), amount); uint256 balanceAfterDeposit = token.balanceOf(address(this)); - uint256 realAmount = balanceAfterDeposit - balanceBeforeDeposit; - emit Deposit(tokenId, to, realAmount); + realAmount = balanceAfterDeposit - balanceBeforeDeposit; } /** @@ -148,23 +122,28 @@ contract FluiDexDemo is * @param ethAddr the L1 address * @param bjjPubkey the L2 address (bjjPubkey) */ - function registerUser(address ethAddr, bytes32 bjjPubkey) public { + function registerUser(address ethAddr, bytes32 bjjPubkey) + external + override + onlyRole(DELEGATE_ROLE) + returns (uint16 userId) + { require(userBjjPubkeyToUserId[bjjPubkey] == 0, "user existed"); userNum++; require(userNum < USER_NUM_LIMIT, "user num limit reached"); - uint16 userId = userNum; + userId = userNum; userIdToUserInfo[userId] = UserInfo({ ethAddr: ethAddr, bjjPubkey: bjjPubkey }); userBjjPubkeyToUserId[bjjPubkey] = userId; - emit RegisterUser(ethAddr, userId, bjjPubkey); } function getBlockStateByBlockId(uint256 _block_id) - public + external view + override returns (BlockState) { return block_states[_block_id]; @@ -230,13 +209,54 @@ contract FluiDexDemo is } /** - * @dev create a user if not exist - * @param bjjPubkey the L2 address (bjjPubkey) + * @param tokenId tokenId + * @return tokenAddr */ - modifier orCreateUser(address origin, bytes32 bjjPubkey) { - if (userBjjPubkeyToUserId[bjjPubkey] == 0) { - registerUser(origin, bjjPubkey); - } - _; + function getTokenAddr(uint16 tokenId) + external + view + override + returns (address) + { + return tokenIdToAddr[tokenId]; + } + + /** + * @param tokenAddr tokenAddr + * @return tokenId + */ + function getTokenId(address tokenAddr) + external + view + override + returns (uint16) + { + return tokenAddrToId[tokenAddr]; + } + + /** + * @param userId userId + * @return UserInfo + */ + function getUserInfo(uint16 userId) + external + view + override + returns (UserInfo memory) + { + return userIdToUserInfo[userId]; + } + + /** + * @param bjjPubkey user's pubkey + * @return userId, returns 0 if not exist + */ + function getUserId(bytes32 bjjPubkey) + external + view + override + returns (uint16) + { + return userBjjPubkeyToUserId[bjjPubkey]; } } diff --git a/contracts/FluiDexDelegate.sol b/contracts/FluiDexDelegate.sol index d664914..955d09e 100644 --- a/contracts/FluiDexDelegate.sol +++ b/contracts/FluiDexDelegate.sol @@ -4,16 +4,31 @@ pragma solidity ^0.8.0; import "@openzeppelin/contracts/access/AccessControl.sol"; import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol"; +import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "./Events.sol"; import "./IFluiDex.sol"; import "./IFluiDexDelegate.sol"; -contract FluiDexDelegate is AccessControl, IFluiDexDelegate, ReentrancyGuard { +contract FluiDexDelegate is + AccessControl, + Events, + IFluiDexDelegate, + ReentrancyGuard +{ + using SafeERC20 for IERC20; + + bytes32 public constant TOKEN_ADMIN_ROLE = keccak256("TOKEN_ADMIN_ROLE"); + + /// use 0 representing ETH in tokenId + uint16 constant ETH_ID = 0; IFluiDex target; event TargetChange(IFluiDex prev, IFluiDex now); constructor(IFluiDex _target) { _setupRole(DEFAULT_ADMIN_ROLE, msg.sender); + _setRoleAdmin(TOKEN_ADMIN_ROLE, DEFAULT_ADMIN_ROLE); + grantRole(TOKEN_ADMIN_ROLE, msg.sender); target = _target; } @@ -32,21 +47,29 @@ contract FluiDexDelegate is AccessControl, IFluiDexDelegate, ReentrancyGuard { /** * @notice request to add a new ERC20 token * @param tokenAddr the ERC20 token address - * @return the new ERC20 token tokenId + * @return tokenId the new ERC20 token tokenId */ - function addToken(address tokenAddr) - external + function addToken(address tokenAddr) + external override - returns (uint16) + onlyRole(TOKEN_ADMIN_ROLE) + returns (uint16 tokenId) { - return target.addToken(msg.sender, tokenAddr); + tokenId = target.addToken(tokenAddr); + emit NewToken(msg.sender, tokenAddr, tokenId); } /** * @param to the L2 address (bjjPubkey) of the deposit target. */ - function depositETH(bytes32 to) external payable override { - target.depositETH{value: msg.value}(msg.sender, to); + function depositETH(bytes32 to) + external + payable + override + orCreateUser(msg.sender, to) + { + target.depositETH{value: msg.value}(to); + emit Deposit(ETH_ID, to, msg.value); } /** @@ -56,9 +79,19 @@ contract FluiDexDelegate is AccessControl, IFluiDexDelegate, ReentrancyGuard { function depositERC20( IERC20 token, bytes32 to, - uint128 amount + uint256 amount ) external override { - target.depositERC20(msg.sender, token, to, amount); + uint256 balanceBeforeDeposit = token.balanceOf(address(this)); + token.safeTransferFrom(msg.sender, address(this), amount); + uint256 balanceAfterDeposit = token.balanceOf(address(this)); + uint256 realAmount = balanceAfterDeposit - balanceBeforeDeposit; + token.safeIncreaseAllowance(address(target), realAmount); + + (uint16 tokenId, uint256 finalAmount) = target.depositERC20( + token, + realAmount + ); + emit Deposit(tokenId, to, finalAmount); } /** @@ -75,4 +108,16 @@ contract FluiDexDelegate is AccessControl, IFluiDexDelegate, ReentrancyGuard { ) external override returns (bool) { return target.submitBlock(_block_id, _public_inputs, _serialized_proof); } + + /** + * @dev create a user if not exist + * @param bjjPubkey the L2 address (bjjPubkey) + */ + modifier orCreateUser(address origin, bytes32 bjjPubkey) { + if (target.getUserId(bjjPubkey) == 0) { + uint16 userId = target.registerUser(origin, bjjPubkey); + emit RegisterUser(msg.sender, userId, bjjPubkey); + } + _; + } } diff --git a/contracts/IFluiDex.sol b/contracts/IFluiDex.sol index 312ee7a..96f1826 100644 --- a/contracts/IFluiDex.sol +++ b/contracts/IFluiDex.sol @@ -5,28 +5,39 @@ pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; interface IFluiDex { + enum BlockState { + Empty, + Committed, + Verified + } + + struct UserInfo { + address ethAddr; + bytes32 bjjPubkey; + } + /** * @notice request to add a new ERC20 token * @param tokenAddr the ERC20 token address * @return the new ERC20 token tokenId */ - function addToken(address origin, address tokenAddr) external returns (uint16); + function addToken(address tokenAddr) external returns (uint16); /** * @param to the L2 address (bjjPubkey) of the deposit target. */ - function depositETH(address origin, bytes32 to) external payable; + function depositETH(bytes32 to) external payable; /** - * @param to the L2 address (bjjPubkey) of the deposit target. * @param amount the deposit amount. */ - function depositERC20( - address origin, - IERC20 token, - bytes32 to, - uint128 amount - ) external; + function depositERC20(IERC20 token, uint256 amount) + external + returns (uint16 tokenId, uint256 realAmount); + + function getBlockStateByBlockId(uint256 _block_id) + external + returns (BlockState); /** * @notice request to submit a new l2 block @@ -40,4 +51,37 @@ interface IFluiDex { uint256[] memory _public_inputs, uint256[] memory _serialized_proof ) external returns (bool); + + /** + * @dev this won't verify the pubkey + * @param ethAddr the L1 address + * @param bjjPubkey the L2 address (bjjPubkey) + */ + function registerUser(address ethAddr, bytes32 bjjPubkey) + external + returns (uint16 userId); + + /** + * @param tokenId tokenId + * @return tokenAddr + */ + function getTokenAddr(uint16 tokenId) external view returns (address); + + /** + * @param tokenAddr tokenAddr + * @return tokenId + */ + function getTokenId(address tokenAddr) external view returns (uint16); + + /** + * @param userId userId + * @return UserInfo + */ + function getUserInfo(uint16 userId) external view returns (UserInfo memory); + + /** + * @param bjjPubkey user's pubkey + * @return userId, returns 0 if not exist + */ + function getUserId(bytes32 bjjPubkey) external view returns (uint16); } diff --git a/contracts/IFluiDexDelegate.sol b/contracts/IFluiDexDelegate.sol index a4f62eb..23605f5 100644 --- a/contracts/IFluiDexDelegate.sol +++ b/contracts/IFluiDexDelegate.sol @@ -24,7 +24,7 @@ interface IFluiDexDelegate { function depositERC20( IERC20 token, bytes32 to, - uint128 amount + uint256 amount ) external; /** diff --git a/contracts/Ticker.sol b/contracts/Ticker.sol deleted file mode 100644 index bf24334..0000000 --- a/contracts/Ticker.sol +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 - -pragma solidity ^0.8.0; - -contract Ticker { - uint256 ticktock; - - constructor() { - ticktock = 0; - } - - function tick() public { - ticktock = block.number; - } -} diff --git a/hardhat.config.ts b/hardhat.config.ts index 216ddc9..b3c9140 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -5,18 +5,17 @@ import "@nomiclabs/hardhat-waffle"; import { infuraApiKey, walletPrivateKey, etherscanApiKey } from './secrets.json'; const config: HardhatUserConfig = { - defaultNetwork: "hardhat", + defaultNetwork: "geth", networks: { - hardhat: { - chainId: 1337, - mining: { - auto: false, - interval: [3000, 6000] - } + geth: { + url: "http://localhost:8545", + gas: 3000000, + gasPrice: 10, }, goerli: { url: `https://goerli.infura.io/v3/${infuraApiKey}`, accounts: [walletPrivateKey], + gas: 30000000, } }, etherscan: { @@ -33,4 +32,4 @@ const config: HardhatUserConfig = { } }; -export default config; \ No newline at end of file +export default config; diff --git a/scripts/deploy.ts b/scripts/deploy.ts index b29c0b7..75398ee 100644 --- a/scripts/deploy.ts +++ b/scripts/deploy.ts @@ -1,20 +1,32 @@ +import * as fs from 'fs'; import { Account } from "fluidex.js"; import { run, ethers } from "hardhat"; import * as hre from "hardhat"; -import { default as tokens} from "../tokens"; import { getTestAccount } from "./accounts"; const loadAccounts = () => Array.from(botsIds).map((user_id) => Account.fromMnemonic(getTestAccount(user_id).mnemonic)); -const botsIds = [1, 2, 3, 4, 5]; +const botsIds = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]; const accounts = loadAccounts(); +interface Token { + symbol: string, + address: string, +} + async function main() { await run('compile'); + let tokens: Token[]; + const raw = fs.readFileSync('/tmp/tokens.json', 'utf-8'); + tokens = JSON.parse(raw); + + let deployed: Record = {}; + const verifierFactory = await ethers.getContractFactory("KeyedVerifier"); const verifier = await verifierFactory.deploy(); await verifier.deployed(); console.log("Verifier deployed to:", verifier.address); + deployed['KeyedVerifier'] = verifier.address; const fluidexFactory = await ethers.getContractFactory("FluiDexDemo"); const genesisRoot = process.env.GENESIS_ROOT; @@ -22,44 +34,57 @@ async function main() { const fluiDex = await fluidexFactory.deploy(genesisRoot, verifier.address); await fluiDex.deployed(); console.log("FluiDex deployed to:", fluiDex.address); + deployed['FluiDexDemo'] = fluiDex.address; const registerUser = fluiDex.functions.registerUser; - for(const account of accounts) { + const accountsDump = new Array(); + for(const [idx, account] of accounts.entries()) { await registerUser(account.ethAddr, account.bjjPubKey); + accountsDump.push({ id: idx, pubkey: account.bjjPubKey }); console.log(`register user ${account.bjjPubKey}`); } + fs.writeFileSync('/tmp/accounts.json', JSON.stringify(accountsDump)); const fluiDexDelegateFactory = await ethers.getContractFactory("FluiDexDelegate"); const fluiDexDelegate = await fluiDexDelegateFactory.deploy(fluiDex.address); await fluiDexDelegate.deployed(); + await fluiDexDelegate.deployTransaction.wait(1); console.log("FluiDexDelegate deployed to:", fluiDexDelegate.address); + deployed['FluiDexDelegate'] = fluiDexDelegate.address; + const tx = await ethers.provider.getTransaction(fluiDexDelegate.deployTransaction.hash); + deployed['baseBlock'] = tx.blockNumber!!; + fs.writeFileSync('/tmp/deployed.json', JSON.stringify(deployed)); const DELEGATE_ROLE = await fluiDex.callStatic.DELEGATE_ROLE(); await fluiDex.functions.grantRole(DELEGATE_ROLE, fluiDexDelegate.address); console.log("grant DELEGATE_ROLE to FluiDexDelegate"); const addToken = fluiDexDelegate.functions.addToken; - for (const {name, address} of Array.from(tokens)) { + for (const {symbol, address} of Array.from(tokens)) { await addToken(address); - console.log(`add ${name} token at`, address); + console.log(`add ${symbol} token at`, address); } // skip verify on localhost - if (hre.network.name !== "localhost") { - await run('verify', { - address: verifier.address, - contract: "contracts/Verifier.sol:KeyedVerifier", - }); - await run('verify', { - address: fluiDex.address, - contract: "contracts/FluiDex.sol:FluiDexDemo", - constructorArgsParams: [genesisRoot, verifier.address], - }); - await run('verify', { - address: fluiDexDelegate.address, - contract: "contracts/FluiDexDelegate.sol:FluiDexDelegate", - constructorArgsParams: [fluiDex.address], - }); + if (hre.network.name !== "geth") { + try { + await run('verify', { + address: verifier.address, + contract: "contracts/Verifier.sol:KeyedVerifier", + }); + await run('verify', { + address: fluiDex.address, + contract: "contracts/FluiDex.sol:FluiDexDemo", + constructorArgsParams: [genesisRoot, verifier.address], + }); + await run('verify', { + address: fluiDexDelegate.address, + contract: "contracts/FluiDexDelegate.sol:FluiDexDelegate", + constructorArgsParams: [fluiDex.address], + }); + } catch (e) { + console.log("verify might fail:", e); + } } } diff --git a/scripts/tick.js b/scripts/tick.js deleted file mode 100644 index 4693b97..0000000 --- a/scripts/tick.js +++ /dev/null @@ -1,31 +0,0 @@ -const hre = require("hardhat"); - -function sleep(ms) { - return new Promise((resolve) => setTimeout(resolve, ms)); -} - -async function main() { - // We get the contract to deploy - const TickerFactory = await ethers.getContractFactory("Ticker"); - ticker = await TickerFactory.deploy(); - await ticker.deployed(); - console.log("ticker deployed to:", ticker.address); - - while (true) { - try { - await sleep(1000); - console.log("tick tock", await ticker.tick()); - } catch (e) { - console.log("error", e); - } - } -} - -// We recommend this pattern to be able to use async/await everywhere -// and properly handle errors. -main() - .then(() => process.exit(0)) - .catch(error => { - console.error(error); - process.exit(1); - }); diff --git a/tokens.json b/tokens.json new file mode 100644 index 0000000..859d66e --- /dev/null +++ b/tokens.json @@ -0,0 +1,7 @@ +[ + { "symbol": "USDT", "address": "0x021030D7BA0B21633fE351e83C9b0b8035360329" }, + { "symbol": "UNI", "address": "0xB412EcE47485D3CFbf65e1b95408D5679eF9c36d" }, + { "symbol": "LINK", "address": "0x1bA66DB7A54738F5597FeCa2640fd2E717722230" }, + { "symbol": "YFI", "address": "0xfb30fd66413c01547115C22B62Bde0fF3700DE1F" }, + { "symbol": "MATIC", "address": "0x1185f0F4Ea8a713920852b6113F2C3eFe593899F" } +] \ No newline at end of file diff --git a/tokens.ts b/tokens.ts deleted file mode 100644 index 263ad47..0000000 --- a/tokens.ts +++ /dev/null @@ -1,7 +0,0 @@ -export default [ - { "name": "USDT", "address": "0x021030D7BA0B21633fE351e83C9b0b8035360329" }, - { "name": "UNI", "address": "0xB412EcE47485D3CFbf65e1b95408D5679eF9c36d" }, - { "name": "LINK", "address": "0x1bA66DB7A54738F5597FeCa2640fd2E717722230" }, - { "name": "YFI", "address": "0xfb30fd66413c01547115C22B62Bde0fF3700DE1F" }, - { "name": "MATIC", "address": "0x1185f0F4Ea8a713920852b6113F2C3eFe593899F" } -]; \ No newline at end of file