Commit 074a5657 authored by XFT's avatar XFT
Browse files

.

parent a4bc252b
Pipeline #27 failed with stages
in 0 seconds
// SPDX-License-Identifier: MIT
pragma solidity ^0.7.0;
import "./Shifter.sol";
import "./Interfaces/ElasticIERC20.sol";
import "./Interfaces/SafeEERC20.sol";
import "./Interfaces/IOracle.sol";
import "@openzeppelin/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts/utils/Pausable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract XFTanon is Shifter, Pausable, Ownable {
using SafeEERC20 for ElasticIERC20;
ElasticIERC20 public immutable xft;
ElasticIERC20 public immutable token;
IOracle public oracle;
uint256 public tokenPrice;
address public xftPool;
address public tokenPool;
address public weth9;
address public chainlinkFeed;
uint256 public flexFeeThreshold;
bool public oracleActive = true;
event SetOracle(address oracle);
event SimpleShift(uint256 amount, address recipient, uint256 output);
event SetFlexFeeThreshold(uint256 _threshold);
event SetChainlinkFeed(address _chainlink);
event SetPool(address _pool);
constructor(
IVerifier _verifier,
IHasher _hasher,
IStorage _storage,
uint256 _denomination,
uint256 _ethDenomination,
uint32 _merkleTreeHeight,
ElasticIERC20 _xft,
ElasticIERC20 _token,
IOracle _oracle,
address _xftPool,
address _tokenPool,
address _weth9,
address _chainlinkFeed,
uint256 _flexFeeThreshold
) Shifter(_verifier, _hasher, _storage, _denomination, _ethDenomination, _merkleTreeHeight) {
if (_tokenPool == address(0x0) || _xftPool == address(0x0)) oracleActive = false;
oracle = _oracle;
xft = _xft;
token = _token;
xftPool = _xftPool;
tokenPool = _tokenPool;
weth9 = _weth9;
chainlinkFeed = _chainlinkFeed;
flexFeeThreshold = _flexFeeThreshold;
}
function pause() external onlyOwner {
_pause();
}
function setOracle(IOracle _oracle) external onlyOwner whenNotPaused {
oracle = _oracle;
emit SetOracle(address(_oracle));
}
function setChainlinkFeed(address _chainlink) external onlyOwner whenNotPaused {
chainlinkFeed = _chainlink;
emit SetChainlinkFeed(_chainlink);
}
function setXFTPool(address _xftPool) external onlyOwner whenNotPaused {
xftPool = _xftPool;
emit SetPool(_xftPool);
}
function setTokenPool(address _tokenPool) external onlyOwner whenNotPaused {
tokenPool = _tokenPool;
emit SetPool(_tokenPool);
}
function setFlexFeeThreshold(uint256 _threshold) external onlyOwner whenNotPaused {
flexFeeThreshold = _threshold;
emit SetFlexFeeThreshold(flexFeeThreshold);
}
function simpleShift(uint256 _amount, address _recipient) public whenNotPaused {
require(token.balanceOf(msg.sender) >= _amount, "Insufficient balance");
uint256 _output = oracle.getCostSimpleShift(_amount, chainlinkFeed, xftPool, tokenPool);
token.burn(msg.sender, _amount);
xft.mint(_recipient, _output);
emit SimpleShift(_amount, _recipient, _output);
}
function getDenomination() external view returns (uint256) {
return denomination;
}
function getCost(uint256 _amount) public view returns (uint256) {
if (!oracleActive) return _amount;
return oracle.getCost(_amount, chainlinkFeed, xftPool);
}
function _processDeposit() internal override whenNotPaused {
uint256 depositCost = getCost(denomination);
require(xft.balanceOf(msg.sender) >= depositCost, "Insufficient Balance");
xft.burn(msg.sender, depositCost);
}
function _processWithdraw (
address payable _recipient,
address payable _relayer,
uint256 _fee, // Fee is in USD
uint256 _refund
) internal override {
require(msg.value == _refund, "Incorrect refund amount received by the contract");
token.mint(_recipient, denomination - _fee);
if (_fee > 0) {
token.mint(_relayer, _fee);
}
if (_refund + ethDenomination > 0) {
(bool success, ) = _recipient.call{ value: _refund + ethDenomination }("");
if (!success) {
// let's return back to the relayer
_relayer.transfer(_refund + ethDenomination);
}
}
}
}
// Copyright 2017 Christian Reitwiessner
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
// 2019 OKIMS
pragma solidity ^0.7.0;
library Pairing {
uint256 constant PRIME_Q = 21888242871839275222246405745257275088696311157297823662689037894645226208583;
struct G1Point {
uint256 X;
uint256 Y;
}
// Encoding of field elements is: X[0] * z + X[1]
struct G2Point {
uint256[2] X;
uint256[2] Y;
}
/*
* @return The negation of p, i.e. p.plus(p.negate()) should be zero.
*/
function negate(G1Point memory p) internal pure returns (G1Point memory) {
// The prime q in the base field F_q for G1
if (p.X == 0 && p.Y == 0) {
return G1Point(0, 0);
} else {
return G1Point(p.X, PRIME_Q - (p.Y % PRIME_Q));
}
}
/*
* @return r the sum of two points of G1
*/
function plus(
G1Point memory p1,
G1Point memory p2
) internal view returns (G1Point memory r) {
uint256[4] memory input;
input[0] = p1.X;
input[1] = p1.Y;
input[2] = p2.X;
input[3] = p2.Y;
bool success;
// solium-disable-next-line security/no-inline-assembly
assembly {
success := staticcall(sub(gas(), 2000), 6, input, 0xc0, r, 0x60)
// Use "invalid" to make gas estimation work
switch success case 0 { invalid() }
}
require(success, "pairing-add-failed");
}
/*
* @return r the product of a point on G1 and a scalar, i.e.
* p == p.scalar_mul(1) and p.plus(p) == p.scalar_mul(2) for all
* points p.
*/
function scalar_mul(G1Point memory p, uint256 s) internal view returns (G1Point memory r) {
uint256[3] memory input;
input[0] = p.X;
input[1] = p.Y;
input[2] = s;
bool success;
// solium-disable-next-line security/no-inline-assembly
assembly {
success := staticcall(sub(gas(), 2000), 7, input, 0x80, r, 0x60)
// Use "invalid" to make gas estimation work
switch success case 0 { invalid() }
}
require(success, "pairing-mul-failed");
}
/* @return The result of computing the pairing check
* e(p1[0], p2[0]) * .... * e(p1[n], p2[n]) == 1
* For example,
* pairing([P1(), P1().negate()], [P2(), P2()]) should return true.
*/
function pairing(
G1Point memory a1,
G2Point memory a2,
G1Point memory b1,
G2Point memory b2,
G1Point memory c1,
G2Point memory c2,
G1Point memory d1,
G2Point memory d2
) internal view returns (bool) {
G1Point[4] memory p1 = [a1, b1, c1, d1];
G2Point[4] memory p2 = [a2, b2, c2, d2];
uint256 inputSize = 24;
uint256[] memory input = new uint256[](inputSize);
for (uint256 i = 0; i < 4; i++) {
uint256 j = i * 6;
input[j + 0] = p1[i].X;
input[j + 1] = p1[i].Y;
input[j + 2] = p2[i].X[0];
input[j + 3] = p2[i].X[1];
input[j + 4] = p2[i].Y[0];
input[j + 5] = p2[i].Y[1];
}
uint256[1] memory out;
bool success;
// solium-disable-next-line security/no-inline-assembly
assembly {
success := staticcall(sub(gas(), 2000), 8, add(input, 0x20), mul(inputSize, 0x20), out, 0x20)
// Use "invalid" to make gas estimation work
switch success case 0 { invalid() }
}
require(success, "pairing-opcode-failed");
return out[0] != 0;
}
}
contract Verifier {
uint256 constant SNARK_SCALAR_FIELD = 21888242871839275222246405745257275088548364400416034343698204186575808495617;
uint256 constant PRIME_Q = 21888242871839275222246405745257275088696311157297823662689037894645226208583;
using Pairing for *;
struct VerifyingKey {
Pairing.G1Point alfa1;
Pairing.G2Point beta2;
Pairing.G2Point gamma2;
Pairing.G2Point delta2;
Pairing.G1Point[7] IC;
}
struct Proof {
Pairing.G1Point A;
Pairing.G2Point B;
Pairing.G1Point C;
}
function verifyingKey() internal pure returns (VerifyingKey memory vk) {
vk.alfa1 = Pairing.G1Point(uint256(20692898189092739278193869274495556617788530808486270118371701516666252877969), uint256(11713062878292653967971378194351968039596396853904572879488166084231740557279));
vk.beta2 = Pairing.G2Point([uint256(12168528810181263706895252315640534818222943348193302139358377162645029937006), uint256(281120578337195720357474965979947690431622127986816839208576358024608803542)], [uint256(16129176515713072042442734839012966563817890688785805090011011570989315559913), uint256(9011703453772030375124466642203641636825223906145908770308724549646909480510)]);
vk.gamma2 = Pairing.G2Point([uint256(11559732032986387107991004021392285783925812861821192530917403151452391805634), uint256(10857046999023057135944570762232829481370756359578518086990519993285655852781)], [uint256(4082367875863433681332203403145435568316851327593401208105741076214120093531), uint256(8495653923123431417604973247489272438418190587263600148770280649306958101930)]);
vk.delta2 = Pairing.G2Point([uint256(15208606214570874334261746446200199033462678344026185572194543686340588455376), uint256(4201549448713475441700620943818443847551216333428073482030847716518222239220)], [uint256(12504569504683876560222890118098094502189148489042391982838401316936649733743), uint256(21452163545381790963880582203990389540905251758607075975866466960090453123328)]);
vk.IC[0] = Pairing.G1Point(uint256(16225148364316337376768119297456868908427925829817748684139175309620217098814), uint256(5167268689450204162046084442581051565997733233062478317813755636162413164690));
vk.IC[1] = Pairing.G1Point(uint256(12882377842072682264979317445365303375159828272423495088911985689463022094260), uint256(19488215856665173565526758360510125932214252767275816329232454875804474844786));
vk.IC[2] = Pairing.G1Point(uint256(13083492661683431044045992285476184182144099829507350352128615182516530014777), uint256(602051281796153692392523702676782023472744522032670801091617246498551238913));
vk.IC[3] = Pairing.G1Point(uint256(9732465972180335629969421513785602934706096902316483580882842789662669212890), uint256(2776526698606888434074200384264824461688198384989521091253289776235602495678));
vk.IC[4] = Pairing.G1Point(uint256(8586364274534577154894611080234048648883781955345622578531233113180532234842), uint256(21276134929883121123323359450658320820075698490666870487450985603988214349407));
vk.IC[5] = Pairing.G1Point(uint256(4910628533171597675018724709631788948355422829499855033965018665300386637884), uint256(20532468890024084510431799098097081600480376127870299142189696620752500664302));
vk.IC[6] = Pairing.G1Point(uint256(15335858102289947642505450692012116222827233918185150176888641903531542034017), uint256(5311597067667671581646709998171703828965875677637292315055030353779531404812));
}
/*
* @returns Whether the proof is valid given the hardcoded verifying key
* above and the public inputs
*/
function verifyProof(
bytes memory proof,
uint256[6] memory input
) public view returns (bool) {
uint256[8] memory p = abi.decode(proof, (uint256[8]));
// Make sure that each element in the proof is less than the prime q
for (uint8 i = 0; i < p.length; i++) {
require(p[i] < PRIME_Q, "verifier-proof-element-gte-prime-q");
}
Proof memory _proof;
_proof.A = Pairing.G1Point(p[0], p[1]);
_proof.B = Pairing.G2Point([p[2], p[3]], [p[4], p[5]]);
_proof.C = Pairing.G1Point(p[6], p[7]);
VerifyingKey memory vk = verifyingKey();
// Compute the linear combination vk_x
Pairing.G1Point memory vk_x = Pairing.G1Point(0, 0);
vk_x = Pairing.plus(vk_x, vk.IC[0]);
// Make sure that every input is less than the snark scalar field
for (uint256 i = 0; i < input.length; i++) {
require(input[i] < SNARK_SCALAR_FIELD, "verifier-gte-snark-scalar-field");
vk_x = Pairing.plus(vk_x, Pairing.scalar_mul(vk.IC[i + 1], input[i]));
}
return Pairing.pairing(
Pairing.negate(_proof.A),
_proof.B,
vk.alfa1,
vk.beta2,
vk_x,
vk.gamma2,
_proof.C,
vk.delta2
);
}
}
This source diff could not be displayed because it is too large. You can view the blob instead.
File added
This source diff could not be displayed because it is too large. You can view the blob instead.
{"IC":[["16225148364316337376768119297456868908427925829817748684139175309620217098814","5167268689450204162046084442581051565997733233062478317813755636162413164690","1"],["12882377842072682264979317445365303375159828272423495088911985689463022094260","19488215856665173565526758360510125932214252767275816329232454875804474844786","1"],["13083492661683431044045992285476184182144099829507350352128615182516530014777","602051281796153692392523702676782023472744522032670801091617246498551238913","1"],["9732465972180335629969421513785602934706096902316483580882842789662669212890","2776526698606888434074200384264824461688198384989521091253289776235602495678","1"],["8586364274534577154894611080234048648883781955345622578531233113180532234842","21276134929883121123323359450658320820075698490666870487450985603988214349407","1"],["4910628533171597675018724709631788948355422829499855033965018665300386637884","20532468890024084510431799098097081600480376127870299142189696620752500664302","1"],["15335858102289947642505450692012116222827233918185150176888641903531542034017","5311597067667671581646709998171703828965875677637292315055030353779531404812","1"]],"vk_alfa_1":["20692898189092739278193869274495556617788530808486270118371701516666252877969","11713062878292653967971378194351968039596396853904572879488166084231740557279","1"],"vk_beta_2":[["281120578337195720357474965979947690431622127986816839208576358024608803542","12168528810181263706895252315640534818222943348193302139358377162645029937006"],["9011703453772030375124466642203641636825223906145908770308724549646909480510","16129176515713072042442734839012966563817890688785805090011011570989315559913"],["1","0"]],"vk_gamma_2":[["10857046999023057135944570762232829481370756359578518086990519993285655852781","11559732032986387107991004021392285783925812861821192530917403151452391805634"],["8495653923123431417604973247489272438418190587263600148770280649306958101930","4082367875863433681332203403145435568316851327593401208105741076214120093531"],["1","0"]],"vk_delta_2":[["4201549448713475441700620943818443847551216333428073482030847716518222239220","15208606214570874334261746446200199033462678344026185572194543686340588455376"],["21452163545381790963880582203990389540905251758607075975866466960090453123328","12504569504683876560222890118098094502189148489042391982838401316936649733743"],["1","0"]],"vk_alfabeta_12":[[["21365812195485565970449951947073093780266368302919758441364015846541070649889","13899346400535634506212110533298291334607392402427556671205120154633328392938"],["9176241749501911223800029754421468853554325976185319022570714890557573371905","16897971416518120303319064173781767941602184168568029998374699041158871223961"],["6994180461635063460601079479427022670717591990958754250719881638298446317234","9179026066977875182617507817914233459615244337807200435373777462238862437279"]],[["12505271218650914527710842094803759916526791354425688234288687823807495160149","21079529389819175768418925187724856376731616978516023985093353832725307191172"],["17191503117818720549103434672697266076462686591305810428253469897937793366640","1460614254014044318793914579722356842222906714283817829623699739748310555420"],["8156206509351086444845867552906130156524383450240899694386232124211773153798","9170252275562998399982022993666567730500187520875477300499051353566398457339"]]],"protocol":"groth","nPublic":6}
\ No newline at end of file
const fs = require('fs');
const configRelativePath = fs.existsSync('./config.json') ? './config.json' : '../config.json';
const config = require(configRelativePath);
const shifters = {}
const XFTanon = artifacts.require('XFTanon')
const Verifier = artifacts.require('Verifier')
const Hasher = artifacts.require('Hasher')
const Storage = artifacts.require('Storage')
const Oracle = artifacts.require('Oracle')
const XFTMock = artifacts.require('XFTMock')
const XFTOLDMock = artifacts.require('XFTOLDMock')
const aUSDMock = artifacts.require('anonUSD')
const INonfungiblePositionManager = artifacts.require('INonfungiblePositionManager')
const IUniswapV3Pool = artifacts.require("IUniswapV3Pool")
const IUniswapV3Factory = artifacts.require("IUniswapV3Factory")
const IERC20 = artifacts.require('IERC20')
const IWETH9 = artifacts.require('IWETH9')
const Web3Utils = require('web3-utils')
const burnerRole = Web3Utils.keccak256("BURNER_ROLE")
const minterRole = Web3Utils.keccak256("MINTER_ROLE")
const TokenSwap = artifacts.require("TokenSwap")
const AggregatorV3 = artifacts.require("AggregatorV3Interface")
advanceTimeAndBlock = async (time) => {
await advanceTime(time);
await advanceBlock();
return Promise.resolve(web3.eth.getBlock('latest'));
}
advanceTime = (time) => {
return new Promise((resolve, reject) => {
web3.currentProvider.send({
jsonrpc: "2.0",
method: "evm_increaseTime",
params: [time],
id: new Date().getTime()
}, (err, result) => {
if (err) { return reject(err); }
return resolve(result);
});
});
}
advanceBlock = () => {
return new Promise((resolve, reject) => {
web3.currentProvider.send({
jsonrpc: "2.0",
method: "evm_mine",
id: new Date().getTime()
}, (err, result) => {
if (err) { return reject(err); }
const newBlockHash = web3.eth.getBlock('latest').hash;
return resolve(newBlockHash)
});
});
}
unlockAccount = async (address) => {
let provider = web3.currentProvider;
return new Promise((res, rej) => {
provider.send({
method: 'evm_addAccount',
params: [address, ""]
}, (d) => {
provider.send({
method: 'personal_unlockAccount',
params: [address, ""]
}, (d) => {
res(address);
});
});
});
}
module.exports = function (deployer, network, accounts) {
return deployer.then(async () => {
let {
MERKLE_TREE_HEIGHT, tokenStyle, xftToken, ethDenomination, weth9, oracle, dry,
HASHER, VERIFIER, STORAGE, XFT_POOL, INTERVAL_SHORT, INTERVAL_LONG
} = config
let { xftUniPool, NonfungiblePositionManager, UniswapV3Factory, UniswapV3Staker, nftID } = config.uniswap
let xft = xftToken
let verifier = VERIFIER
let hasher = HASHER
let storage = STORAGE
oracle = oracle || ''
let xftPool = XFT_POOL || "0x0000000000000000000000000000000000000000"
let tokenInstance, xftInstance, storageInstance, minted, account
if (verifier === '') verifierInstance = await deployer.deploy(Verifier)
else verifierInstance = await Verifier.at(VERIFIER)
verifier = verifierInstance.address
console.log(`Verifier at ${verifier}`)
if (hasher === '') hasherInstance = await deployer.deploy(Hasher)
else hasherInstance = await Hasher.at(HASHER)
hasher = hasherInstance.address
// Init storage to the null address since constructor requires it
if (storage === '') storageInstance = await deployer.deploy(Storage, "0x0000000000000000000000000000000000000000")
else storageInstance = await Storage.at(STORAGE)
storage = storageInstance.address
if (xft === '') xftInstance = await deployer.deploy(XFTMock)
else xftInstance = await XFTMock.at(xftToken)
xft = xftInstance.address
let xftOldMockInstance = await deployer.deploy(XFTOLDMock);
const xftOldMock = xftOldMockInstance.address;
// Deploy TokenSwap contract and grant it XFT Minter Permissions
const xftNewMock = xft
let tokenSwapInstance = await deployer.deploy(TokenSwap, xftOldMock, xftNewMock)
let tokenSwap = tokenSwapInstance.address
await xftInstance.grantRole(minterRole, tokenSwap)
// Contract interfaces for UniswapV3
const nonfungiblePositionManager = await INonfungiblePositionManager.at(NonfungiblePositionManager)
const xftUniswapV3Pool = await IUniswapV3Pool.at(xftUniPool)
const uniswapV3Factory = await IUniswapV3Factory.at(UniswapV3Factory)
const _weth9 = await IWETH9.at(weth9) // For wrapping/unwrapping
const WETH = await IERC20.at(weth9) // For ERC20 stuff
// Depositing 10000 ETH into Wrapped ETH, to be used in UniswapV3 Pools
await _weth9.deposit({value: "10000000000000000000000"})
// Contract deployment address
account = deployer.networks.development.from;
// Pool Initialization Function
/* Parameters
token0_: address
token1_: address
fee_: uint24,
pool_: address
*/
let initPool = async function (token0_, token1_, fee_, pool_) {
const pool = await IUniswapV3Pool.at(pool_) //Pool we're fetching init price from
const slot0 = await pool.slot0()
const price = slot0.sqrtPriceX96
const fee = await pool.fee()
// Uniswap reverts pool initialization if you don't sort by address number, beware!
let token0, token1
if (token1_ > token0_) {
token1 = token1_
token0 = token0_
} else {
token1 = token0_
token0 = token1_
}
await nonfungiblePositionManager.createAndInitializePoolIfNecessary(token0, token1, fee, price)
}
let initPoolETH = async function (token0_, token1_, fee_, price_) {
// Uniswap reverts pool initialization if you don't sort by address number, beware!
let sqrtPrice = Math.sqrt(price_)
let token0, token1, price
if (token1_ > token0_) {
token1 = token1_
token0 = token0_
} else {
token1 = token0_
token0 = token1_
}
if (token0 === weth9) price = BigInt(sqrtPrice*2**96)
else price = BigInt(2**96/sqrtPrice)
await nonfungiblePositionManager.createAndInitializePoolIfNecessary(token0, token1, fee_, price)
}
// Liquidity Providing Function
/* Parameters
token0_: address
token1_: address
fee_: uint24
tickLower_: int24
tickUpper_: int24
amount0ToMint_: uint256
amount1ToMint_: uint256
amount0Min_: uint256
amount1Min_: uint256
recipient: address
timestamp_: uint256
*/
let addLiquidity = async function (
token0_,
token1_,
fee_,
tickLower_ = -887220,
tickUpper_ = 887220,
amount0ToMint_,
amount1ToMint_,
amount0Min_ = 0,
amount1Min_ = 0,
recipient_ = account,
timestamp_ = Math.ceil(Date.now()/1000 + 300)) {
// Uniswap reverts pool initialization if you don't sort by address number, beware!
let token0, token1
if (token1_ > token0_) {
token1 = await aUSDMock.at(token1_)
token0 = await aUSDMock.at(token0_)
} else {
token1 = await aUSDMock.at(token0_)
token0 = await aUSDMock.at(token1_)
}
let mintParams = [
token0.address,
token1.address,
fee_,
tickLower_,
tickUpper_,
BigInt(amount0ToMint_),
BigInt(amount1ToMint_),
amount0Min_,
amount1Min_,
recipient_,
timestamp_
]
await token0.approve(NonfungiblePositionManager, amount0ToMint_)
await token1.approve(NonfungiblePositionManager, amount1ToMint_)
const {logs} = await nonfungiblePositionManager.mint(mintParams)
const tokenId = logs[1].args.tokenId
return tokenId
}
// Pool parameters from XFT/WETH UniswapV3 pool
const _fee = await xftUniswapV3Pool.fee()
// Min and Max tick numbers, as a multiple of 60
const tickMin = -887220
const tickMax = 887220
const defaultTimestamp = Math.ceil(Date.now()/1000 + 300)
// ERC20 contract interfaces for XFT and WETH
const xftNew = tokenStyle["XFT"]["address"]
const xftNewInstance = await IERC20.at(xftNew)
// Granting minter role to deployment account for Mock ETH and Mock XFT
await xftInstance.grantRole(minterRole, account)
// Live token balances from the XFT/WETH Uniswap V3 Pool
let token0, token1, amount0, amount1, amount0Min, amount1Min
let oldBalXFT = await xftNewInstance.balanceOf(xftUniPool)
let oldBalWETH = await WETH.balanceOf(xftUniPool)
if (xftNewMock > weth9) {
token0 = weth9
token1 = xftNewMock
amount0 = oldBalWETH
amount1 = oldBalXFT
amount0Min = BigInt(amount0)*BigInt(9)/BigInt(10) // 90% of the original balance
amount1Min = BigInt(amount1)*BigInt(9)/BigInt(10) // 90% of the original balance
} else {
token0 = xftNewMock
token1 = weth9
amount0 = oldBalXFT
amount1 = oldBalWETH
amount0Min = BigInt(amount0)*BigInt(9)/BigInt(10) // 90% of the original balance
amount1Min = BigInt(amount1)*BigInt(9)/BigInt(10) // 90% of the original balance
}
// Deployer is the LP
let recipient = account
// Minting surplus tokens to the token deployer/liquidity provider
await xftInstance.mint(recipient, BigInt(1e26))
// Raw BigNumber tick values from NonfungiblePositionManager
let xftwethTickLower = (await nonfungiblePositionManager.positions(nftID)).tickLower
let xftwethTickUpper = (await nonfungiblePositionManager.positions(nftID)).tickUpper
// Initialize mock version of the old XFT pool
await initPool(weth9, xftNewMock, _fee, xftUniPool)
const tokenId = await addLiquidity(
token0,
token1,
_fee,
xftwethTickLower,
xftwethTickUpper,
amount0,
amount1,
amount0Min,
amount1Min,
account,
defaultTimestamp
)
// Logging pool values
xftPool = await uniswapV3Factory.getPool(weth9, xftNewMock, _fee)
console.log(`XFT Pool: ${xftPool}`)
const poolBalWETH = await WETH.balanceOf(xftPool)
const poolBalXFT = await xftInstance.balanceOf(xftPool)
console.log(`New Pool Bal WETH: ${poolBalWETH.toString()}`)
console.log(`New Pool Bal XFT: ${poolBalXFT.toString()}`)
let xftPoolInstance = await IUniswapV3Pool.at(xftPool)
let xftPoolSlot0 = await xftPoolInstance.slot0()
console.log(`New XFT Pool Price: ${xftPoolSlot0.sqrtPriceX96}`)
let cardinality = 10
// Writing XFT Mock contract to shifters.ts
shifters["XFT"] = {
color: tokenStyle["XFT"]["color"],
icon: tokenStyle["XFT"]["icon"],
name: tokenStyle["XFT"]["name"],
symbol: `${tokenStyle["XFT"]["name"]}`,
zkSymbol: `${tokenStyle["XFT"]["name"]}`,
denom: "0",
contract: xft,
shifter: "0x0000000000000000000000000000000000000000",
}
shifters["WETH"] = {
color: tokenStyle["XFT"]["color"],
icon: "assets/images/wETH-white.svg",
name: "Ether",
symbol: "ETH",
zkSymbol: "WETH",
denom: "0",
contract: weth9,
shifter: "0x0000000000000000000000000000000000000000",
}
// Deploy oracle
let oracleInstance = await deployer.deploy(
Oracle,
weth9,
INTERVAL_SHORT,
INTERVAL_LONG,
BigInt(975e15) //threshold 0.975*1e18
)
oracle = oracleInstance.address
for (let [key, value] of Object.entries(tokenStyle)) {
let token = value["address"]
let name = value["name"]
let symbol = value["symbol"]
let tokenPool = value["pool"]
let chainlink = value["chainlink"]
let threshold = value["threshold"]
let denoms = value["denoms"]
let price = value["price"]
if (chainlink !== "0x0000000000000000000000000000000000000000") {
let chainlinkInstance = await AggregatorV3.at(chainlink)
let latestRoundData = await chainlinkInstance.latestRoundData();
let latestRoundPrice = Number(latestRoundData.answer);
let decimals = Number(await chainlinkInstance.decimals());
price = latestRoundPrice / 10**decimals
console.log(`Chainlink Price ${name}/ETH: ${price}`)
}
// Deploy mock token if not already deployed .
if (token === '') tokenInstance = await deployer.deploy(aUSDMock, name, symbol)
else tokenInstance = await aUSDMock.at(token)
token = tokenInstance.address
if (key !== "XFT") {
await tokenInstance.grantRole(minterRole, account)
await tokenInstance.mint(account, BigInt(value["mint"]))
let poolAddress = await uniswapV3Factory.getPool(weth9, token, _fee)
if (poolAddress === "0x0000000000000000000000000000000000000000") await initPoolETH(weth9, token, _fee, price)
tokenPool = await uniswapV3Factory.getPool(weth9, token, _fee)
value["pool"] = tokenPool
await addLiquidity(
weth9,
token,
_fee,
tickMin,
tickMax,
BigInt(value["mint"]),
BigInt(value["mint"]),
BigInt(0),
BigInt(0),
recipient,
defaultTimestamp
)
const weth9Bal = await WETH.balanceOf(tokenPool)
const tokenBal = await tokenInstance.balanceOf(tokenPool)
console.log(`WETH balance ${weth9Bal.toString()}`)
console.log(`${key} balance ${tokenBal.toString()}`)
}
// Iterate denoms
if (key !== "XFT" && denoms.length > 0)
for (let denom of denoms) {
const denomWei = Web3Utils.toWei(denom)
let shifter;
if (key == "anonETH") console.log();
shifter = await deployer.deploy(
XFTanon,
verifier,
hasher,
storage,
denomWei,
ethDenomination,
MERKLE_TREE_HEIGHT,
xft,
token,
oracle,
xftPool,
tokenPool,
weth9,
chainlink,
threshold
)
// Settings shifter address in storage to deployed shifter contract
await storageInstance.addShifter(shifter.address)
// Make sure the shifter can burn/mint
await tokenInstance.grantRole(burnerRole, shifter.address)
await tokenInstance.grantRole(minterRole, shifter.address)
await xftInstance.grantRole(minterRole, shifter.address)
await xftInstance.grantRole(burnerRole, shifter.address)
/*shifters["shifters"][`${tokenSymbol}_${denomination}`] = {
display: `${tokenSymbol} (${denomination})`,
name: tokenName,
address: shifter.address,
denomination: denom.toString(),
verifier: verifier,
storage: storage,
xftToken: token,
output: usd
}*/
let varName = `${key.toString()}${denom.replace(".", "")}`
shifters[varName] = {
color: tokenStyle[key]["color"],
icon: tokenStyle[key]["icon"],
name: tokenStyle[key]["name"],
symbol: `${tokenStyle[key]["name"]} (${denom})`,
zkSymbol: `${tokenStyle[key]["name"]}${denom.replace(".", "")}`,
denom: denomWei.toString(),
contract: token,
shifter: shifter.address,
}
console.log(`
Token: ${key}
Denomination: ${denom}
Shifter deployed to ${shifter.address}
`)
}
}
// Get the pools to be as old as INTERVAL_LONG
await advanceTimeAndBlock(INTERVAL_LONG)
await xftPoolInstance.increaseObservationCardinalityNext(cardinality)
console.log(`
Verifier deployed to ${verifier}
Hasher deployed to ${hasher}
XFT Token deployed to ${xft}
Storage deployed to ${storage}
Oracle deployed to ${oracle}
`)
fs.writeFileSync('./shifters.ts', `export const shifters = ${JSON.stringify(shifters, null, 4)}`);
})
}
{
"name": "circuits",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build:circuit:compile": "npx circom circuits/withdraw.circom -o build/circuits/withdraw.json && npx snarkjs info -c build/circuits/withdraw.json",
"build:circuit:setup": "npx snarkjs setup --protocol groth -c build/circuits/withdraw.json --pk build/circuits/withdraw_proving_key.json --vk build/circuits/withdraw_verification_key.json",
"build:circuit:bin": "node node_modules/websnark/tools/buildpkey.js -i build/circuits/withdraw_proving_key.json -o build/circuits/withdraw_proving_key.bin",
"build:circuit:contract": "npx snarkjs generateverifier -v build/circuits/Verifier.sol --vk build/circuits/withdraw_verification_key.json",
"build:circuit": "npm run build:circuit:compile && npm run build:circuit:setup && npm run build:circuit:bin && npm run build:circuit:contract",
"build:contract": "npx truffle compile",
"build": "npm run build:circuit && npm run build:contract",
"test": "npx truffle test",
"migrate": "npm run migrate:kovan",
"migrate:dev": "npx truffle migrate --network development --reset",
"multimigrate:dev": "node ./scripts/multi.js",
"eslint": "eslint --ext .js --ignore-path .gitignore .",
"prettier:check": "prettier --check . --config .prettierrc",
"prettier:fix": "prettier --write . --config .prettierrc",
"lint": "yarn eslint && yarn prettier:check",
"flat": "npx truffle-flattener contracts/XFTanon.sol > XFTanon_flat.sol",
"download": "bash scripts/download.sh",
"coverage": "yarn truffle run coverage",
"fork": "npx ganache-cli -m \"artwork story jeans lyrics expose update sword absurd rail game argue submit\" -p 8545 -h 0.0.0.0 -l 80000000 --fork <RPC URL HERE> --defaultBalanceEther 100000",
"ganache": "npx ganache-cli -m \"artwork story jeans lyrics expose update sword absurd rail game argue submit\" -p 8545 -h 0.0.0.0"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@chainlink/contracts": "^0.5.1",
"@openzeppelin/contracts": "^3.4.1",
"@truffle/contract": "^4.0.39",
"@truffle/hdwallet-provider": "^1.0.24",
"@uniswap/v3-core": "^1.0.1",
"@uniswap/v3-periphery": "^1.4.3",
"@uniswap/v3-staker": "^1.0.2",
"axios": "^0.19.0",
"babel-eslint": "^10.1.0",
"bn-chai": "1.0.1",
"browserify": "^16.5.0",
"chai": "4.2.0",
"chai-as-promised": "7.1.1",
"circom": "^0.0.35",
"circomlib": "git+https://github.com/tornadocash/circomlib.git#c372f14d324d57339c88451834bf2824e73bbdbc",
"commander": "^4.1.1",
"dotenv": "^8.2.0",
"eslint": "^7.19.0",
"eslint-config-prettier": "^7.2.0",
"eslint-plugin-prettier": "^3.3.1",
"esm": "^3.2.25",
"eth-json-rpc-filters": "^4.1.1",
"fixed-merkle-tree": "^0.6.0",
"ganache-cli": "^6.7.0",
"prettier": "^2.2.1",
"prettier-plugin-solidity": "^1.0.0-beta.3",
"snarkjs": "git+https://github.com/tornadocash/snarkjs.git#869181cfaf7526fe8972073d31655493a04326d5",
"solhint-plugin-prettier": "^0.0.5",
"solidity-coverage": "^0.7.20",
"truffle": "^5.1.67",
"truffle-flattener": "^1.4.2",
"web3": "^1.3.4",
"web3-utils": "^1.3.4",
"websnark": "git+https://github.com/tornadocash/websnark.git#4c0af6a8b65aabea3c09f377f63c44e7a58afa6d"
}
}
// Generates Hasher artifact at compile-time using Truffle's external compiler
// mechanism
const path = require('path')
const fs = require('fs')
const genContract = require('circomlib/src/mimcsponge_gencontract.js')
// where Truffle will expect to find the results of the external compiler
// command
const outputPath = path.join(__dirname, '..', 'build', 'Hasher.json')
function main() {
const contract = {
contractName: 'Hasher',
abi: genContract.abi,
bytecode: genContract.createCode('mimcsponge', 220),
}
fs.writeFileSync(outputPath, JSON.stringify(contract))
}
main()
if [ ! -d ./build ]; then
mkdir ./build
echo "./build directory created"
else
echo "./build already exists"
fi
if [ ! -d ./build/circuits ]; then
mkdir ./build/circuits
"./build/circuits directory created"
else
echo "./build/circuits already exists"
fi
cp ./download/Verifier.sol ./build/circuits/Verifier.sol
echo "Verifier copied"
cp ./download/withdraw_proving_key.json ./build/circuits/withdraw_proving_key.json
echo "Proving key json copied"
cp ./download/withdraw_proving_key.bin ./build/circuits/withdraw_proving_key.bin
echo "Proving key binary copied"
cp ./download/withdraw_verification_key.json ./build/circuits/withdraw_verification_key.json
echo "Verifying key copied"
cp ./download/withdraw.json ./build/circuits/withdraw.json
echo "Withdraw.json copied"
\ No newline at end of file
// This module is used only for tests
function send(method, params = []) {
return new Promise((resolve, reject) => {
// eslint-disable-next-line no-undef
web3.currentProvider.send({
jsonrpc: '2.0',
id: Date.now(),
method,
params,
}, (err, res) => {
return err ? reject(err) : resolve(res)
})
})
}
const takeSnapshot = async () => {
return await send('evm_snapshot')
}
const traceTransaction = async (tx) => {
return await send('debug_traceTransaction', [tx, {}])
}
const revertSnapshot = async (id) => {
await send('evm_revert', [id])
}
const mineBlock = async (timestamp) => {
await send('evm_mine', [timestamp])
}
const increaseTime = async (seconds) => {
await send('evm_increaseTime', [seconds])
}
const minerStop = async () => {
await send('miner_stop', [])
}
const minerStart = async () => {
await send('miner_start', [])
}
module.exports = {
takeSnapshot,
revertSnapshot,
mineBlock,
minerStop,
minerStart,
increaseTime,
traceTransaction,
}
/* eslint-disable prettier/prettier */
/* global artifacts, web3, contract */
require('chai').use(require('bn-chai')(web3.utils.BN)).use(require('chai-as-promised')).should()
const fs = require('fs')
const Web3Utils = require('web3-utils')
const { takeSnapshot, revertSnapshot } = require('../scripts/ganacheHelper')
const Storage = artifacts.require('./Storage.sol')
const Shifter = artifacts.require('./XFTanon.sol')
const XFT = artifacts.require('./XFTMock.sol')
const XFTOLDMock = artifacts.require('./XFTOLDMock.sol')
const Token = artifacts.require('anonUSD')
const SwapRouter = artifacts.require('./ISwapRouter.sol')
const UniV3Pool = artifacts.require('./IUniswapV3Pool.sol')
const Oracle = artifacts.require('./Oracle.sol')
const ERC20 = artifacts.require('./IERC20.sol')
const TokenSwap = artifacts.require('./TokenSwap.sol')
const { tokenStyle, weth9, uniswap }
= require('../config.json')
const burnerRole = Web3Utils.keccak256("BURNER_ROLE")
const minterRole = Web3Utils.keccak256("MINTER_ROLE")
require = require('esm')(module)
let allShifters = require('../shifters.ts').shifters
const cardinalityLaunch = 10 // How many observations to save in a pool, at launch
advanceTimeAndBlock = async (time) => {
await advanceTime(time);
await advanceBlock();
return Promise.resolve(web3.eth.getBlock('latest'));
}
advanceTime = (time) => {
return new Promise((resolve, reject) => {
web3.currentProvider.send({
jsonrpc: "2.0",
method: "evm_increaseTime",
params: [time],
id: new Date().getTime()
}, (err, result) => {
if (err) { return reject(err); }
return resolve(result);
});
});
}
advanceBlock = () => {
return new Promise((resolve, reject) => {
web3.currentProvider.send({
jsonrpc: "2.0",
method: "evm_mine",
id: new Date().getTime()
}, (err, result) => {
if (err) { return reject(err); }
const newBlockHash = web3.eth.getBlock('latest').hash;
return resolve(newBlockHash)
});
});
}
contract('Oracle', (accounts) => {
let shifter, shifterBTC, shifterETH
let xft
let token
const sender = accounts[0]
let snapshotId
before(async () => {
shifter = await Shifter.at(allShifters['anonUSD500'].shifter) // Use a specific shifter
shifterETH = await Shifter.at(allShifters['anonETH1'].shifter)
shifterBTC = await Shifter.at(allShifters['anonBTC1'].shifter)
storage = await Storage.deployed()
oracle = await Oracle.deployed()
tokenSwap = await TokenSwap.deployed()
xftOld = await XFTOLDMock.deployed()
xft = await XFT.at(allShifters['XFT'].contract)
token = await Token.at(allShifters['anonUSD500'].contract)
tokenETH = await Token.at(allShifters['anonETH1'].contract)
tokenBTC = await Token.at(allShifters['anonBTC1'].contract)
weth = await ERC20.at(weth9)
let shifterPoolETH = await shifterETH.tokenPool()
let shifterPoolUSD = await shifter.tokenPool()
let shifterPoolBTC = await shifterBTC.tokenPool()
let poolETH = await UniV3Pool.at(shifterPoolETH)
let poolUSD = await UniV3Pool.at(shifterPoolUSD)
let poolBTC = await UniV3Pool.at(shifterPoolBTC)
await poolETH.increaseObservationCardinalityNext.sendTransaction(cardinalityLaunch, {from: sender})
await poolUSD.increaseObservationCardinalityNext.sendTransaction(cardinalityLaunch, {from: sender})
await poolBTC.increaseObservationCardinalityNext.sendTransaction(cardinalityLaunch, {from: sender})
// Give the shifter roles
await xft.grantRole(burnerRole, shifter.address)
await xft.grantRole(burnerRole, shifterETH.address)
await token.grantRole(minterRole, shifter.address)
await tokenETH.grantRole(minterRole, shifterETH.address)
// Give sender roles
await xft.grantRole(minterRole, sender)
await token.grantRole(minterRole, sender)
await tokenETH.grantRole(minterRole, sender)
// Snapshot here
snapshotId = await takeSnapshot()
})
describe('#pool check', () => {
it('should work ETH', async () => {
let anonETHPool = await shifterETH.tokenPool()
let ETHPool = await UniV3Pool.at(anonETHPool)
let cardinality = cardinalityLaunch + 100
let { logs } = await ETHPool.increaseObservationCardinalityNext.sendTransaction(cardinality, {from: sender})
const {sqrtPriceX96, unlocked} = await ETHPool.slot0()
BigInt(sqrtPriceX96).should.not.be.equal(BigInt(0))
unlocked.should.be.equal(true)
logs[0].event.should.be.equal("IncreaseObservationCardinalityNext")
logs[0].args.observationCardinalityNextOld.should.be.eq.BN(cardinalityLaunch)
logs[0].args.observationCardinalityNextNew.should.be.eq.BN(cardinality)
})
it('should work USD', async () => {
let tokenPool = await shifter.tokenPool()
let pool = await UniV3Pool.at(tokenPool)
let cardinality = 100 + cardinalityLaunch
let { logs } = await pool.increaseObservationCardinalityNext.sendTransaction(cardinality, {from: sender})
const {sqrtPriceX96, unlocked} = await pool.slot0()
BigInt(sqrtPriceX96).should.not.be.equal(BigInt(0))
unlocked.should.be.equal(true)
logs[0].event.should.be.equal("IncreaseObservationCardinalityNext")
logs[0].args.observationCardinalityNextOld.should.be.eq.BN(cardinalityLaunch)
logs[0].args.observationCardinalityNextNew.should.be.eq.BN(cardinality)
})
it('should work BTC', async () => {
let tokenPool = await shifterBTC.tokenPool()
let pool = await UniV3Pool.at(tokenPool)
let cardinality = 100 + cardinalityLaunch
let { logs } = await pool.increaseObservationCardinalityNext.sendTransaction(cardinality, {from: sender})
const {sqrtPriceX96, unlocked} = await pool.slot0()
BigInt(sqrtPriceX96).should.not.be.equal(BigInt(0))
unlocked.should.be.equal(true)
logs[0].event.should.be.equal("IncreaseObservationCardinalityNext")
logs[0].args.observationCardinalityNextOld.should.be.eq.BN(cardinalityLaunch)
logs[0].args.observationCardinalityNextNew.should.be.eq.BN(cardinality)
})
})
describe('#swap check anonETH', () => {
it('should work', async () => {
// Setting up the swap anonETH -> WETH
let routerAddress = uniswap.SwapRouter
let router = await SwapRouter.at(routerAddress)
let anonETHPool = await shifterETH.tokenPool()
let xftPool = await shifter.xftPool()
await tokenETH.mint(sender, BigInt(1e22)) // 10,000 anonETH
let tokenIn, tokenOut, fee, recipient, deadline, amountOut, amountInMaximum, sqrtPriceLimitX96
tokenIn = tokenETH.address
tokenOut = weth9
fee = "3000"
recipient = sender
deadline = Math.round((Date.now() / 1000 + 300)).toString() // Deadline five minutes from 'now'
deadline+=1800 // Time advanced 30min in migration to allow for the long interval
amountOut = BigInt(5e17).toString() //0.5 ETH
amountInMaximum = "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
sqrtPriceLimitX96 = "0x0"
input = await tokenETH.balanceOf(sender)
await tokenETH.approve(routerAddress, amountInMaximum)
// paramsCall is Uniswap's price quote, by running the function but not yet sending
const paramsCall = [tokenIn, tokenOut, fee, sender, deadline, amountOut, input, sqrtPriceLimitX96]
let amountCall = await router.exactOutputSingle.call(paramsCall, {from: sender})
amountInMaximum = amountCall.toString()
console.log(`Amount of anonETH to swap for 0.5 WETH, in Wei: ${amountInMaximum}`)
// Checking whether the token is below threshold before swapping
let threshold = tokenStyle.anonETH.threshold
let intervalShort = 180
let intervalLong = 1800
let chainlinkFeed = tokenStyle.anonETH.chainlink
let tokenBelowThreshold = await oracle.isTokenBelowThreshold.call(threshold, anonETHPool, intervalShort, intervalLong, chainlinkFeed, weth9, {from: sender})
// Checking the pool's price before swapping
let uniSqrtPrice = await oracle.getV3SqrtPrice.call(anonETHPool, intervalShort, intervalLong, {from: sender})
let uniPrice = BigInt(uniSqrtPrice.toString())*BigInt(uniSqrtPrice.toString())/BigInt(2**192)
console.log(`Swap price of pool, from Wei: ${uniPrice.toString()}`)
console.log(`Token below threshold: ${tokenBelowThreshold}`)
// Checking the tokens for amount before swapping
let tokensForAmount = await oracle.getTokensForAmount.call(anonETHPool, intervalShort, intervalLong, chainlinkFeed, BigInt(1e18), weth9)
tokensForAmount = tokensForAmount.toString()
let xftForAmount = await oracle.getTokensForAmount.call(xftPool, intervalShort, intervalLong, chainlinkFeed, BigInt(1e18), weth9)
xftForAmount = xftForAmount.toString()
console.log(`Tokens for amount, XFT for 1 anonETH, in Wei: ${xftForAmount}`)
console.log(`Tokens for amount, anonETH for 1 WETH, in Wei: ${tokensForAmount}`)
// Executing swap transaction using the price quote from above
let params = [tokenIn, tokenOut, fee, recipient, deadline, amountOut, amountInMaximum, sqrtPriceLimitX96]
await router.exactOutputSingle.sendTransaction(params, {from: sender})
// Checking the variables after the swap is carried out to track changes
let tokenBelowThresholdAfter = await oracle.isTokenBelowThreshold.call(threshold, anonETHPool, intervalShort, intervalLong, chainlinkFeed, weth9, {from: sender})
let uniSqrtPriceAfter = await oracle.getV3SqrtPrice.call(anonETHPool, intervalShort, intervalLong, {from: sender})
let uniPriceAfter = BigInt(uniSqrtPriceAfter.toString())*BigInt(uniSqrtPriceAfter.toString())*BigInt(1e18)/BigInt(2**192)
console.log(`Swap price of pool after swap, in Wei: ${uniPriceAfter.toString()}`)
console.log(`Token below threshold, after swap: ${tokenBelowThresholdAfter}`)
})
})
describe('#swap check anonUSD', () => {
it('should work', async () => {
// Using the same setup as anonETH for anonUSD
let routerAddress = uniswap.SwapRouter
let router = await SwapRouter.at(routerAddress)
let anonUSDPool = await shifter.tokenPool()
let xftPool = await shifter.xftPool()
await token.mint(sender, BigInt(1e22))
let tokenIn, tokenOut, fee, recipient, deadline, amountOut, amountInMaximum, sqrtPriceLimitX96
tokenIn = token.address
tokenOut = weth9
fee = "3000"
recipient = sender
deadline = Math.round((Date.now() / 1000 + 300)).toString() // Deadline five minutes from 'now'
deadline+=1800 // Time advanced 30min in migration to allow for the long interval
amountOut = BigInt(5e17).toString() // 0.5 ETH
amountInMaximum = "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
sqrtPriceLimitX96 = "0x0"
input = await token.balanceOf(sender)
await token.approve(routerAddress, amountInMaximum)
const paramsCall = [tokenIn, tokenOut, fee, sender, deadline, amountOut, input, sqrtPriceLimitX96]
let amountCall = await router.exactOutputSingle.call(paramsCall, {from: sender})
amountInMaximum = amountCall.toString()
console.log(`Amount of anonUSD to swap for 0.5 WETH, in Wei: ${amountInMaximum}`)
let threshold = tokenStyle.anonUSD.threshold
let intervalShort = 180
let intervalLong = 1800
let chainlinkFeed = tokenStyle.anonUSD.chainlink
let tokenBelowThreshold = await oracle.isTokenBelowThreshold.call(threshold, anonUSDPool, intervalShort, intervalLong, chainlinkFeed, weth9, {from: sender})
let uniSqrtPrice = await oracle.getV3SqrtPrice.call(anonUSDPool, intervalShort, intervalLong, {from: sender})
let uniPrice = BigInt(uniSqrtPrice.toString())*BigInt(uniSqrtPrice.toString())*BigInt(1e18)/BigInt(2**192)
console.log(`Swap price of pool, in Wei: ${uniPrice.toString()}`)
console.log(`Token below threshold before swap: ${tokenBelowThreshold}`)
let tokensForAmount = await oracle.getTokensForAmount.call(anonUSDPool, intervalShort, intervalLong, chainlinkFeed, BigInt(1e18), weth9)
tokensForAmount = tokensForAmount.toString()
let xftForAmount = await oracle.getTokensForAmount.call(xftPool, intervalShort, intervalLong, chainlinkFeed, BigInt(1e18), weth9)
xftForAmount = xftForAmount.toString()
console.log(`Tokens for amount, XFT for 1 anonUSD, in Wei: ${xftForAmount}`)
console.log(`Tokens for amount, ETH for 1 USD, in Wei: ${tokensForAmount}`)
let params = [tokenIn, tokenOut, fee, recipient, deadline, amountOut, amountInMaximum, sqrtPriceLimitX96]
await router.exactOutputSingle.sendTransaction(params, {from: sender})
let tokenBelowThresholdAfter = await oracle.isTokenBelowThreshold.call(threshold, anonUSDPool, intervalShort, intervalLong, chainlinkFeed, weth9, {from: sender})
let uniSqrtPriceAfter = await oracle.getV3SqrtPrice.call(anonUSDPool, intervalShort, intervalLong, {from: sender})
let uniPriceAfter = BigInt(uniSqrtPriceAfter.toString())*BigInt(uniSqrtPriceAfter.toString())*BigInt(1e18)/BigInt(2**192)
console.log(`Swap price of pool after swap, in Wei: ${uniPriceAfter.toString()}`)
console.log(`Token below threshold, after swap: ${tokenBelowThresholdAfter}`)
})
})
describe('#swap check anonBTC', () => {
it('should work', async () => {
// Using the same setup for anonBTC as for anonETH
let routerAddress = uniswap.SwapRouter
let router = await SwapRouter.at(routerAddress)
let anonBTCPool = await shifterBTC.tokenPool()
let xftPool = await shifter.xftPool()
await tokenBTC.mint(sender, BigInt(1e22))
let tokenIn, tokenOut, fee, recipient, deadline, amountOut, amountInMaximum, sqrtPriceLimitX96
tokenIn = tokenBTC.address
tokenOut = weth9
fee = "3000"
recipient = sender
deadline = Math.round((Date.now() / 1000 + 300)).toString() // Deadline five minutes from 'now'
deadline+=1800 // Time advanced 30min in migration to allow for the long interval
amountOut = BigInt(5e17).toString() // 0.5 ETH
amountInMaximum = "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
sqrtPriceLimitX96 = "0x0"
input = await tokenBTC.balanceOf(sender)
await tokenBTC.approve(routerAddress, amountInMaximum)
const paramsCall = [tokenIn, tokenOut, fee, sender, deadline, amountOut, input, sqrtPriceLimitX96]
let amountCall = await router.exactOutputSingle.call(paramsCall, {from: sender})
amountInMaximum = amountCall.toString()
console.log(`Amount of anonBTC to swap for 0.5 WETH, in Wei: ${amountInMaximum}`)
let threshold = tokenStyle.anonBTC.threshold
let intervalShort = 180
let intervalLong = 1800
let chainlinkFeed = tokenStyle.anonBTC.chainlink
let tokenBelowThreshold = await oracle.isTokenBelowThreshold.call(threshold, anonBTCPool, intervalShort, intervalLong, chainlinkFeed, weth9, {from: sender})
let uniSqrtPrice = await oracle.getV3SqrtPrice.call(anonBTCPool, intervalShort, intervalLong, {from: sender})
let uniPrice = BigInt(uniSqrtPrice.toString())*BigInt(uniSqrtPrice.toString())*BigInt(1e18)/BigInt(2**192)
console.log(`Swap price of pool, from Wei: ${uniPrice.toString()}`)
console.log(`Token below threshold: ${tokenBelowThreshold}`)
let tokensForAmount = await oracle.getTokensForAmount.call(anonBTCPool, intervalShort, intervalLong, chainlinkFeed, BigInt(1e18), weth9)
tokensForAmount = tokensForAmount.toString()
let xftForAmount = await oracle.getTokensForAmount.call(xftPool, intervalShort, intervalLong, chainlinkFeed, BigInt(1e18), weth9)
xftForAmount = xftForAmount.toString()
console.log(`Tokens for amount, XFT for 1 anonBTC, in Wei: ${xftForAmount}`)
console.log(`Tokens for amount, WETH for 1 BTC, in Wei: ${tokensForAmount}`)
let params = [tokenIn, tokenOut, fee, recipient, deadline, amountOut, amountInMaximum, sqrtPriceLimitX96]
await router.exactOutputSingle.sendTransaction(params, {from: sender})
let tokenBelowThresholdAfter = await oracle.isTokenBelowThreshold.call(threshold, anonBTCPool, intervalShort, intervalLong, chainlinkFeed, weth9, {from: sender})
let uniSqrtPriceAfter = await oracle.getV3SqrtPrice.call(anonBTCPool, intervalShort, intervalLong, {from: sender})
let uniPriceAfter = BigInt(uniSqrtPriceAfter.toString())*BigInt(uniSqrtPriceAfter.toString())*BigInt(1e18)/BigInt(2**192)
console.log(`Swap price of pool after swap, in Wei: ${uniPriceAfter.toString()}`)
console.log(`Token below threshold, after swap: ${tokenBelowThresholdAfter}`)
})
})
describe('#tokenBelowThreshold', () => {
it('should change after swapping 5 anonETH for WETH', async () => {
let routerAddress = uniswap.SwapRouter
let router = await SwapRouter.at(routerAddress)
let pool = await shifterETH.tokenPool()
await tokenETH.mint(sender, BigInt(1e22))
let tokenIn, tokenOut, fee, recipient, deadline, amountOut, amountInMaximum, sqrtPriceLimitX96
tokenIn = tokenETH.address
tokenOut = weth9
fee = "3000"
recipient = sender
deadline = Math.round((Date.now() / 1000 + 300)).toString() // Deadline five minutes from 'now'
deadline+=1800 // Time advanced 30min in migration to allow for the long interval
amountOut = BigInt(5e18).toString() // 5 ETH
amountInMaximum = "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" //
sqrtPriceLimitX96 = "0x0"
input = await tokenETH.balanceOf(sender)
await tokenETH.approve(routerAddress, amountInMaximum)
const paramsCall = [tokenIn, tokenOut, fee, sender, deadline, amountOut, input, sqrtPriceLimitX96]
let amountCall = await router.exactOutputSingle.call(paramsCall, {from: sender})
amountInMaximum = amountCall.toString()
let threshold = tokenStyle.anonETH.threshold
let intervalShort = 180
let intervalLong = 1800
let chainlinkFeed = tokenStyle.anonETH.chainlink
let tokenBelowThresholdBefore = await oracle.isTokenBelowThreshold.call(
threshold, pool, intervalShort, intervalLong, chainlinkFeed, weth9, {from: sender}
)
let uniSqrtPriceBefore = await oracle.getV3SqrtPrice.call(pool, intervalShort, intervalLong, {from: sender})
let uniPriceBefore = BigInt(uniSqrtPriceBefore.toString())*BigInt(uniSqrtPriceBefore.toString())*BigInt(1e18)/BigInt(2**192)
let priceBelowThresholdBefore = (uniPriceBefore < threshold)
priceBelowThresholdBefore.should.be.equal(false)
tokenBelowThresholdBefore.should.be.equal(priceBelowThresholdBefore)
let params = [tokenIn, tokenOut, fee, recipient, deadline, amountOut, amountInMaximum, sqrtPriceLimitX96]
await router.exactOutputSingle.sendTransaction(params, {from: sender})
let tokenBelowThresholdAfter = await oracle.isTokenBelowThreshold.call(
threshold, pool, intervalShort, intervalLong, chainlinkFeed, weth9, {from: sender}
)
let uniSqrtPriceAfter = await oracle.getV3SqrtPrice.call(pool, intervalShort, intervalLong, {from: sender})
let uniPriceAfter = BigInt(uniSqrtPriceAfter.toString())*BigInt(uniSqrtPriceAfter.toString())/BigInt(2**192)
let priceBelowThresholdAfter = (uniPriceAfter < threshold)
// Tests
priceBelowThresholdAfter.should.be.equal(true)
tokenBelowThresholdAfter.should.be.equal(priceBelowThresholdAfter)
})
})
describe('#getCostSimpleShift anonETH', () => {
it('should return getTokensForAmount when at or above threshold', async () => {
let routerAddress = uniswap.SwapRouter
let router = await SwapRouter.at(routerAddress)
let pool = await shifterETH.tokenPool()
let xftPool = await shifterETH.xftPool()
let threshold = tokenStyle.anonETH.threshold
let linkFeed = tokenStyle.anonETH.chainlink
await tokenETH.mint(sender, BigInt(1e22)) // 10,000 anonETH
await oracle.setIntervalShort.sendTransaction(180, {from: sender})
await oracle.setIntervalLong.sendTransaction(1800, {from: sender})
let tokenIn, tokenOut, fee, recipient, deadline, amountOut, amountInMaximum, sqrtPriceLimitX96
tokenIn = tokenETH.address
tokenOut = weth9
fee = "3000"
recipient = sender
deadline = Math.round((Date.now() / 1000 + 300)).toString() // Deadline five minutes from 'now'
deadline += 1800 // Time advanced 30min in migration to allow for the long interval
amountOut = BigInt(1e18).toString() // 1 ETH, enough to lower pool price, but to remain above threshold
amountInMaximum = "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
sqrtPriceLimitX96 = "0x0"
input = await tokenETH.balanceOf(sender)
await tokenETH.approve(routerAddress, amountInMaximum)
let paramsCall = [tokenIn, tokenOut, fee, sender, deadline, amountOut, input, sqrtPriceLimitX96]
let amountCall = await router.exactOutputSingle.call(paramsCall, {from: sender})
amountInMaximum = amountCall.toString()
let intervalShort = 180
let intervalLong = 1800
let params = [tokenIn, tokenOut, fee, recipient, deadline, amountOut, amountInMaximum, sqrtPriceLimitX96]
await router.exactOutputSingle.sendTransaction(params, {from: sender})
let amount = BigInt(1e18)
let costSimpleShift = await oracle.getCostSimpleShift(amount, linkFeed, xftPool, pool)
costSimpleShift = costSimpleShift.toString()
let costRaw = (await oracle.getTokensRaw(xftPool, pool, intervalShort, intervalLong, amount, weth9)).toString()
let costAmount = (await oracle.getTokensForAmountSimpleShift(xftPool, intervalShort, intervalLong, linkFeed, amount, weth9)).toString()
let belowThreshold = await oracle.isTokenBelowThreshold(threshold, pool, intervalShort, intervalLong, linkFeed, weth9)
let costRawLeqCostAmount = (BigInt(costRaw) <= BigInt(costAmount))
// Tests
belowThreshold.should.be.equal(false)
costRawLeqCostAmount.should.be.equal(true)
costSimpleShift.should.be.equal(costAmount)
});
it('should return getTokensRaw when below threshold', async () => {
let routerAddress = uniswap.SwapRouter
let router = await SwapRouter.at(routerAddress)
let pool = await shifterETH.tokenPool()
let xftPool = await shifterETH.xftPool()
let threshold = tokenStyle.anonETH.threshold
let linkFeed = tokenStyle.anonETH.chainlink
await tokenETH.mint(sender, BigInt(1e22)) // 10,000 anonETH
await oracle.setIntervalShort.sendTransaction(180, {from: sender})
await oracle.setIntervalLong.sendTransaction(1800, {from: sender})
let tokenIn, tokenOut, fee, recipient, deadline, amountOut, amountInMaximum, sqrtPriceLimitX96
tokenIn = tokenETH.address
tokenOut = weth9
fee = "3000"
recipient = sender
deadline = Math.round((Date.now() / 1000 + 300)).toString() // Deadline five minutes from 'now'
deadline+=1800 // Time advanced 30min in migration to allow for the long interval
amountOut = BigInt(5e18).toString() // 5 ETH, enough to put price below threshold
amountInMaximum = "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
sqrtPriceLimitX96 = "0x0"
input = await tokenETH.balanceOf(sender)
await tokenETH.approve(routerAddress, amountInMaximum)
let paramsCall = [tokenIn, tokenOut, fee, sender, deadline, amountOut, input, sqrtPriceLimitX96]
let amountCall = await router.exactOutputSingle.call(paramsCall, {from: sender})
amountInMaximum = amountCall.toString()
let intervalShort = 180
let intervalLong = 1800
let params = [tokenIn, tokenOut, fee, recipient, deadline, amountOut, amountInMaximum, sqrtPriceLimitX96]
await router.exactOutputSingle.sendTransaction(params, {from: sender})
let amount = BigInt(1e18)
let costSimpleShift = await oracle.getCostSimpleShift(amount, linkFeed, xftPool, pool)
let costRaw = await oracle.getTokensRaw(xftPool, pool, intervalShort, intervalLong, amount, weth9)
let costAmount = await oracle.getTokensForAmount(xftPool, intervalShort, intervalLong, linkFeed, amount, weth9)
let belowThreshold = await oracle.isTokenBelowThreshold(threshold, pool, intervalShort, intervalLong, linkFeed, weth9)
let costRawLeqCostAmount_ = (BigInt(costRaw) <= BigInt(costAmount))
// Tests
belowThreshold.should.be.equal(true)
costRawLeqCostAmount_.should.be.equal(true)
costSimpleShift.toString().should.be.equal(costRaw.toString())
});
})
describe('#getV3SqrtPrice', () => {
it('should give Spot Price when Lowest', async () => {
await tokenETH.mint(sender, BigInt(1e22)) // 10,000 anonETH
let routerAddress = uniswap.SwapRouter
let router = await SwapRouter.at(routerAddress)
let pool = await shifterETH.tokenPool()
let ETHPool = await UniV3Pool.at(pool)
let cardinality = 10
await ETHPool.increaseObservationCardinalityNext.sendTransaction(cardinality, {from: sender})
let tokenIn, tokenOut, fee, recipient, deadline, amountOut, amountInMaximum, sqrtPriceLimitX96
tokenIn = tokenETH.address
tokenOut = weth9
fee = "3000"
recipient = sender
deadline = Math.round((Date.now() / 1000 + 300)).toString() // Deadline five minutes from 'now'
deadline+=1800 // Time advanced 30min in migration to allow for the long interval
advanceTimeAndBlock(1800)
amountOut = BigInt(5e18).toString() // 5 ETH
amountInMaximum = "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
sqrtPriceLimitX96 = "0x0"
input = await tokenETH.balanceOf(sender)
await tokenETH.approve(routerAddress, amountInMaximum)
let paramsCall = [tokenIn, tokenOut, fee, sender, deadline, amountOut, input, sqrtPriceLimitX96]
// Swap anonETH -> WETH at the end of the interval to make spot price the lowest
let amountCall = await router.exactOutputSingle.call(paramsCall, {from: sender})
amountInMaximum = amountCall.toString()
let intervalShort = 180
let intervalLong = 1800
deadline += 1800
let params = [tokenIn, tokenOut, fee, recipient, deadline, amountOut, amountInMaximum, sqrtPriceLimitX96]
await router.exactOutputSingle.sendTransaction(params, {from: sender})
let slot0ETH = await ETHPool.slot0()
let sqrtPriceX96Spot = slot0ETH.sqrtPriceX96
let getV3SqrtPriceShort = await oracle.getV3SqrtPriceAvg.call(pool, intervalShort, {from: sender})
let getV3SqrtPriceLong = await oracle.getV3SqrtPriceAvg.call(pool, intervalLong, {from: sender})
let getV3SqrtPrice = await oracle.getV3SqrtPrice.call(pool, intervalShort, intervalLong, {from: sender})
let shortLeqLong, spotLeqShort
const token0 = await ETHPool.token0()
if (token0 === weth9){
shortLeqLong = (getV3SqrtPriceShort >= getV3SqrtPriceLong)
spotLeqShort = (sqrtPriceX96Spot >= getV3SqrtPriceShort)
} else {
shortLeqLong = (getV3SqrtPriceShort <= getV3SqrtPriceLong)
spotLeqShort = (sqrtPriceX96Spot <= getV3SqrtPriceShort)
}
// Tests
shortLeqLong.should.be.equal(true)
spotLeqShort.should.be.equal(true)
getV3SqrtPrice.should.be.eq.BN(sqrtPriceX96Spot)
})
it('should give Short Interval Price when Lowest', async () => {
await tokenETH.mint(sender, BigInt(1e22)) // 10,000 anonETH
let routerAddress = uniswap.SwapRouter
let router = await SwapRouter.at(routerAddress)
let pool = await shifterETH.tokenPool()
let ETHPool = await UniV3Pool.at(pool)
let cardinality = 10
await ETHPool.increaseObservationCardinalityNext.sendTransaction(cardinality, {from: sender})
let tokenIn, tokenOut, fee, recipient, deadline, amountOut, amountInMaximum, sqrtPriceLimitX96
tokenIn = tokenETH.address
tokenOut = weth9
fee = "3000"
recipient = sender
deadline = Math.round((Date.now() / 1000 + 300)).toString() // Deadline five minutes from 'now'
deadline += 3600 // add an hour
amountOut = BigInt(5e18).toString() // 5 ETH
amountInMaximum = "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
sqrtPriceLimitX96 = "0x0"
input = await tokenETH.balanceOf(sender)
await tokenETH.approve(routerAddress, amountInMaximum)
let paramsCall = [tokenIn, tokenOut, fee, sender, deadline, amountOut, input, sqrtPriceLimitX96]
let amountCall = await router.exactOutputSingle.call(paramsCall, {from: sender})
amountInMaximum = amountCall.toString()
let intervalShort = 180
let intervalLong = 1800
advanceTimeAndBlock(intervalLong - intervalShort)
deadline += intervalLong*2
let params = [tokenIn, tokenOut, fee, recipient, deadline, amountOut, amountInMaximum, sqrtPriceLimitX96]
// Swap anonETH -> WETH at (intervalLong - intervalShort) to make the short interval price lower than the long
await router.exactOutputSingle.sendTransaction(params, {from: sender})
advanceTimeAndBlock(intervalShort)
// Swap tokenIn and tokenOut, WETH in anonETH out
input = await weth.balanceOf(sender)
await weth.approve.sendTransaction(routerAddress, amountInMaximum, {from: sender})
paramsCall = [tokenOut, tokenIn, fee, sender, deadline, amountOut, input, sqrtPriceLimitX96]
amountCall = await router.exactOutputSingle.call(paramsCall, {from: sender})
amountInMaximum = amountCall.toString()
params = [tokenOut, tokenIn, fee, recipient, deadline, amountOut, amountInMaximum, sqrtPriceLimitX96]
// Swap WETH -> anonETH to make short interval price lower than spot price
await router.exactOutputSingle.sendTransaction(params, {from: sender})
let slot0ETH = await ETHPool.slot0()
let sqrtPriceX96Spot = slot0ETH.sqrtPriceX96
let getV3SqrtPriceShort = await oracle.getV3SqrtPriceAvg.call(pool, intervalShort, {from: sender})
let getV3SqrtPriceLong = await oracle.getV3SqrtPriceAvg.call(pool, intervalLong, {from: sender})
let getV3SqrtPrice = await oracle.getV3SqrtPrice.call(pool, intervalShort, intervalLong, {from: sender})
let shortLeqLong, spotLeqShort
const token0 = await ETHPool.token0()
if (token0 === weth9){
shortLeqLong = (getV3SqrtPriceShort >= getV3SqrtPriceLong)
spotLeqShort = (sqrtPriceX96Spot >= getV3SqrtPriceShort)
} else {
shortLeqLong = (getV3SqrtPriceShort <= getV3SqrtPriceLong)
spotLeqShort = (sqrtPriceX96Spot <= getV3SqrtPriceShort)
}
// Tests
shortLeqLong.should.be.equal(true)
spotLeqShort.should.be.equal(false)
getV3SqrtPrice.should.be.eq.BN(getV3SqrtPriceShort)
})
it('should give Long Interval Price when Lowest', async () => {
await tokenETH.mint(sender, BigInt(1e22)) // 10,000 anonETH
let routerAddress = uniswap.SwapRouter
let router = await SwapRouter.at(routerAddress)
let pool = await shifterETH.tokenPool()
let ETHPool = await UniV3Pool.at(pool)
let cardinality = 10
await ETHPool.increaseObservationCardinalityNext.sendTransaction(cardinality, {from: sender})
let tokenIn, tokenOut, fee, recipient, deadline, amountOut, amountInMaximum, sqrtPriceLimitX96
tokenIn = tokenETH.address
tokenOut = weth9
fee = "3000"
recipient = sender
deadline = Math.round((Date.now() / 1000 + 300)).toString() // Deadline five minutes from 'now'
deadline += 3600 // add an hour
amountOut = BigInt(5e18).toString() // 5 ETH
amountInMaximum = "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
sqrtPriceLimitX96 = "0x0"
input = await tokenETH.balanceOf(sender)
await weth.approve.sendTransaction(routerAddress, amountInMaximum, {from: sender})
await tokenETH.approve(routerAddress, amountInMaximum)
let paramsCall = [tokenIn, tokenOut, fee, sender, deadline, amountOut, input, sqrtPriceLimitX96]
// Swap anonETH -> WETH at the beginning of the interval to make the long interval return the lowest price
let amountCall = await router.exactOutputSingle.call(paramsCall, {from: sender})
amountInMaximum = amountCall.toString()
let intervalShort = 180
let intervalLong = 1800
deadline += intervalLong*2
let params = [tokenIn, tokenOut, fee, recipient, deadline, amountOut, amountInMaximum, sqrtPriceLimitX96]
await router.exactOutputSingle.sendTransaction(params, {from: sender})
advanceTimeAndBlock(intervalLong - intervalShort)
// Swap tokenIn and tokenOut, WETH in anonETH out
input = await weth.balanceOf(sender)
paramsCall = [tokenOut, tokenIn, fee, sender, deadline, amountOut, input, sqrtPriceLimitX96]
amountCall = await router.exactOutputSingle.call(paramsCall, {from: sender})
amountInMaximum = amountCall.toString()
params = [tokenOut, tokenIn, fee, recipient, deadline, amountOut, amountInMaximum, sqrtPriceLimitX96]
// Swap WETH -> anonETH at the start of intervalShort to increase short interval and spot price
await router.exactOutputSingle.sendTransaction(params, {from: sender})
advanceTimeAndBlock(intervalShort)
let slot0ETH = await ETHPool.slot0()
let sqrtPriceX96Spot = slot0ETH.sqrtPriceX96
let getV3SqrtPriceShort = await oracle.getV3SqrtPriceAvg.call(pool, intervalShort, {from: sender})
let getV3SqrtPriceLong = await oracle.getV3SqrtPriceAvg.call(pool, intervalLong, {from: sender})
let getV3SqrtPrice = await oracle.getV3SqrtPrice.call(pool, intervalShort, intervalLong, {from: sender})
let shortLeqLong, spotLeqLong
const token0 = await ETHPool.token0()
if (token0 === weth9){
shortLeqLong = (getV3SqrtPriceShort >= getV3SqrtPriceLong)
spotLeqLong = (sqrtPriceX96Spot >= getV3SqrtPriceLong)
} else {
shortLeqLong = (getV3SqrtPriceShort <= getV3SqrtPriceLong)
spotLeqLong = (sqrtPriceX96Spot <= getV3SqrtPriceLong)
}
// Tests
shortLeqLong.should.be.equal(false)
spotLeqLong.should.be.equal(false)
getV3SqrtPrice.should.be.eq.BN(getV3SqrtPriceLong)
})
})
describe('#getV3SqrtPriceSimpleShift', () => {
it('should give Spot Price when Highest', async () => {
await tokenETH.mint(sender, BigInt(1e22)) // 10,000 anonETH
let routerAddress = uniswap.SwapRouter
let router = await SwapRouter.at(routerAddress)
let pool = await shifterETH.tokenPool()
let ETHPool = await UniV3Pool.at(pool)
let cardinality = 10
await ETHPool.increaseObservationCardinalityNext.sendTransaction(cardinality, {from: sender})
let tokenIn, tokenOut, fee, recipient, deadline, amountOut, amountInMaximum, sqrtPriceLimitX96
tokenOut = tokenETH.address
tokenIn = weth9
fee = "3000"
recipient = sender
deadline = Math.round((Date.now() / 1000 + 300)).toString() // Deadline five minutes from 'now'
deadline+=1800 // Time advanced 30min in migration to allow for the long interval
advanceTimeAndBlock(1800)
amountOut = BigInt(5e18).toString() // 5 ETH
amountInMaximum = "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
sqrtPriceLimitX96 = "0x0"
input = await weth.balanceOf(sender)
await weth.approve(routerAddress, amountInMaximum)
let paramsCall = [tokenIn, tokenOut, fee, sender, deadline, amountOut, input, sqrtPriceLimitX96]
// Swap anonETH -> WETH at the end of the interval to make spot price the lowest
let amountCall = await router.exactOutputSingle.call(paramsCall, {from: sender})
amountInMaximum = amountCall.toString()
let intervalShort = 180
let intervalLong = 1800
deadline += 1800
let params = [tokenIn, tokenOut, fee, recipient, deadline, amountOut, amountInMaximum, sqrtPriceLimitX96]
await router.exactOutputSingle.sendTransaction(params, {from: sender})
let slot0ETH = await ETHPool.slot0()
let sqrtPriceX96Spot = slot0ETH.sqrtPriceX96
let getV3SqrtPriceShort = await oracle.getV3SqrtPriceAvg.call(pool, intervalShort, {from: sender})
let getV3SqrtPriceLong = await oracle.getV3SqrtPriceAvg.call(pool, intervalLong, {from: sender})
let getV3SqrtPrice = await oracle.getV3SqrtPriceSimpleShift.call(pool, intervalShort, intervalLong, {from: sender})
let shortGeqLong, spotGeqShort
const token0 = await ETHPool.token0()
if (token0 === weth9){
shortGeqLong = (getV3SqrtPriceShort <= getV3SqrtPriceLong)
spotGeqShort = (sqrtPriceX96Spot <= getV3SqrtPriceShort)
} else {
shortGeqLong = (getV3SqrtPriceShort >= getV3SqrtPriceLong)
spotGeqShort = (sqrtPriceX96Spot >= getV3SqrtPriceShort)
}
// Tests
shortGeqLong.should.be.equal(true)
spotGeqShort.should.be.equal(true)
getV3SqrtPrice.should.be.eq.BN(sqrtPriceX96Spot)
})
it('should give Short Interval Price when Highest', async () => {
await tokenETH.mint(sender, BigInt(1e22)) // 10,000 anonETH
let routerAddress = uniswap.SwapRouter
let router = await SwapRouter.at(routerAddress)
let pool = await shifterETH.tokenPool()
let ETHPool = await UniV3Pool.at(pool)
let cardinality = 10
await ETHPool.increaseObservationCardinalityNext.sendTransaction(cardinality, {from: sender})
let tokenIn, tokenOut, fee, recipient, deadline, amountOut, amountInMaximum, sqrtPriceLimitX96
tokenOut = tokenETH.address
tokenIn = weth9
fee = "3000"
recipient = sender
deadline = Math.round((Date.now() / 1000 + 300)).toString() //Deadline five minutes from 'now'
deadline += 3600 //add an hour
amountOut = BigInt(5e18).toString() //5 ETH
amountInMaximum = "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
sqrtPriceLimitX96 = "0x0"
input = await weth.balanceOf(sender)
await weth.approve(routerAddress, amountInMaximum)
let paramsCall = [tokenIn, tokenOut, fee, sender, deadline, amountOut, input, sqrtPriceLimitX96]
let amountCall = await router.exactOutputSingle.call(paramsCall, {from: sender})
amountInMaximum = amountCall.toString()
let intervalShort = 180
let intervalLong = 1800
advanceTimeAndBlock(intervalLong - intervalShort)
deadline += intervalLong*2
let params = [tokenIn, tokenOut, fee, recipient, deadline, amountOut, amountInMaximum, sqrtPriceLimitX96]
//Swap WETH -> anonUSD at (intervalLong - intervalShort) to make the short interval price lower than the long
await router.exactOutputSingle.sendTransaction(params, {from: sender})
advanceTimeAndBlock(intervalShort)
//Swap tokenIn and tokenOut, anonETH in WETH out
input = await tokenETH.balanceOf(sender)
await tokenETH.approve.sendTransaction(routerAddress, amountInMaximum, {from: sender})
paramsCall = [tokenOut, tokenIn, fee, sender, deadline, amountOut, input, sqrtPriceLimitX96]
amountCall = await router.exactOutputSingle.call(paramsCall, {from: sender})
amountInMaximum = amountCall.toString()
params = [tokenOut, tokenIn, fee, recipient, deadline, amountOut, amountInMaximum, sqrtPriceLimitX96]
//Swap WETH -> anonETH to make short interval price lower than spot price
await router.exactOutputSingle.sendTransaction(params, {from: sender})
let slot0ETH = await ETHPool.slot0()
let sqrtPriceX96Spot = slot0ETH.sqrtPriceX96
let getV3SqrtPriceShort = await oracle.getV3SqrtPriceAvg.call(pool, intervalShort, {from: sender})
let getV3SqrtPriceLong = await oracle.getV3SqrtPriceAvg.call(pool, intervalLong, {from: sender})
let getV3SqrtPrice = await oracle.getV3SqrtPriceSimpleShift.call(pool, intervalShort, intervalLong, {from: sender})
let shortGeqLong, spotGeqShort
const token0 = await ETHPool.token0()
if (token0 === weth9){
shortGeqLong = (getV3SqrtPriceShort <= getV3SqrtPriceLong)
spotGeqShort = (sqrtPriceX96Spot <= getV3SqrtPriceShort)
} else {
shortGeqLong = (getV3SqrtPriceShort >= getV3SqrtPriceLong)
spotGeqShort = (sqrtPriceX96Spot >= getV3SqrtPriceShort)
}
// Tests
shortGeqLong.should.be.equal(true)
spotGeqShort.should.be.equal(false)
getV3SqrtPrice.should.be.eq.BN(getV3SqrtPriceShort)
})
it('should give Long Interval Price when Highest', async () => {
await tokenETH.mint(sender, BigInt(1e22)) // 10,000 anonETH
let routerAddress = uniswap.SwapRouter
let router = await SwapRouter.at(routerAddress)
let pool = await shifterETH.tokenPool()
let ETHPool = await UniV3Pool.at(pool)
let cardinality = 10
await ETHPool.increaseObservationCardinalityNext.sendTransaction(cardinality, {from: sender})
let tokenIn, tokenOut, fee, recipient, deadline, amountOut, amountInMaximum, sqrtPriceLimitX96
tokenOut = tokenETH.address
tokenIn = weth9
fee = "3000"
recipient = sender
deadline = Math.round((Date.now() / 1000 + 300)).toString() // Deadline five minutes from 'now'
deadline += 3600 //add an hour
amountOut = BigInt(5e18).toString() //5 ETH
amountInMaximum = "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
sqrtPriceLimitX96 = "0x0"
input = await weth.balanceOf(sender)
await weth.approve.sendTransaction(routerAddress, amountInMaximum, {from: sender})
await tokenETH.approve(routerAddress, amountInMaximum)
let paramsCall = [tokenIn, tokenOut, fee, sender, deadline, amountOut, input, sqrtPriceLimitX96]
// Swap anonETH -> WETH at the beginning of the interval to make the long interval return the lowest price
let amountCall = await router.exactOutputSingle.call(paramsCall, {from: sender})
amountInMaximum = amountCall.toString()
let intervalShort = 180
let intervalLong = 1800
deadline += intervalLong*2
let params = [tokenIn, tokenOut, fee, recipient, deadline, amountOut, amountInMaximum, sqrtPriceLimitX96]
await router.exactOutputSingle.sendTransaction(params, {from: sender})
advanceTimeAndBlock(intervalLong - intervalShort)
// Swap tokenIn and tokenOut, WETH in anonETH out
input = await tokenETH.balanceOf(sender)
paramsCall = [tokenOut, tokenIn, fee, sender, deadline, amountOut, input, sqrtPriceLimitX96]
amountCall = await router.exactOutputSingle.call(paramsCall, {from: sender})
amountInMaximum = amountCall.toString()
params = [tokenOut, tokenIn, fee, recipient, deadline, amountOut, amountInMaximum, sqrtPriceLimitX96]
// Swap WETH -> anonETH at the start of intervalShort to increase short interval and spot price
await router.exactOutputSingle.sendTransaction(params, {from: sender})
advanceTimeAndBlock(intervalShort)
let slot0ETH = await ETHPool.slot0()
let sqrtPriceX96Spot = slot0ETH.sqrtPriceX96
let getV3SqrtPriceShort = await oracle.getV3SqrtPriceAvg.call(pool, intervalShort, {from: sender})
let getV3SqrtPriceLong = await oracle.getV3SqrtPriceAvg.call(pool, intervalLong, {from: sender})
let getV3SqrtPrice = await oracle.getV3SqrtPriceSimpleShift.call(pool, intervalShort, intervalLong, {from: sender})
let shortGeqLong, spotGeqLong
const token0 = await ETHPool.token0()
if (token0 === weth9){
shortGeqLong = (getV3SqrtPriceShort <= getV3SqrtPriceLong)
spotGeqLong = (sqrtPriceX96Spot <= getV3SqrtPriceLong)
} else {
shortGeqLong = (getV3SqrtPriceShort >= getV3SqrtPriceLong)
spotGeqLong = (sqrtPriceX96Spot >= getV3SqrtPriceLong)
}
// Tests
shortGeqLong.should.be.equal(false)
spotGeqLong.should.be.equal(false)
getV3SqrtPrice.should.be.eq.BN(getV3SqrtPriceLong)
})
})
describe('#getTokensForAmount', () => {
// getTokensForAmountCeiling should always be higher than getTokensForAmount for all assets
it('should be <= getTokensForAmountCeiling anonETH', async () => {
await tokenETH.mint(sender, BigInt(1e22)) // 10,000 anonETH
let xftPool = await shifterETH.tokenPool()
let intervalShort = 180
let intervalLong = 1800
let linkFeed = tokenStyle.anonETH.chainlink
let amount = BigInt(1e18) // 1 anonETH
let tokensForAmount = await oracle.getTokensForAmount(xftPool, intervalShort, intervalLong, linkFeed, amount, weth9)
let tokensForAmountCeiling = await oracle.getTokensForAmountCeiling(xftPool, intervalShort, intervalLong, linkFeed, amount, weth9)
let amountLeqCeiling = (BigInt(tokensForAmount) <= BigInt(tokensForAmountCeiling))
// Test
amountLeqCeiling.should.be.equal(true)
})
it('should be <= getTokensForAmountCeiling anonUSD', async () => {
await token.mint(sender, BigInt(1e22)) // 10,000 anonUSD
let xftPool = await shifter.tokenPool()
let intervalShort = 180
let intervalLong = 1800
let linkFeed = tokenStyle.anonUSD.chainlink
let amount = BigInt(1e18) // 1 anonUSD
let tokensForAmount = await oracle.getTokensForAmount(xftPool, intervalShort, intervalLong, linkFeed, amount, weth9)
let tokensForAmountCeiling = await oracle.getTokensForAmountCeiling(xftPool, intervalShort, intervalLong, linkFeed, amount, weth9)
let amountLeqCeiling = (BigInt(tokensForAmount) <= BigInt(tokensForAmountCeiling))
// Test
amountLeqCeiling.should.be.equal(true)
})
it('should be <= getTokensForAmountCeiling anonBTC', async () => {
await tokenBTC.mint(sender, BigInt(1e22)) // 10,000 anonBTC
let xftPool = await shifterBTC.tokenPool()
let intervalShort = 180
let intervalLong = 1800
let linkFeed = tokenStyle.anonBTC.chainlink
let amount = BigInt(1e18) // 1 anonBTC
let tokensForAmount = await oracle.getTokensForAmount(xftPool, intervalShort, intervalLong, linkFeed, amount, weth9)
let tokensForAmountCeiling = await oracle.getTokensForAmountCeiling(xftPool, intervalShort, intervalLong, linkFeed, amount, weth9)
let amountLeqCeiling = (BigInt(tokensForAmount) <= BigInt(tokensForAmountCeiling))
// Test
amountLeqCeiling.should.be.equal(true)
})
})
describe('#getTokensForAmountSimpleShift', () => {
// getTokensForAmountCeiling should always be higher than getTokensForAmount for all assets
it('should be <= getTokensForAmount anonETH', async () => {
await tokenETH.mint(sender, BigInt(1e22)) // 10,000 anonETH
let xftPool = await shifterETH.tokenPool()
let intervalShort = 180
let intervalLong = 1800
let linkFeed = tokenStyle.anonETH.chainlink
let amount = BigInt(1e18) // 1 anonETH
let tokensForAmount = await oracle.getTokensForAmount(xftPool, intervalShort, intervalLong, linkFeed, amount, weth9)
let tokensForAmountSimpleShift = await oracle.getTokensForAmountSimpleShift(xftPool, intervalShort, intervalLong, linkFeed, amount, weth9)
let amountLeq = (BigInt(tokensForAmount) >= BigInt(tokensForAmountSimpleShift))
// Test
amountLeq.should.be.equal(true)
})
it('should be <= getTokensForAmount anonUSD', async () => {
await token.mint(sender, BigInt(1e22)) // 10,000 anonUSD
let xftPool = await shifter.tokenPool()
let intervalShort = 180
let intervalLong = 1800
let linkFeed = tokenStyle.anonUSD.chainlink
let amount = BigInt(1e18) // 1 anonUSD
let tokensForAmount = await oracle.getTokensForAmount(xftPool, intervalShort, intervalLong, linkFeed, amount, weth9)
let tokensForAmountSimpleShift = await oracle.getTokensForAmountSimpleShift(xftPool, intervalShort, intervalLong, linkFeed, amount, weth9)
let amountLeq = (BigInt(tokensForAmount) >= BigInt(tokensForAmountSimpleShift))
// Test
amountLeq.should.be.equal(true)
})
it('should be <= getTokensForAmount anonBTC', async () => {
await tokenBTC.mint(sender, BigInt(1e22)) // 10,000 anonBTC
let xftPool = await shifterBTC.tokenPool()
let intervalShort = 180
let intervalLong = 1800
let linkFeed = tokenStyle.anonBTC.chainlink
let amount = BigInt(1e18) // 1 anonBTC
let tokensForAmount = await oracle.getTokensForAmount(xftPool, intervalShort, intervalLong, linkFeed, amount, weth9)
let tokensForAmountSimpleShift = await oracle.getTokensForAmountSimpleShift(xftPool, intervalShort, intervalLong, linkFeed, amount, weth9)
let amountLeq = (BigInt(tokensForAmount) >= BigInt(tokensForAmountSimpleShift))
// Test
amountLeq.should.be.equal(true)
})
})
describe('#getChainlinkPrice', () => {
it('should fetch ETH/USD', async () => {
let feed = tokenStyle.anonUSD.chainlink
let price = await oracle.chainlinkPrice(feed)
let priceNonZero = (BigInt(price) !== BigInt(0))
priceNonZero.should.be.equal(true)
})
it('should fetch BTC/ETH', async () => {
let feed = tokenStyle.anonBTC.chainlink
let price = await oracle.chainlinkPrice(feed)
let priceNonZero = (BigInt(price) !== BigInt(0))
priceNonZero.should.be.equal(true)
})
})
afterEach(async () => {
await revertSnapshot(snapshotId.result)
// eslint-disable-next-line require-atomic-updates
snapshotId = await takeSnapshot()
})
})
/* eslint-disable prettier/prettier */
/* global artifacts, web3, contract */
require('chai').use(require('bn-chai')(web3.utils.BN)).use(require('chai-as-promised')).should()
const fs = require('fs')
const { toBN } = require('web3-utils')
const Web3Utils = require('web3-utils')
const { takeSnapshot, revertSnapshot } = require('../scripts/ganacheHelper')
const Storage = artifacts.require('./Storage.sol')
const Shifter = artifacts.require('./XFTanon.sol')
const BadRecipient = artifacts.require('./BadRecipient.sol')
const XFT = artifacts.require('./XFTMock.sol')
const XFTOLDMock = artifacts.require('./XFTOLDMock.sol')
const Token = artifacts.require('anonUSD')
const SwapRouter = artifacts.require('./ISwapRouter.sol')
const UniV3Pool = artifacts.require('./IUniswapV3Pool.sol')
const ERC20 = artifacts.require('./IERC20.sol')
const TokenSwap = artifacts.require('./TokenSwap.sol')
const Oracle = artifacts.require('./Oracle.sol')
const { ETH_AMOUNT, MERKLE_TREE_HEIGHT, FEE_AMOUNT, tokenStyle, weth9, uniswap }
= require('../config.json')
const burnerRole = Web3Utils.keccak256("BURNER_ROLE")
const minterRole = Web3Utils.keccak256("MINTER_ROLE")
const websnarkUtils = require('websnark/src/utils')
const buildGroth16 = require('websnark/src/groth16')
const stringifyBigInts = require('websnark/tools/stringifybigint').stringifyBigInts
const snarkjs = require('snarkjs')
const bigInt = snarkjs.bigInt
const crypto = require('crypto')
const subtle = crypto.subtle
const circomlib = require('circomlib')
const MerkleTree = require('fixed-merkle-tree')
const { use } = require('chai')
const relayerConfig = require('../../relayer/config.json')
const nonEOAConfig = require ('../../noneoa-relayer/config.json')
require = require('esm')(module)
let allShifters = require('../shifters.ts').shifters
let storagePassword = web3.utils.keccak256('nowledgeIsPower');
let storageHash = web3.utils.keccak256(storagePassword);
let xcrypt;
const cardinalityLaunch = 10 //How many observations to save in a pool, at laumch
const rbigint = (nbytes) => snarkjs.bigInt.leBuff2int(crypto.randomBytes(nbytes))
const pedersenHash = (data) => circomlib.babyJub.unpackPoint(circomlib.pedersenHash.hash(data))[0]
const toFixedHex = (number, length = 32) =>
'0x' +
bigInt(number)
.toString(16)
.padStart(length * 2, '0')
const getRandomRecipient = () => rbigint(20)
const unlockAccount = async (address) => {
let provider = web3.currentProvider;
return new Promise((res, rej) => {
provider.send({
method: 'evm_addAccount',
params: [address, ""]
}, (d) => {
provider.send({
method: 'personal_unlockAccount',
params: [address, ""]
}, (d) => {
res(address);
});
});
});
}
let encryptNote;
function generateDeposit() {
let deposit = {
secret: rbigint(31),
nullifier: rbigint(31),
}
const preimage = Buffer.concat([deposit.nullifier.leInt2Buff(31), deposit.secret.leInt2Buff(31)])
deposit.commitment = pedersenHash(preimage)
return deposit
}
advanceTimeAndBlock = async (time) => {
await advanceTime(time);
await advanceBlock();
return Promise.resolve(web3.eth.getBlock('latest'));
}
advanceTime = (time) => {
return new Promise((resolve, reject) => {
web3.currentProvider.send({
jsonrpc: "2.0",
method: "evm_increaseTime",
params: [time],
id: new Date().getTime()
}, (err, result) => {
if (err) { return reject(err); }
return resolve(result);
});
});
}
advanceBlock = () => {
return new Promise((resolve, reject) => {
web3.currentProvider.send({
jsonrpc: "2.0",
method: "evm_mine",
id: new Date().getTime()
}, (err, result) => {
if (err) { return reject(err); }
const newBlockHash = web3.eth.getBlock('latest').hash;
return resolve(newBlockHash)
});
});
}
contract('XFTShifter', (accounts) => {
let shifter, shifterBTC, shifterETH
let xft
let xftOld
let token
let storage
let badRecipient
const sender = accounts[0]
const operator = accounts[0]
const levels = MERKLE_TREE_HEIGHT || 16
let tokenDenomination = tokenStyle.anonUSD.denoms[0] + "000000000000000000" || '1000000000000000000' // 1 ether
let uniFee = 3000
let snapshotId
let tree
const fee = FEE_AMOUNT || '1' /* in aUSD */
const refund = ETH_AMOUNT || '1000000000000000000' // 1 ether
let recipient = getRandomRecipient()
const relayer = accounts[1]
const relayerNonEOA = accounts[2]
let groth16
let circuit
let proving_key
before(async () => {
tree = new MerkleTree(levels)
USDtree = new MerkleTree(levels)
shifter = await Shifter.at(allShifters['anonUSD500'].shifter) // Use a specific shifter
shifterETH = await Shifter.at(allShifters['anonETH1'].shifter)
shifterBTC = await Shifter.at(allShifters['anonBTC1'].shifter)
storage = await Storage.deployed()
oracle = await Oracle.deployed()
xftOld = await XFTOLDMock.deployed()
xcrypt = async (data, password) => {
// IV is 0 for the test, but frontend uses the deposit index
const iv = (await subtle.digest('SHA-256', (password + (await storage.getDepositsLength(shifter.address, sender, storageHash))))).slice(0, 16);
const dataBuffer = new Uint8Array(Buffer.from(data.slice(2), 'hex'));
const keyBuffer = new Uint8Array(Buffer.from(password.slice(2), 'hex'));
const key = await subtle.importKey('raw', keyBuffer, { name: 'AES-GCM' }, false, ['encrypt']);
try {
let encrypted = await subtle.encrypt({ name: 'AES-GCM', iv }, key, dataBuffer);
return "0x" + Buffer.from(encrypted).toString('hex').slice(0, 124);
} catch (err) {
console.log(err)
throw new Error("The data provided couldn't be encrypted or decrypted, please check the inputs");
}
}
encryptNote = async (deposit, key) => await xcrypt(`0x${deposit.nullifier.toString(16).padEnd(62, '0')}${deposit.secret.toString(16).padEnd(62, '0')}`, key);
xft = await XFT.at(allShifters['XFT'].contract)
token = await Token.at(allShifters['anonUSD500'].contract)
tokenETH = await Token.at(allShifters['anonETH1'].contract)
tokenBTC = await Token.at(allShifters['anonBTC1'].contract)
weth = await ERC20.at(weth9)
let shifterPoolETH = await shifterETH.tokenPool()
let shifterPoolUSD = await shifter.tokenPool()
let shifterPoolBTC = await shifterBTC.tokenPool()
let poolETH = await UniV3Pool.at(shifterPoolETH)
let poolUSD = await UniV3Pool.at(shifterPoolUSD)
let poolBTC = await UniV3Pool.at(shifterPoolBTC)
await poolETH.increaseObservationCardinalityNext.sendTransaction(cardinalityLaunch, {from: sender})
await poolUSD.increaseObservationCardinalityNext.sendTransaction(cardinalityLaunch, {from: sender})
await poolBTC.increaseObservationCardinalityNext.sendTransaction(cardinalityLaunch, {from: sender})
badRecipient = await BadRecipient.new()
groth16 = await buildGroth16()
circuit = require('../build/circuits/withdraw.json')
proving_key = fs.readFileSync('build/circuits/withdraw_proving_key.bin').buffer
// Give the shifter roles
await xft.grantRole(burnerRole, shifter.address)
await xft.grantRole(burnerRole, shifterETH.address)
await token.grantRole(minterRole, shifter.address)
await tokenETH.grantRole(minterRole, shifterETH.address)
// Give sender roles
await xft.grantRole(minterRole, sender)
await token.grantRole(minterRole, sender)
await tokenETH.grantRole(minterRole, sender)
// Snapshot here
snapshotId = await takeSnapshot()
})
describe('#constructor', () => {
it('should initialize', async () => {
const tokenFromContract = await shifter.xft()
tokenFromContract.should.be.equal(xft.address)
})
})
describe('#grantRole Burner', () => {
it('should work', async () => {
// Checks if contract has role granted already and revokes role if true
let hasRole = await xft.hasRole(burnerRole, shifter.address)
if (hasRole) {
await xft.revokeRole(burnerRole, shifter.address)
}
let { logs } = await xft.grantRole(burnerRole, shifter.address)
logs[0].address.should.be.equal(xft.address)
logs[0].event.should.be.equal('RoleGranted')
logs[0].args.role.should.be.equal(Web3Utils.keccak256("BURNER_ROLE"))
logs[0].args.account.should.be.equal(shifter.address)
})
})
describe('#grantRole Minter', () => {
it('should work', async () => {
// Checks if contract has role granted already and revokes role if true
let hasRole = await token.hasRole(minterRole, shifter.address)
if (hasRole) {
await token.revokeRole(minterRole, shifter.address)
}
let { logs } = await token.grantRole(minterRole, shifter.address)
logs[0].address.should.be.equal(token.address)
logs[0].event.should.be.equal('RoleGranted')
logs[0].args.role.should.be.equal(Web3Utils.keccak256("MINTER_ROLE"))
logs[0].args.account.should.be.equal(shifter.address)
})
})
describe('#TokenSwap', () => {
it('should reject if not activated', async () => {
const holder = accounts[0]
// Checks if contract has role granted already and grants role if false
let hasRole = await xft.hasRole(minterRole, tokenSwap.address)
if (!hasRole) {
await xft.grantRole(minterRole, tokenSwap.address)
}
let balanceBefore = await xftOld.balanceOf(holder)
await xftOld.approve.sendTransaction(tokenSwap.address, balanceBefore, {from: holder})
let { reason } = await tokenSwap.upgrade.sendTransaction({from: holder}).should.be.rejected
reason.should.be.equal("You can't upgrade yet");
})
it('should work after activation', async () => {
const holder = accounts[0]
// Checks if contract has role granted already and grants role if false
let hasRole = await xft.hasRole(minterRole, tokenSwap.address)
if (!hasRole) {
await xft.grantRole(minterRole, tokenSwap.address)
}
await tokenSwap.setActive.sendTransaction({from: holder});
let balanceBefore = await xftOld.balanceOf(holder)
let balanceNewBefore = await xft.balanceOf(holder)
await xftOld.approve.sendTransaction(tokenSwap.address, balanceBefore, {from: holder})
let { logs } = await tokenSwap.upgrade.sendTransaction({from: holder})
let balanceOld = await xftOld.balanceOf(holder)
let balanceNew = await xft.balanceOf(holder)
let balanceContract = await xft.balanceOf(tokenSwap.address)
// Tests
logs[0].event.should.be.equal('XFTUpgraded')
logs[0].args.user.should.be.equal(holder)
logs[0].args.amount.should.be.eq.BN(balanceBefore)
balanceNew.should.be.eq.BN(balanceBefore.add(balanceNewBefore))
balanceOld.should.be.eq.BN(0)
balanceContract.should.be.eq.BN(0)
})
it('should reject if contract does not have minterRole', async () => {
const holder = accounts[0]
await tokenSwap.setActive.sendTransaction({from: holder});
let hasRole = await xft.hasRole(minterRole, tokenSwap.address)
if (hasRole) {
await xft.revokeRole(minterRole, tokenSwap.address)
}
let balanceBefore = await xftOld.balanceOf(holder)
await xftOld.approve.sendTransaction(tokenSwap.address, balanceBefore, {from: holder})
let error = await tokenSwap.upgrade.sendTransaction({ from: holder }).should.be.rejected
error.reason.should.be.equal('Caller is not a minter')
})
})
describe('#deposit', () => {
it('should work', async () => {
const commitment = toFixedHex(43)
let role0 = await xft.hasRole(burnerRole, shifter.address)
role0.should.be.equal(true)
await xft.approve(shifter.address, tokenDenomination)
hexNote = await encryptNote(generateDeposit(), storagePassword);
let { logs } = await shifter.deposit(commitment, hexNote, storageHash, { from: sender, value: "50000000000000000" })
logs[0].event.should.be.equal('Deposit')
logs[0].args.commitment.should.be.equal(commitment)
logs[0].args.leafIndex.should.be.eq.BN(0)
})
it('should reject insufficient balance', async () => {
const commitment = toFixedHex(43)
await xft.approve(shifter.address, tokenDenomination)
let user = accounts[5]
hexNote = await encryptNote(generateDeposit(), storagePassword);
let error = await shifter.deposit(commitment, hexNote, storageHash, { from: user, value: "50000000000000000" }).should.be.rejected
error.reason.should.be.equal('Insufficient Balance')
})
it('should revert if contract is not assigned burnerRole', async () => {
const commitment = toFixedHex(43)
// Checks if contract has role granted already and revokes role if true
let hasRole = await xft.hasRole(burnerRole, shifter.address)
if (hasRole) {
await xft.revokeRole(burnerRole, shifter.address)
}
await xft.approve(shifter.address, tokenDenomination)
hexNote = await encryptNote(generateDeposit(), storagePassword);
let error = await shifter.deposit(commitment, hexNote, storageHash, { from: sender, value: "50000000000000000" }).should.be.rejected
error.reason.should.be.equal('Caller is not a burner')
})
})
describe('#withdraw', () => {
it('should work (and fetch the note onchain)', async () => {
const deposit = generateDeposit()
const user = sender;
const cost = await shifter.getCost(tokenDenomination);
await xft.mint(user, cost + "0") // Give it 10x the cost
tree.insert(deposit.commitment)
const balanceUserBefore = await xft.balanceOf(user)
await xft.approve(shifter.address, tokenDenomination, { from: user })
hexNote = await encryptNote(deposit, storagePassword);
await shifter.deposit(toFixedHex(deposit.commitment), hexNote, storageHash, { from: sender, gasPrice: '0', value: "50000000000000000" })
const latestDeposit = await storage.getLatestDeposit(shifter.address, user, storageHash)
hexNote = await xcrypt(latestDeposit, storagePassword);
// Chainlink Feed only has 8 decimals of precision.
// Price changes on the fly, can never have accurate price due to atomicity of txs unless no oracles change in between txs
const { pathElements, pathIndices } = tree.path(0)
// Circuit input
const input = stringifyBigInts({
// public
root: tree.root(),
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
relayer,
recipient,
fee,
refund,
// private
nullifier: deposit.nullifier,
secret: deposit.secret,
pathElements: pathElements,
pathIndices: pathIndices,
})
const proofData = await websnarkUtils.genWitnessAndProve(groth16, input, circuit, proving_key)
const { proof } = websnarkUtils.toSolidityInput(proofData)
const balanceShifterBefore = await xft.balanceOf(shifter.address)
const balanceRelayerBefore = await token.balanceOf(relayer)
const balanceReceiverBefore = await token.balanceOf(toFixedHex(recipient, 20))
const ethBalanceOperatorBefore = await web3.eth.getBalance(operator)
const ethBalanceReceiverBefore = await web3.eth.getBalance(toFixedHex(recipient, 20))
const ethBalanceRelayerBefore = await web3.eth.getBalance(relayer)
let isSpent = await shifter.isSpent(toFixedHex(input.nullifierHash))
isSpent.should.be.equal(false)
const args = [
toFixedHex(input.root),
toFixedHex(input.nullifierHash),
toFixedHex(input.recipient, 20),
toFixedHex(input.relayer, 20),
toFixedHex(input.fee),
toFixedHex(input.refund),
]
const { logs } = await shifter.withdraw(proof, ...args, { value: refund, from: relayer, gasPrice: '0' })
const balanceRelayerAfter = await token.balanceOf(relayer)
const ethBalanceOperatorAfter = await web3.eth.getBalance(operator)
const balanceReceiverAfter = await token.balanceOf(toFixedHex(recipient, 20))
const ethBalanceReceiverAfter = await web3.eth.getBalance(toFixedHex(recipient, 20))
const ethBalanceRelayerAfter = await web3.eth.getBalance(relayer)
const feeBN = toBN(fee.toString())
balanceRelayerAfter.should.be.eq.BN(toBN(balanceRelayerBefore).add(feeBN))
balanceReceiverAfter.should.be.eq.BN(
toBN(balanceReceiverBefore).add(toBN(tokenDenomination).sub(feeBN)),
)
ethBalanceOperatorAfter.should.be.eq.BN(toBN(ethBalanceOperatorBefore))
ethBalanceReceiverAfter.should.be.eq.BN(toBN(ethBalanceReceiverBefore).add(toBN(refund)).add(toBN("50000000000000000"))) //Adds the deposited ether ToDo: Fix hardcoded value
ethBalanceRelayerAfter.should.be.eq.BN(toBN(ethBalanceRelayerBefore).sub(toBN(refund)))
logs[0].args.nullifierHash.should.be.equal(toFixedHex(input.nullifierHash))
logs[0].args.relayer.should.be.eq.BN(relayer)
logs[0].args.fee.should.be.eq.BN(feeBN)
isSpent = await shifter.isSpent(toFixedHex(input.nullifierHash))
isSpent.should.be.equal(true)
})
it('should reject with wrong refund value', async () => {
const deposit = generateDeposit()
const user = accounts[4]
const cost = await shifter.getCost(tokenDenomination);
await xft.mint(user, cost + "0") // Give it 10x the cost
tree.insert(deposit.commitment)
await xft.approve(shifter.address, tokenDenomination, { from: user })
await shifter.deposit(toFixedHex(deposit.commitment), toFixedHex(rbigint(62)), storageHash, { from: user, gasPrice: '0', value: "50000000000000000" })
const { pathElements, pathIndices } = tree.path(0)
// Circuit input
const input = stringifyBigInts({
// public
root: tree.root(),
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
relayer,
recipient,
fee,
refund,
// private
nullifier: deposit.nullifier,
secret: deposit.secret,
pathElements: pathElements,
pathIndices: pathIndices,
})
const proofData = await websnarkUtils.genWitnessAndProve(groth16, input, circuit, proving_key)
const { proof } = websnarkUtils.toSolidityInput(proofData)
const args = [
toFixedHex(input.root),
toFixedHex(input.nullifierHash),
toFixedHex(input.recipient, 20),
toFixedHex(input.relayer, 20),
toFixedHex(input.fee),
toFixedHex(input.refund),
]
let { reason } = await shifter.withdraw(proof, ...args, { value: 1, from: relayer, gasPrice: '0' })
.should.be.rejected
reason.should.be.equal('Incorrect refund amount received by the contract')
; ({ reason } = await shifter.withdraw(proof, ...args, {
value: toBN(refund).mul(toBN(2)),
from: relayer,
gasPrice: '0',
}).should.be.rejected)
reason.should.be.equal('Incorrect refund amount received by the contract')
})
it('should revert if contract not assigned minterRole', async () => {
const deposit = generateDeposit()
hexNote = await encryptNote(generateDeposit(), storagePassword);
const user = accounts[4]
const cost = await shifter.getCost(tokenDenomination);
await xft.mint(user, cost + "0") // Give it 10x the cost
// Checks if contract has role granted already and revokes role if true
let hasRole = await token.hasRole(minterRole, shifter.address)
if (hasRole) {
await token.revokeRole(minterRole, shifter.address)
}
tree.insert(deposit.commitment)
const balanceUserBefore = await xft.balanceOf(user)
await xft.approve(shifter.address, tokenDenomination, { from: user })
await shifter.deposit(toFixedHex(deposit.commitment), hexNote, storageHash, { from: user, gasPrice: '0', value: "50000000000000000" })
const balanceUserAfter = await xft.balanceOf(user)
const { pathElements, pathIndices } = tree.path(0)
// Circuit input
const input = stringifyBigInts({
// public
root: tree.root(),
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
relayer,
recipient,
fee,
refund,
// private
nullifier: deposit.nullifier,
secret: deposit.secret,
pathElements: pathElements,
pathIndices: pathIndices,
})
const proofData = await websnarkUtils.genWitnessAndProve(groth16, input, circuit, proving_key)
const { proof } = websnarkUtils.toSolidityInput(proofData)
const balanceShifterBefore = await xft.balanceOf(shifter.address)
const balanceRelayerBefore = await token.balanceOf(relayer)
const balanceReceiverBefore = await token.balanceOf(toFixedHex(recipient, 20))
const ethBalanceOperatorBefore = await web3.eth.getBalance(operator)
const ethBalanceReceiverBefore = await web3.eth.getBalance(toFixedHex(recipient, 20))
const ethBalanceRelayerBefore = await web3.eth.getBalance(relayer)
let isSpent = await shifter.isSpent(toFixedHex(input.nullifierHash))
isSpent.should.be.equal(false)
const args = [
toFixedHex(input.root),
toFixedHex(input.nullifierHash),
toFixedHex(input.recipient, 20),
toFixedHex(input.relayer, 20),
toFixedHex(input.fee),
toFixedHex(input.refund),
]
let { reason } = await shifter.withdraw(proof, ...args, { value: refund, from: relayer, gasPrice: '0' }).should.be.rejected
reason.should.be.equal('Caller is not a minter')
})
})
describe('#simpleShift', () => {
it('should work', async () => {
const user = accounts[4]
const amount = "10000";
const chainlinkFeed = await shifter.chainlinkFeed();
const xftPool = await shifter.xftPool();
const tokenPool = await shifter.tokenPool();
await token.mint(user, amount)
const cost = await oracle.getCostSimpleShift(amount, chainlinkFeed, xftPool, tokenPool, {from: user})
await shifter.simpleShift(amount, user, { from: user })
senderTokenBalance = await xft.balanceOf(user)
senderUSDBalance = await token.balanceOf(user)
// Find a way to atomically check balances
senderTokenBalance.should.be.eq.BN(cost)
senderUSDBalance.should.be.eq.BN(toBN(0))
})
it('should revert if sender has insufficient balance', async () => {
let { reason } = await shifter.simpleShift(tokenDenomination, sender, { from: sender }).should.be.rejected
reason.should.be.equal('Insufficient balance')
})
})
describe('#Paused', () => {
it('should work', async () => {
await shifter.pause({ from: sender })
let isPaused = await shifter.paused()
isPaused.should.be.equal(true)
let { reason } = await shifter.simpleShift(tokenDenomination, sender, { from: sender }).should.be.rejected
reason.should.be.equal('Pausable: paused')
})
})
afterEach(async () => {
await revertSnapshot(snapshotId.result)
// eslint-disable-next-line require-atomic-updates
snapshotId = await takeSnapshot()
tree = new MerkleTree(levels)
})
})
require('dotenv').config()
const HDWalletProvider = require('@truffle/hdwallet-provider')
const utils = require('web3-utils')
module.exports = {
/**
* Networks define how you connect to your ethereum client and let you set the
* defaults web3 uses to send transactions. If you don't specify one truffle
* will spin up a development blockchain for you on port 9545 when you
* run `develop` or `test`. You can ask a truffle command to use a specific
* network from the command line, e.g
*
* $ truffle test --network <network-name>
*/
networks: {
development: {
host: '127.0.0.1', // Localhost (default: none)
port: 8545, // Standard Ethereum port (default: none)
gas: 30000000,
gasPrice: utils.toWei('1', 'gwei'),
network_id: '*', // Any network (default: none)
skipDryRun: true,
}
},
mocha: {
timeout: 3000000
},
// Configure your compilers
compilers: {
solc: {
version: '0.7.6',
settings: {
optimizer: {
enabled: true,
runs: 200,
},
},
},
external: {
command: 'node ./scripts/compileHasher.js',
targets: [
{
path: './build/Hasher.json',
},
],
},
},
plugins: ['solidity-coverage'],
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment