Skip to content

Instantly share code, notes, and snippets.

@daragao
Last active August 1, 2021 15:41
Show Gist options
  • Save daragao/f36d66cc996cfb82bf599a0d8597ed44 to your computer and use it in GitHub Desktop.
Save daragao/f36d66cc996cfb82bf599a0d8597ed44 to your computer and use it in GitHub Desktop.
Study about Chainlink in and outflows

How do Chainlink price feeds get paid?

Chainlink provides three distinct services:

We will only focus on the price feed service. The price feed service relies on off chain services, and a set of smart contracts. The way the smart contracts function is what will gives us most clues to understand who gets paid, and who pays.

There are multiple price feeds which are listed in the Chainlink Market. An example of a specific price feed would be the ETH/USD. In the price feed page we are even able to get details about the nodes that submit values to the price feed.

For the ETH/USD price feed there are two relevant smart contracts (listed in the page), and a token:

  • Flux Aggregator
    • Where the functionality lives
  • Flux Aggregator Proxy
    • More a router than a proxy. A router is a contract that has no functionality, but only does safety checks for protection of the client.
  • Link Token
    • The token used to pay the data providers

How does it all work in a high level?

How is the price value updated?

In the example of ETH/USD.

  1. Multiple nodes send a value to the Flux Aggregator (or Flux Aggregator Proxy). This is what is called a round.
  2. Once enough values have been received by the Flux Aggregator. The average is calculated, and the round is closed.
  3. After the round is closed the withdrawal amount available is updated. After this the nodes can withdraw the corresponding amount of Link Token.

Where are the funds to pay the nodes?

The funds paid to the nodes are from the Link Token, and are escrowed in the Flux Aggregator address.

The assumption is that there are sponsors that buy funds into the Flux Aggregator address, in order to keep nodes motivated.

What do the smart contracts actually do?

How do smart contracts access the price feed?

It is free for a smart contract to access the data in the contract.

This can be checked by looking into the function getAnswer() of the Flux Aggregator Proxy and Flux Aggregator.

FluxAggregator.sol is the implementation of the Flux Aggregator.

  function getAnswer(uint256 _roundId)
    public
    view
    virtual
    override
    returns (int256)
  {
    if (validRoundId(_roundId)) {
      return rounds[uint32(_roundId)].answer;
    }
    return 0;
  }

AccessControledAggregator.sol is the implementation of the Flux Aggregator Proxy.

  function getAnswer(uint256 _roundId)
    public
    view
    override
    checkAccess()
    returns (int256)
  {
    return super.getAnswer(_roundId);
  }

How is the price feed updated?

(We will ignore the Flux Aggregator Proxy contract, since it only does checks and does not alter the state)

The nodes (referred by oracles sometimes in the code) submit one value per round. To submit this value they send a transaction to call the submit() function in the Flux Aggregator.

We can find the submit implementation in the FluxAggregator.sol.

  function submit(uint256 _roundId, int256 _submission)
    external
  {
    bytes memory error = validateOracleRound(msg.sender, uint32(_roundId));
    require(_submission >= minSubmissionValue, "value below minSubmissionValue");
    require(_submission <= maxSubmissionValue, "value above maxSubmissionValue");
    require(error.length == 0, string(error));

    oracleInitializeNewRound(uint32(_roundId));
    recordSubmission(_submission, uint32(_roundId));
    (bool updated, int256 newAnswer) = updateRoundAnswer(uint32(_roundId));
    payOracle(uint32(_roundId));
    deleteRoundDetails(uint32(_roundId));
    if (updated) {
      validateAnswer(uint32(_roundId), newAnswer);
    }
  }

The function recordSubmission() and payOracle() can be found in the FluxAggregator.sol, and are called by the submit() function in order to update the current answer (price value) and mark the withdrawable amount by the nodes (oracles).

  function payOracle(uint32 _roundId)
    private
  {
    uint128 payment = details[_roundId].paymentAmount;
    Funds memory funds = recordedFunds;
    funds.available = funds.available.sub(payment);
    funds.allocated = funds.allocated.add(payment);
    recordedFunds = funds;
    oracles[msg.sender].withdrawable = oracles[msg.sender].withdrawable.add(payment);

    emit AvailableFundsUpdated(funds.available);
  }

  function recordSubmission(int256 _submission, uint32 _roundId)
    private
  {
    require(acceptingSubmissions(_roundId), "round not accepting submissions");

    details[_roundId].submissions.push(_submission);
    oracles[msg.sender].lastReportedRound = _roundId;
    oracles[msg.sender].latestSubmission = _submission;

    emit SubmissionReceived(_submission, _roundId, msg.sender);
  }

To make clear. The answer is only updated once a certain number of submissions have been made on the round as can be seen on FluxAggregator.sol.

  function updateRoundAnswer(uint32 _roundId)
    internal
    returns (bool, int256)
  {
    if (details[_roundId].submissions.length < details[_roundId].minSubmissions) {
      return (false, 0);
    }

    int256 newAnswer = Median.calculateInplace(details[_roundId].submissions);
    rounds[_roundId].answer = newAnswer;
    rounds[_roundId].updatedAt = uint64(block.timestamp);
    rounds[_roundId].answeredInRound = _roundId;
    latestRoundId = _roundId;

    emit AnswerUpdated(newAnswer, _roundId, now);

    return (true, newAnswer);
  }

How can a node (oracle) withdraw their funds?

After making a submission and having funds allocated to the nodes, the nodes can withdraw the funds.

In the FluxAggregator.sol there are two functions that allow us to make transfers of funds escrowed in the aggregator to other accounts. The withdrawPayment() function allows nodes to transfer funds to any account. The withdrawFunds() function allows the Flux Aggregator owner to transfer funds to any account.

  function withdrawPayment(address _oracle, address _recipient, uint256 _amount)
    external
  {
    require(oracles[_oracle].admin == msg.sender, "only callable by admin");

    // Safe to downcast _amount because the total amount of LINK is less than 2^128.
    uint128 amount = uint128(_amount);
    uint128 available = oracles[_oracle].withdrawable;
    require(available >= amount, "insufficient withdrawable funds");

    oracles[_oracle].withdrawable = available.sub(amount);
    recordedFunds.allocated = recordedFunds.allocated.sub(amount);

    assert(linkToken.transfer(_recipient, uint256(amount)));
  }

  function withdrawFunds(address _recipient, uint256 _amount)
    external
    onlyOwner()
  {
    uint256 available = uint256(recordedFunds.available);
    require(available.sub(requiredReserve(paymentAmount)) >= _amount, "insufficient reserve funds");
    require(linkToken.transfer(_recipient, _amount), "token transfer failed");
    updateAvailableFunds();
  }

How much is actually flowing?

To actually understand how all is working, a Jupyter Notebook was made to check the flow of LINK token and the amount of submissions made (this notebook can be extended and improved, but for now it is enough to prove our assumptions of the flow).

The notebook can be found available on a public gist and it relies on the ETH/USD price feed (all the other price feeds use the same contract but deployed to other addresses).

How much is escrowed?

On 07/03/2021 the Flux Aggregator contract had 66,963.67678707543 LINK tokens escrowed. Out of that 47289.89 LINK tokens are available to be used for future payments, and 19673.78678707542 LINK tokens are already allocated to nodes to be withdrawn.

How many submissions per round?

Most rounds have 20 submissions, although there have been rounds with less submissions. Although rounds with less submissions I don't think are closed (I would need to check that for sure in the code).

Submissions per round

How many submissions per node?

The maximum amount of submissions a single node has made is 23187 the minimum has been 1.

Total submissions per Oracle

Oracle Address Total Submission Round First Block Number First Timestamp Last Block Number Last Timestamp
0xaaeed1e72a10214c705467316779272e4ba78433 1 10677688 2020-08-17 12:55:25 10677688 2020-08-17 12:55:25
0xff2b320cf904d78eae7298ed10ec143c5e7e2314 21508 10702986 2020-08-21 10:18:09 11987359 2021-03-06 21:39:05
0x87bf62ec62f872a4c74b1a39bb143702c5364785 21622 10703605 2020-08-21 12:37:06 11987357 2021-03-06 21:38:10
0xabbabb17965310949842323f06eede92fd787d0d 22094 10687730 2020-08-19 02:04:36 11987358 2021-03-06 21:38:50
0x1edb9539d67b0ceb929ae1f334a6c24499ae9cb9 22437 10689906 2020-08-19 10:02:39 11987358 2021-03-06 21:38:50
0xcf2d187d3833dd9063b019d0c39e4566576c3c56 22580 10683198 2020-08-18 09:37:28 11987358 2021-03-06 21:38:50
0xa14ebb5909fba95a9305cbcaa6ea069733fcd443 22695 10685189 2020-08-18 16:52:03 11987358 2021-03-06 21:38:50
0x2183464128d6f4020cb4c1c908bae2e87f83214a 22743 10682213 2020-08-18 05:57:44 11987358 2021-03-06 21:38:50
0x6a9dec02a37f08a7420186c8d273221fd462f7e8 22759 10679200 2020-08-17 18:39:13 11987358 2021-03-06 21:38:50
0x1d0f12e3546e4734d7521a46ed02ef2c2df51e59 22774 10677391 2020-08-17 11:47:01 11987358 2021-03-06 21:38:50
0xac94a69b0996c8edcf19cb7548a644836cb77509 22776 10996841 2020-10-05 16:36:40 11987358 2021-03-06 21:38:50
0x6d1b8ee3d1b5834dde3ae8ba6d7a587ae6d14dc3 22808 10679636 2020-08-17 20:13:48 11987358 2021-03-06 21:38:50
0x98db8c499016aa9d860fcd1a9b75a1d7ef3fc3b0 22821 10677318 2020-08-17 11:32:10 11987358 2021-03-06 21:38:50
0xc8f53fae35468100df31901e939d8ff80b667166 22851 10996841 2020-10-05 16:36:40 11987358 2021-03-06 21:38:50
0xd45727e3d7405c6ab3b2b3a57474012e1f998483 22883 10609170 2020-08-06 23:16:56 11987358 2021-03-06 21:38:50
0x9ab2a7af33d50738caf23e9283e584d954dfb287 22886 10677281 2020-08-17 11:24:58 11987358 2021-03-06 21:38:50
0xc7310123914f624da9c376f8ec590055e62733c1 22986 10610214 2020-08-07 03:14:06 11987358 2021-03-06 21:38:50
0x2ad9b7b9386c2f45223ddfc4a4d81c2957bae19a 23037 10639355 2020-08-11 15:17:08 11987358 2021-03-06 21:38:50
0xc4a92358757ef8d22580c5efed30d5241ac725ae 23108 10612042 2020-08-07 10:05:03 11987358 2021-03-06 21:38:50
0xf687bfa503376e088c6032cd277eebdf19af4c7d 23132 10613041 2020-08-07 13:49:03 11987358 2021-03-06 21:38:50
0x501698a6f6f762c79e4d28e3815c135e3f9af996 23157 10609429 2020-08-07 00:18:37 11987358 2021-03-06 21:38:50
0x0699a397c3cf614c9a7db23a4be28fc4c8f3a755 23187 10609170 2020-08-06 23:16:56 11987358 2021-03-06 21:38:50

How much LINK Token was escrowed into the contract?

Initially the ETH/USD Flux Aggregator contract was funded by Chainlink. Later it became a sponsored price feed.

In the Chainlink feeds page a list of sponsors can be seen.

Sponsor
Synthetix
Loopring
OpenLaw
1inch
ParaSwap
MCDEX
Futureswap
DMM
Aave
The Force Protocol
DigiTix
Consensus Cell
ENS
Sandbank
Plasm Network
Swipe
HXRO
DODO
Bitrue
Lien
StrongBlock
Auctus
Finnexus
DeFiDollar
Celsius
Synlev
Perpetual Protocol
Bifrost BiFi
Hakka Finance
Jarvis Network

Although the list of sponsors is long. The addresses that actually deposited funds are not that many (there might be many reasons for this, that haven't been investigated).

Depositor Address Total Token Amount
0x1b4c87add7a98d28910a0e1ab616d71aa4b4ce8b 51000
0x27158157136384c713bc09a0a7ae81c8391d7f11 245000
0x2f0acb9c5dd2a3511bc1d9d67258e5c9434ba569 27500
0x6781a24ccc819941b7a4c9cc7b0dc32666e587e4 108500
0x71c05a4ea5e9d5b1ac87bf962a043f5265d4bdc8 132.3

Total inflows

The amount sponsors contributed has varied a bit through time.

Inflows vs Time

How much has been paid to each node?

Work has not been done to actually know how much each node has gotten. For that we would need to run every transaction that calls the withdraw function, and check the msg.sender address (some could be internal transactions, so we would need to check every call instruction done for every transaction).

Although to seen how much LINK token has been withdrawn from the escrow it is very straight forward.

Address Receiving Funds Total withdrawn
0x039fdfdb14911608a34ebcda6009a80ef5d16e50 17631.3
0x0808d4689b347d499a96f139a5fc5b5101258406 2265.23
0x0a88fd0a0e8d99d70e051495d60b4eec3125175e 207.03
0x0e5a997ba3b0e5a69e24468d30439911fe353961 1543.56
0x111f1b41f702c20707686769a4b7f25c56c533b2 15920.6
0x15918ff7f6c44592c81d999b442956b07d26cc44 18401.6
0x182d4990bb0ff595b308b3efcb93313abad575e7 15685.7
0x183a96629ff566e7aa8afa38980cd037eb40a59a 17527.3
0x18bb60aee18a2ca3b082549fe65ec6f92598a6d2 121.55
0x210b4b57e4f1aa68a7e76e91d307fd4f88a7ab2d 16604
0x3515d8cd82c021819f5d28cdec1a7ff702080676 1475.3
0x3651c260ee7100cbad162ca3bdb65da0d65cf0b7 201.6
0x3fb4600736d306ee2a89edf0356d4272fb095768 17386.9
0x4564a9c6061f6f1f2eadb954b1b3c241d2dc984e 8406.67
0x4a3df8cae46765d33c2551ff5438a5c5fc44347c 1075.73
0x4e64d4f804e66ed364d7456e0b49207fae94f9ac 1390.88
0x4fbefaf1bff0130945c61603b97d38dd6e21f5cf 18277.8
0x560e8bd3cb91cb00ab37c12e2280763483359381 378
0x61c808d82a3ac53231750dadc13c777b59310bd9 15182.7
0x7ac1ad6e4ba95783197038210a512ad697418c71 17923.6
0x8d689476eb446a1fb0065bffac32398ed7f89165 18241.3
0x95c98112bd9635a3159518401ae227d5a296e994 17205.7
0x9b18ed2d951151aaf71949885292f6f869785c7c 4900.93
0x9ccbfd17fa284f36c2ff503546160b256d1cd3d1 17849.6
0x9d219125a0ce10241b4ec1280c2f880475f172f1 16066
0x9efa0a617c0552f1558c95993aa8b8a68b3e709c 18354
0xab35418fb9f8b13e3e6857c36a0769b9f94a87ec 17312.8
0xb54c7c9fa1a51300e6054b70ae9952c1fb2800b4 7829
0xb56a4dffe84f5a6edaa1971f80e8a7fb504c9edd 1047.03
0xbdb624cd1051f687f116bb0c642330b2abdfcc06 17603.5
0xbf42c2ebc5e23661b255c92d8f7271e92668e795 15869.1
0xc8c30fa803833dd1fd6dbcdd91ed0b301eff87cf 17854.9
0xdfbfb73f3013bc1584ccaa0cd2d9621194aed29b 6803.79
0xfb71f6f2e0674a89cf60b734602226ad83495eb2 8.6

Total outflows

There isn't any obvious pattern from the outflows through time (x axis hidden due to having too many values).

Outflows vs Time

Conclusion

This only covers superficially the research that can be done just by looking at the event emitted by the Chainlink contracts. I hope that it gives some clarity into some of the high level workings of the Chainlink price feed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment