Synthetixio / synthetix

Synthetix Solidity smart contracts
https://synthetix.io/
MIT License
1.2k stars 594 forks source link

Need web3 query to get max_eth_to_convert, tokens_bought and reward_tokens #295

Closed BlinkyStitt closed 4 years ago

BlinkyStitt commented 4 years ago

I'm looking into building some automation that calls arbSynthRate on the ArbRewarder contract. The first thing I'm wanting to build is a dashboard that shows the maximum input ether along with the tokens + reward. Maybe I'm missing it, but I'm not seeing a way to get this information out of the contract since most of the methods are marked private. For now, I'm essentially re-implementing most of the calls in my own code.

I think it would be very helpful if there was a function that had a signature something like this:

function arbitrageAvailable() public view returns (uint max_input_ether, uint synth_output, uint synthetix_reward);

Then, every time a new block arrives, I can use web3 to query arbitrageAvailable.

Am I missing a better way to do this?

I also want to say that this contract is my favorite example of permissionless defi composability yet. Awesome work.

jjgonecrypto commented 4 years ago

Hey @WyseNynja, typically we want to keep contract sizes down and instead refer developers to querying the contract for these kinds of things, calculating values off-chain using on-chain information as reference.

Right now synthetix-js is the best place to find JavaScript-based functionality to query the Synthetix contracts without having to load the ABIs yourself.

For example, to figure out how much SNX is left in the ArbRewarder:

const { SynthetixJs } = require('synthetix-js'); // assumes node.js - if using browser then SynthetixJs is attached to `window`
const snxjs = new SynthetixJs();
const arbRewarder = '0xA6B5E74466eDc95D0b6e65c5CBFcA0a676d893a4';

snxjs.Synthetix.balanceOf(arbRewarder).then(value => {
  const snxRemaining = snxjs.utils.formatEther(value);
  console.log('The ArbRewarder has', snxRemaining, 'SNX remaining');

  snxjs.ExchangeRates.ratesForCurrencies(['SNX','ETH'].map(snxjs.utils.toUtf8Bytes32).then(([snx, eth]) => {
     const snxRate = snxjs.utils.formatEther(snx);
     const ethRate = snxjs.utils.formatEther(eth);

      // now you have the SNXUSD and ETHUSD rates, figure out how much ETH you can send
     const maxInputEther = snxRemaining * snxRate / ethRate;

      // huge success
  });
});
BlinkyStitt commented 4 years ago

Thank you. This is helpful.

However, I don't have a javascript environment setup. Can you please tell me what ['SNX','ETH'].map(snxjs.utils.toUtf8Bytes32) returns?

BlinkyStitt commented 4 years ago

Nevermind. I figured it out:

assert_eq!(snx_bytes32, [83, 78, 88, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);

assert_eq!(eth_bytes32, [69, 84, 72, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
BlinkyStitt commented 4 years ago

Are you sure const maxInputEther = snxRemaining * snxRate / ethRate is correct?

The arbSynthRate function does a lot more things and it includes the uniswap sETH exchange balance:

    /**
     * Here the caller gives us some ETH. We convert the ETH->sETH  and reward the caller with SNX worth
     * the value of the sETH received from the earlier swap.
     */
    function arbSynthRate() public payable
        rateNotStale("ETH")
        rateNotStale("SNX")
        notPaused
        returns (uint reward_tokens)
    {
        /* Ensure there is enough more sETH than ETH in the Uniswap pool */
        uint seth_in_uniswap = synth.balanceOf(uniswapAddress);
        uint eth_in_uniswap = uniswapAddress.balance;
        require(eth_in_uniswap.divideDecimal(seth_in_uniswap) < uint(divisor-off_peg_min).divideDecimal(divisor), "sETH/ETH ratio is too high");

        /* Get maximum ETH we'll convert for caller */
        uint max_eth_to_convert = maxConvert(eth_in_uniswap, seth_in_uniswap, divisor, divisor-off_peg_min);
        uint eth_to_convert = min(msg.value, max_eth_to_convert);
        uint unspent_input = msg.value - eth_to_convert;

        /* Actually swap ETH for sETH */
        uint min_seth_bought = expectedOutput(uniswapExchange, eth_to_convert);
        uint tokens_bought = uniswapExchange.ethToTokenSwapInput.value(eth_to_convert)(min_seth_bought, now + max_delay);

        /* Reward caller */
        reward_tokens = rewardCaller(tokens_bought, unspent_input);
    }

    function maxConvert(uint a, uint b, uint n, uint d) private pure returns (uint result) {
        result = (sqrt((a * (9*a*n + 3988000*b*d)) / n) - 1997*a) / 1994;
    }

    function rewardCaller(uint bought, uint unspent_input)
        private
        returns
        (uint reward_tokens)
    {
        uint snx_rate = exchangeRates.rateForCurrency("SNX");
        uint eth_rate = exchangeRates.rateForCurrency("ETH");

        reward_tokens = eth_rate.multiplyDecimal(bought).divideDecimal(snx_rate);
        synthetix.transfer(msg.sender, reward_tokens);

        if(unspent_input > 0) {
            msg.sender.transfer(unspent_input);
        }
    }
jjgonecrypto commented 4 years ago

No, I just threw something together to get you started.

To get the uniswap ETH balance, you'll need to query the Uniswap contract (via it's address and ABI), for balance - or you can use the Uniswap Graph's endpont.

If you want more help, please jump into the #dev-portal channel in our Discord - there are more people in there who can help.