New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
latest aggregation contracts #1007
Conversation
78f343b
to
fd175b9
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Other than these comments, LGTM.
solidity/contracts/Coordinator.sol
Outdated
uint256 result; | ||
uint256 oraclePayment = callback.amount.div(oracles.length); | ||
for (uint i = 0; i < responseCount; i++) { | ||
result = result.add(callbacks[requestId].responses[oracles[i]]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using SafeMath#add
means that the oracles don't get paid if the total of their responses overflow. A malicious consumer could use this to elicit unrewarded work from oracles.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is mixing the result-aggregation logic with the chainlink-business logic of paying the oracles. We will almost certainly want to make the aggregation logic pluggable, because it's very application-sensitive. Might want to make this two separate loops, even though they're iterating over the same set.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would checking if the value is less than the max value for uint248 before running aggregation work for us?
For example:
require(uint256(_data) < MAX_UINT248, "Number too big");
Unfortunately we're at the stack limit in this function as well, so we wouldn't be able to implement this check until aggregation is pulled out to its own function.
solidity/contracts/Coordinator.sol
Outdated
withdrawableTokens[oracles[i]] = withdrawableTokens[oracles[i]].add(oraclePayment); | ||
} | ||
result = result.div(responseCount); | ||
delete callbacks[requestId]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the above calculation fails, this removal of the callback will not execute. Might matter, once storage is rented rather than bought.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SafeMath's .div()
operation has no assertion:
/**
* @dev Integer division of two numbers, truncating the quotient.
*/
function div(uint256 _a, uint256 _b) internal pure returns (uint256) {
// assert(_b > 0); // Solidity automatically throws when dividing by 0
// uint256 c = _a / _b;
// assert(_a == _b * c + _a % _b); // There is no case in which this doesn't hold
return _a / _b;
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SaveMath won't, but it looks like solidity will?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In what case would aggregation run with 0 responses, which is where we would be dividing by 0?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure, just pointing out that even though SaveMath has no assertion, div can still fail.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So basically how I see it is the fix for this is to use SafeMath for the addition operation on line 235, incrementing the responseCount. Just checking, are we good with that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Following up myself on this and I think we should make this change. The responseCount variable now is a uint8 (I had assumed it was already uint256), and we're not checking that some max number of oracles is being sent in for the initiateServiceAgreement function. So, assuming we didn't hit the gas limit for a block, we could overflow with 256 oracles, which SafeMath would guard against. This would also require changing responseCount in the Callback struct to uint256.
3afdbfa
to
d0ce3cf
Compare
d0ce3cf
to
d966418
Compare
mapping(bytes32 => ServiceAgreement) public serviceAgreements; | ||
mapping(address => uint256) public withdrawableTokens; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Optional
Should this be public? Even if this doesn't have public setters, do we want people to be able to read its value?
I'm focusing on the reality that some are private, and some are public, and the inconsistency probably stems from testability. Let's make sure that's ok.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Anyone would be able to read the storage of a deployed contract anyway in order to see "private" values.
@@ -131,56 +111,11 @@ contract Coordinator is ChainlinkRequestInterface, CoordinatorInterface { | |||
_amount, | |||
_callbackAddress, | |||
_callbackFunctionId, | |||
now.add(5 minutes), | |||
now.add(EXPIRY_TIME), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Optional
You add to now twice. Perhaps there's gas savings if you DRY it up.
uint64 memory expiry = node.add(EXPIRY_INTERVAL)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We've hit the stack limit in this function. Introducing a new variable to store the expiry time won't compile.
// exit early if not all response have been received | ||
if (oracles.length > callback.responseCount) { | ||
return true; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Optional
Suggestion for above, below, and this whole method in general. If there are no gas implications, would it be better to invoke methods that document their purpose in the name?
if responseIncomplete(oracles) {
return true
}
fullfillWithAverage(...)
If there are gas considerations or the indirection reduces audit-ability, ignore.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are correct, we're eventually going to call the averaging function specified by the requester in the service agreement. For example:
return address(this).call(_aggFuncId, _requestId, _callbackAddress, _callbackFunctionId);
However, I think this story just covers the most basic form of aggregation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Broke out a couple small helper methods. Possibly premature, given that this area is going to be refactored quite a bit, but it should also help comprehension when we get back to it.
4f096ff
to
017ae10
Compare
5467c66
to
46c8dd3
Compare
46c8dd3
to
10ce4bb
Compare
cab6bb2
to
f8b0ef5
Compare
Cherry-picked onto the latest master. Still has a fair amount of outstanding work to do, but probably better to get it into master sooner than have to deal with large rebases/cherry-picks.
Adds limited support for aggregating oracle answers.