Skip to content

jarvis-network/synthereum

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Jarvis Exchange | Synthereum

Built on top of the priceless contracts developed by UMA.

Getting started - development environment setup

Requirements for manual setup

We strongly recommend using Nix to manage the system dependencies, so you can skip to setting up Nix below.

However, if you prefer to not set up Nix, you need to install the following:

Please consult the manual of your system package manager for instructions on installing the software listed above.

Setting up a development environment via Nix, Nix Flakes and direnv

  1. Clone the project:

    Via SSH (if you have a GitLab account and you have added your SSH keys to it )
    git clone git@gitlab.com:jarvis-network/apps/exchange/mono-repo.git
    Via HTTPS
    git clone https://gitlab.com/jarvis-network/apps/exchange/mono-repo.git
  2. Install Nix, enable nix-flake support and configure direnv

  3. Build the project:

    1. Enter the development shell:

      • If you have direnv installed, you just need to allow it to evaluate the .envrc file in the current folder:

        direnv allow .
      • If you don't have direnv installed (or you prefer not to use it), run:

        nix develop
    2. Install Node.js dependencies via yarn (npm is not supported):

      yarn

Project structure

jarvis-network/exchange/mono-repo/
├── libs/                    | source code | libraries (on which the applications depend)
│   ├── contracts/           | source code | Synthereum Solidity implementation
│   ├── core-utils/          | source code | @jarvis-network/core-utils - core utility library
│   └── hardhat-utils/       | source code | @jarvis-network/hardhat-utils
│
├── scripts/                 | scripts | development utility scripts
│   ├── check_commit_range.bash
│   ├── ci_test_affected.bash
│   ├── prepare-release.bash
│   └── repo-cleanup-post-part-1-of-nx.dev-changes.bash
│
├── README.md                | docs | this file
├── LICENSE                  | docs | MIT license
├── docs/                    | docs
│   ├── security-audits/     | docs
│   ├── fee-calculations.md  | docs
│   └── install-nix.md       | docs
│
├── .envrc                   | nix | direnv config file; takes care of sourcing nix env variables
├── flake.nix                | nix | Nix Flake file
├── flake.lock               | nix | Nix Flake lock file
├── shell.nix                | nix | Nix build-inputs (system dependencies) file
│
├── .npmrc                   | yarn | npm publish config
├── package.json             | yarn | workspace root package.json
├── yarn.lock                | yarn | lock file
├── .yarnrc                  | yarn | config file
│
├── nx.json                  | nx | (mono repo dev tools) config
├── workspace.json           | nx
│
├── docker-bake.hcl          | docker | Docker Buildx config
├── Dockerfile               | docker | Dockerfile containing all image targets
├── .dockerignore            | docker | Docker ignore file
│
├── .gitignore               | git | list of files ignored by git
├── CODEOWNERS               | gitlab | GitLab merge request approvals config
├── .gitlab-ci.yml           | gitlab | config for GitLab CI/CD
│
├── netlify.toml             | netlify | URL redirects and rewrites
│
├── commitlint.config.js     | code style | commit message lint config
├── .editorconfig            | code style | general editor config
├── .eslintrc.json           | code style | ESLint
├── .prettierrc              | code style | Prettier
├── .prettierignore          | code style | Prettier
│
├── jest.config.js           | jest | Jest test framework config
├── jest.preset.js           | jest
│
├── tsconfig.base.json       | typescript | base TypeScript config (shared by the whole repo)
├── tsconfig.cli.json        | typescript | config for CLI programs (inherits the base one)
├── tsconfig.frontend.json   | typescript | config for frontend apps (inherits the base one)
└── tsconfig.node.json       | typescript | config for Node.js apps (inherits the base one)

Environment Variables

Running applications or scripts may require setting environment variables. In addition to exporting environnement variables using the shell, they can also be set persistently in an .env file located in the respective app / script folder. In each folder, where this is relevant there is an .env.example file that lists the variables that are recognized by the script.

.env files are ignored by git, so they suitable for setting secrets for development purposes, such as private keys.

Smart contracts

Hardhat

The tasks of building, testing and migrating the contracts are managed by a Hardhat config file located here: libs/contracts/hardhat.config.ts

Migration scripts (used for deploying the contracts) can be invoked from the libs/contracts folder like so:

yarn migrate-<migration script> --network <network name>

For example:

yarn migrate-finder --network kovan

UML Class Diagram (Outdated - covers only v1)

mermaid diagram

Installing new smart contract dependencies

Use NPM to install new dependencies in the root project directory. Smart contracts can import these dependencies and the compiler will resolve the correct path.

E.g. import "@openzeppelin/contracts/math/SafeMath.sol";

Smart contract commands

SynFiat uses Truffle and therefore all the standard Truffle commands are available:

  • truffle console
  • truffle compile
  • truffle migrate
  • truffle test

Front-end client

A front-end client created with create-react-app that uses React and Web3 can be developed in the ./client directory.

Client dependencies

Run npm install in the client directory to install the client dependencies.

All new client dependencies must be installed here and not in the root project directory.

Running the client

Run npm run start to start a local web server for client development. The server defaults to port 3000 and will watch for code changes.

Testing

There are a few automated tests written for the TIC. These are helpful to run to ensure there are no regressions introduced when working on the contract.

Requirements for running the TIC tests

Tests must be run on a fork of the Kovan network.

  • Forking Kovan is necessary because the tests depend on deployed contracts for DAI, rDAI, and the ExpiringMultiPartyCreator. The rDAI address must also be on the ExpiringMultiPartyCreator whitelist.
  • Creating a fork of the Kovan network with Ganache is easy. For example, if using Infura as the Ethereum client, run ganache-cli -f https://kovan.infura.io/v3/{Infura ID}.
  • To use the included Ganache script ./run-ganache.sh, set the following environment variables:
    • ETH_WALLET_MNEMONIC The 12 word seed phrase for the accounts used to test and deploy the SynFiat smart contracts.
    • ETH_KOVAN_ENDPOINT The endpoint for the Ethereum client connected to Kovan. E.g. https://kovan.infura.io/v3/${INFURA_ID}

Truffle must be configured to create at least 2 accounts for the network used to run the tests.

  • For example, if using the HDWallet provider, the 4th argument specifying the number of accounts to make must be set to 2: HDWalletProvider(mnemonic, kovanEndpoint, 0, 2).

The 2 accounts used to run tests must be funded with ETH and DAI before the network is forked.

  • To obtain Kovan ETH (KETH) use the Kovan faucet.
  • To obtain DAI, either purchase some with KETH on Oasis (make sure you set MetaMask to the Kovan network) or follow these instructions to set up a CDP and mint DAI.
  • Remember, it is important that the DAI contract is the same one that is used by the whitelisted rDAI contract.
  • You can find a list of all the Kovan DAI contract addresses that will work with the whitelisted rDAI contract here.

Running the TIC tests

To run the TIC tests, simply use the following Truffle command:

truffle --network kovan-fork test

Deployment

Deployment requirements

The SynFiat migration scripts in ./migrations will only deploy properly when used with either the Kovan network or a fork of Kovan.

Running migrations

To deploy the SynFiat smart contracts with the migration scripts, run the following Truffle command:

truffle --network kovan migrate

If the contracts have already been deployed but changes have been made and they must be deployed again, use the --reset flag.

Changing derivative parameters

Derivative parameters can be changed in ./tic-config.json. The following parameters are used:

  • expirationTimestamp: Time that the derivative expires.
  • disputeBondPct: Percent of a liquidation position's locked collateral to be deposited by a potential disputer.
  • sponsorDisputeRewardPct: Percent of price paid to sponsor in the Disputed state (i.e. following a successful dispute).
  • disputerDisputeRewardPct: Percent of price paid to disputer in the Disputed state (i.e. following a successful dispute).

E.g.

{
  "expirationTimestamp": 1590969600,
  "disputeBondPct": { "rawValue": "1500000000000000000" },
  "sponsorDisputeRewardPct": { "rawValue": "500000000000000000" },
  "disputerDisputeRewardPct": { "rawValue": "400000000000000000" }
}

Creating new SynFiat assets

New synthetic assets can be created by modifying ./synthetic-assets.json before running the migration scripts.

Simply add a new object to the JSON array in ./synthetic-assets.json. The following parameters are used:

  • syntheticName: The name which describes the new token.
  • syntheticSymbol: The ticker abbreviation of the name.
  • priceFeedIdentifier: Unique identifier for DVM price feed ticker.
  • collateralRequirement: The collateral ratio required to prevent liquidation
  • startingCollateralization: The collateral to token ratio used when the global ratio is zero

E.g.

[
  ...
  {
    "syntheticName": "Jarvis Synthetic Euro",
    "syntheticSymbol": "jEUR",
    "priceFeedIdentifier": "EUR/USD",
    "collateralRequirement": { "rawValue": "1100000000000000000" },
    "startingCollateralization": "1300000000000000000"
  }
]

Now when running truffle --network kovan migrate --reset, this new synthetic asset will also be deployed.

Using the TIC (Token Issuer Contract)

First import the TICFactory Truffle artifact stored in ./client/src/contracts and create the contract instance with web3.

import TICFactory from "./contracts/TICFactory.json";

const networkId = await web3.eth.net.getId();
const factory = new web3.eth.Contract(TICFactory.abi, TICFactory.networks[networkId].address);

The factory is then used to retrieve the TIC for the synthetic asset you wish to interact with.

import TIC from "./contracts/TIC.json";

// ...
const ticAddress = await factory.methods.symbolToTIC("jEUR").call();
const tic = new web3.eth.Contract(TIC.abi, ticAddress);

Create the DAI contract instance. This is needed to approve the DAI transfers that will be made by the TIC.

import IERC20 from "./contracts/IERC20.json";

// ...
const daiAddress = await tic.collateralToken();
const dai = new web3.eth.Contract(IERC20.abi, daiAddress);

Create the synthetic token contract instance. This is the token minted by the TIC for the user.

const syntheticTokenAddress = await tic.methods.syntheticToken().call();
const syntheticToken = new web3.eth.Contract(IERC20.abi, syntheticTokenAddress);

Finally get the account addresses we will use to interact with the contracts.

const accounts = await web3.eth.getAccounts();

Making a collateral deposit as a liquidity provider

We will assume that accounts[0] is the liquidity provider.

Set the amount of collateral to deposit.

const collateralAmount = web3.utils.toWei("10");

Approve the transfer of DAI collateral.

await dai.methods.approve(tic.address, collateralAmount).send({ from: accounts[0] });

Deposit the DAI collateral.

await tic.methods.deposit(collateralAmount).send({ from: accounts[0] });

Minting jEUR as a user

We will assume that accounts[1] is the user.

Set the amount of collateral used to mint tokens.

const collateralAmount = web3.utils.toWei("10");

Set the number of tokens we will try to mint with the collateral.

const numTokens = web3.utils.toWei("100");

Calculate the fees for the user.

const fees = await tic.methods.calculateMintFee(collateralAmount).call();

Calculate the total amount of DAI the user will need to transfer (collateral plus fees).

const totalToTransfer = web3.utils.toBN(collateralAmount).add(web3.utils.toBN(fees));

Approve the transfer of DAI.

await dai.methods.approve(tic.address, totalToTransfer).send({ from: accounts[1] });

Mint the jEUR tokens.

await tic.methods.mint(collateralAmount, numTokens).send({ from: accounts[1] });

Viewing jEUR balance

View the jEUR balance.

const jeurBalance = await syntheticToken.methods.balanceOf(accounts[1]).call();
console.log(web3.utils.fromWei(jeurBalance.toString()));

Transfering jEUR

Set the amount of jEUR tokens to transfer. In this case we will transfer all the tokens owned by a user.

const jeurToTransfer = await syntheticToken.methods.balanceOf(accounts[1]).call();

Transfer the jEUR tokens to accounts[2].

await derivative.methods.transfer(accounts[2], jeurToTransfer).send({ from: accounts[1] });

Redeeming jEUR at contract expiry as a user

Set the amount of jEUR tokens to redeem. In this case we will redeem all the tokens owned by a user.

const jeurToTransfer = await syntheticToken.methods.balanceOf(accounts[1]).call();

Approve the transfer of jEUR tokens.

await syntheticToken.methods.approve(tic.options.address, jeurBalance).send({ from: accounts[1] });

Redeem the user's jEUR tokens.

await tic.methods.settleExpired().send({ from: accounts[1] });

Withdrawing collateral as a liquidity provider

We will assume that accounts[0] is the liquidity provider.

Set the amount of collateral to withdraw. Note that if a LP tries to withdraw enough collateral to undercollateralize the contract, they will be at risk of liquidation.

const excessCollateral = web3.utils.toWei("5");

Submit a withdraw request.

await tic.methods.withdrawRequest(excessCollateral).send({ from: accounts[0] });

If the request is not disputed during the withdrawal liveness period, the request can be fulfilled.

await tic.methods.withdrawPassedRequest({ from: accounts[0] });

Withdraw the collateral to the liquidity provider's account.

await tic.methods.withdraw(excessCollateral).send({ from: accounts[0] });

Atomic swap between tokens as a user

Set the amount of source tokens to swap.

const numTokens = web3.utils.toWei("10");

Set the amount of destination tokens to receive.

const destNumTokens = web3.utils.toWei("10");

Get the destination TIC you wish to swap tokens with.

const otherTICAddress = await factory.methods.symbolToTIC("jGBP");
const otherTIC = new web3.eth.Contract(TIC.abi, otherTICAddress);

Approve the transfer of the source tokens.

await syntheticToken.methods.approve(tic.address, numTokens).send({ from: accounts[1] });

Perform the atomic swap of tokens.

await tic.exchange(otherTIC.address, numTokens, destNumTokens);