Last active
October 11, 2018 23:26
-
-
Save thodges-gh/bf43838d78871c8ca063e0b08384aae6 to your computer and use it in GitHub Desktop.
Chainlink Package Delivery
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
pragma solidity ^0.4.24; | |
// File: solidity-cborutils/contracts/Buffer.sol | |
library Buffer { | |
struct buffer { | |
bytes buf; | |
uint capacity; | |
} | |
function init(buffer memory buf, uint capacity) internal pure { | |
if(capacity % 32 != 0) capacity += 32 - (capacity % 32); | |
// Allocate space for the buffer data | |
buf.capacity = capacity; | |
assembly { | |
let ptr := mload(0x40) | |
mstore(buf, ptr) | |
mstore(ptr, 0) | |
mstore(0x40, add(ptr, capacity)) | |
} | |
} | |
function resize(buffer memory buf, uint capacity) private pure { | |
bytes memory oldbuf = buf.buf; | |
init(buf, capacity); | |
append(buf, oldbuf); | |
} | |
function max(uint a, uint b) private pure returns(uint) { | |
if(a > b) { | |
return a; | |
} | |
return b; | |
} | |
/** | |
* @dev Appends a byte array to the end of the buffer. Resizes if doing so | |
* would exceed the capacity of the buffer. | |
* @param buf The buffer to append to. | |
* @param data The data to append. | |
* @return The original buffer. | |
*/ | |
function append(buffer memory buf, bytes data) internal pure returns(buffer memory) { | |
if(data.length + buf.buf.length > buf.capacity) { | |
resize(buf, max(buf.capacity, data.length) * 2); | |
} | |
uint dest; | |
uint src; | |
uint len = data.length; | |
assembly { | |
// Memory address of the buffer data | |
let bufptr := mload(buf) | |
// Length of existing buffer data | |
let buflen := mload(bufptr) | |
// Start address = buffer address + buffer length + sizeof(buffer length) | |
dest := add(add(bufptr, buflen), 32) | |
// Update buffer length | |
mstore(bufptr, add(buflen, mload(data))) | |
src := add(data, 32) | |
} | |
// Copy word-length chunks while possible | |
for(; len >= 32; len -= 32) { | |
assembly { | |
mstore(dest, mload(src)) | |
} | |
dest += 32; | |
src += 32; | |
} | |
// Copy remaining bytes | |
uint mask = 256 ** (32 - len) - 1; | |
assembly { | |
let srcpart := and(mload(src), not(mask)) | |
let destpart := and(mload(dest), mask) | |
mstore(dest, or(destpart, srcpart)) | |
} | |
return buf; | |
} | |
/** | |
* @dev Appends a byte to the end of the buffer. Resizes if doing so would | |
* exceed the capacity of the buffer. | |
* @param buf The buffer to append to. | |
* @param data The data to append. | |
* @return The original buffer. | |
*/ | |
function append(buffer memory buf, uint8 data) internal pure { | |
if(buf.buf.length + 1 > buf.capacity) { | |
resize(buf, buf.capacity * 2); | |
} | |
assembly { | |
// Memory address of the buffer data | |
let bufptr := mload(buf) | |
// Length of existing buffer data | |
let buflen := mload(bufptr) | |
// Address = buffer address + buffer length + sizeof(buffer length) | |
let dest := add(add(bufptr, buflen), 32) | |
mstore8(dest, data) | |
// Update buffer length | |
mstore(bufptr, add(buflen, 1)) | |
} | |
} | |
/** | |
* @dev Appends a byte to the end of the buffer. Resizes if doing so would | |
* exceed the capacity of the buffer. | |
* @param buf The buffer to append to. | |
* @param data The data to append. | |
* @return The original buffer. | |
*/ | |
function appendInt(buffer memory buf, uint data, uint len) internal pure returns(buffer memory) { | |
if(len + buf.buf.length > buf.capacity) { | |
resize(buf, max(buf.capacity, len) * 2); | |
} | |
uint mask = 256 ** len - 1; | |
assembly { | |
// Memory address of the buffer data | |
let bufptr := mload(buf) | |
// Length of existing buffer data | |
let buflen := mload(bufptr) | |
// Address = buffer address + buffer length + sizeof(buffer length) + len | |
let dest := add(add(bufptr, buflen), len) | |
mstore(dest, or(and(mload(dest), not(mask)), data)) | |
// Update buffer length | |
mstore(bufptr, add(buflen, len)) | |
} | |
return buf; | |
} | |
} | |
// File: solidity-cborutils/contracts/CBOR.sol | |
library CBOR { | |
using Buffer for Buffer.buffer; | |
uint8 private constant MAJOR_TYPE_INT = 0; | |
uint8 private constant MAJOR_TYPE_NEGATIVE_INT = 1; | |
uint8 private constant MAJOR_TYPE_BYTES = 2; | |
uint8 private constant MAJOR_TYPE_STRING = 3; | |
uint8 private constant MAJOR_TYPE_ARRAY = 4; | |
uint8 private constant MAJOR_TYPE_MAP = 5; | |
uint8 private constant MAJOR_TYPE_CONTENT_FREE = 7; | |
function encodeType(Buffer.buffer memory buf, uint8 major, uint value) private pure { | |
if(value <= 23) { | |
buf.append(uint8((major << 5) | value)); | |
} else if(value <= 0xFF) { | |
buf.append(uint8((major << 5) | 24)); | |
buf.appendInt(value, 1); | |
} else if(value <= 0xFFFF) { | |
buf.append(uint8((major << 5) | 25)); | |
buf.appendInt(value, 2); | |
} else if(value <= 0xFFFFFFFF) { | |
buf.append(uint8((major << 5) | 26)); | |
buf.appendInt(value, 4); | |
} else if(value <= 0xFFFFFFFFFFFFFFFF) { | |
buf.append(uint8((major << 5) | 27)); | |
buf.appendInt(value, 8); | |
} | |
} | |
function encodeIndefiniteLengthType(Buffer.buffer memory buf, uint8 major) private pure { | |
buf.append(uint8((major << 5) | 31)); | |
} | |
function encodeUInt(Buffer.buffer memory buf, uint value) internal pure { | |
encodeType(buf, MAJOR_TYPE_INT, value); | |
} | |
function encodeInt(Buffer.buffer memory buf, int value) internal pure { | |
if(value >= 0) { | |
encodeType(buf, MAJOR_TYPE_INT, uint(value)); | |
} else { | |
encodeType(buf, MAJOR_TYPE_NEGATIVE_INT, uint(-1 - value)); | |
} | |
} | |
function encodeBytes(Buffer.buffer memory buf, bytes value) internal pure { | |
encodeType(buf, MAJOR_TYPE_BYTES, value.length); | |
buf.append(value); | |
} | |
function encodeString(Buffer.buffer memory buf, string value) internal pure { | |
encodeType(buf, MAJOR_TYPE_STRING, bytes(value).length); | |
buf.append(bytes(value)); | |
} | |
function startArray(Buffer.buffer memory buf) internal pure { | |
encodeIndefiniteLengthType(buf, MAJOR_TYPE_ARRAY); | |
} | |
function startMap(Buffer.buffer memory buf) internal pure { | |
encodeIndefiniteLengthType(buf, MAJOR_TYPE_MAP); | |
} | |
function endSequence(Buffer.buffer memory buf) internal pure { | |
encodeIndefiniteLengthType(buf, MAJOR_TYPE_CONTENT_FREE); | |
} | |
} | |
// File: ../solidity/contracts/ChainlinkLib.sol | |
library ChainlinkLib { | |
bytes4 internal constant oracleRequestDataFid = bytes4(keccak256("requestData(address,uint256,uint256,bytes32,address,bytes4,bytes32,bytes)")); | |
using CBOR for Buffer.buffer; | |
struct Run { | |
bytes32 specId; | |
address callbackAddress; | |
bytes4 callbackFunctionId; | |
bytes32 requestId; | |
Buffer.buffer buf; | |
} | |
function initialize( | |
Run memory self, | |
bytes32 _specId, | |
address _callbackAddress, | |
string _callbackFunctionSignature | |
) internal pure returns (ChainlinkLib.Run memory) { | |
Buffer.init(self.buf, 128); | |
self.specId = _specId; | |
self.callbackAddress = _callbackAddress; | |
self.callbackFunctionId = bytes4(keccak256(bytes(_callbackFunctionSignature))); | |
self.buf.startMap(); | |
return self; | |
} | |
function encodeForOracle( | |
Run memory self, | |
uint256 _clArgsVersion | |
) internal pure returns (bytes memory) { | |
return abi.encodeWithSelector( | |
oracleRequestDataFid, | |
0, // overridden by onTokenTransfer | |
0, // overridden by onTokenTransfer | |
_clArgsVersion, | |
self.specId, | |
self.callbackAddress, | |
self.callbackFunctionId, | |
self.requestId, | |
self.buf.buf); | |
} | |
function add(Run memory self, string _key, string _value) | |
internal pure | |
{ | |
self.buf.encodeString(_key); | |
self.buf.encodeString(_value); | |
} | |
function addBytes(Run memory self, string _key, bytes _value) | |
internal pure | |
{ | |
self.buf.encodeString(_key); | |
self.buf.encodeBytes(_value); | |
} | |
function addInt(Run memory self, string _key, int256 _value) | |
internal pure | |
{ | |
self.buf.encodeString(_key); | |
self.buf.encodeInt(_value); | |
} | |
function addUint(Run memory self, string _key, uint256 _value) | |
internal pure | |
{ | |
self.buf.encodeString(_key); | |
self.buf.encodeUInt(_value); | |
} | |
function addStringArray(Run memory self, string _key, string[] memory _values) | |
internal pure | |
{ | |
self.buf.encodeString(_key); | |
self.buf.startArray(); | |
for (uint256 i = 0; i < _values.length; i++) { | |
self.buf.encodeString(_values[i]); | |
} | |
self.buf.endSequence(); | |
} | |
function close(Run memory self) internal pure { | |
self.buf.endSequence(); | |
} | |
} | |
// File: ../solidity/contracts/ENSResolver.sol | |
contract ENSResolver { | |
function addr(bytes32 node) public view returns (address); | |
} | |
// File: ../solidity/contracts/interfaces/IENS.sol | |
interface IENS { | |
// Logged when the owner of a node assigns a new owner to a subnode. | |
event NewOwner(bytes32 indexed node, bytes32 indexed label, address owner); | |
// Logged when the owner of a node transfers ownership to a new account. | |
event Transfer(bytes32 indexed node, address owner); | |
// Logged when the resolver for a node changes. | |
event NewResolver(bytes32 indexed node, address resolver); | |
// Logged when the TTL of a node changes | |
event NewTTL(bytes32 indexed node, uint64 ttl); | |
function setSubnodeOwner(bytes32 node, bytes32 label, address owner) external; | |
function setResolver(bytes32 node, address resolver) external; | |
function setOwner(bytes32 node, address owner) external; | |
function setTTL(bytes32 node, uint64 ttl) external; | |
function owner(bytes32 node) external view returns (address); | |
function resolver(bytes32 node) external view returns (address); | |
function ttl(bytes32 node) external view returns (uint64); | |
} | |
// File: ../solidity/contracts/interfaces/ILinkToken.sol | |
interface ILinkToken { | |
function balanceOf(address _owner) external returns (uint256 balance); | |
function transfer(address _to, uint _value) external returns (bool success); | |
function transferAndCall(address _to, uint _value, bytes _data) external returns (bool success); | |
} | |
// File: ../solidity/contracts/interfaces/IOracle.sol | |
interface IOracle { | |
function cancel(bytes32 _externalId) external; | |
} | |
// File: openzeppelin-solidity/contracts/math/SafeMath.sol | |
/** | |
* @title SafeMath | |
* @dev Math operations with safety checks that throw on error | |
*/ | |
library SafeMath { | |
/** | |
* @dev Multiplies two numbers, throws on overflow. | |
*/ | |
function mul(uint256 _a, uint256 _b) internal pure returns (uint256 c) { | |
// Gas optimization: this is cheaper than asserting 'a' not being zero, but the | |
// benefit is lost if 'b' is also tested. | |
// See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522 | |
if (_a == 0) { | |
return 0; | |
} | |
c = _a * _b; | |
assert(c / _a == _b); | |
return c; | |
} | |
/** | |
* @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; | |
} | |
/** | |
* @dev Subtracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend). | |
*/ | |
function sub(uint256 _a, uint256 _b) internal pure returns (uint256) { | |
assert(_b <= _a); | |
return _a - _b; | |
} | |
/** | |
* @dev Adds two numbers, throws on overflow. | |
*/ | |
function add(uint256 _a, uint256 _b) internal pure returns (uint256 c) { | |
c = _a + _b; | |
assert(c >= _a); | |
return c; | |
} | |
} | |
// File: ../solidity/contracts/Chainlinked.sol | |
contract Chainlinked { | |
using ChainlinkLib for ChainlinkLib.Run; | |
using SafeMath for uint256; | |
uint256 constant private clArgsVersion = 1; | |
uint256 constant private linkDivisibility = 10**18; | |
ILinkToken private link; | |
IOracle private oracle; | |
uint256 private requests = 1; | |
mapping(bytes32 => address) private unfulfilledRequests; | |
IENS private ens; | |
bytes32 private ensNode; | |
bytes32 constant private ensTokenSubname = keccak256("link"); | |
bytes32 constant private ensOracleSubname = keccak256("oracle"); | |
event ChainlinkRequested(bytes32 id); | |
event ChainlinkFulfilled(bytes32 id); | |
event ChainlinkCancelled(bytes32 id); | |
function newRun( | |
bytes32 _specId, | |
address _callbackAddress, | |
string _callbackFunctionSignature | |
) internal pure returns (ChainlinkLib.Run memory) { | |
ChainlinkLib.Run memory run; | |
return run.initialize(_specId, _callbackAddress, _callbackFunctionSignature); | |
} | |
function chainlinkRequest(ChainlinkLib.Run memory _run, uint256 _amount) | |
internal | |
returns (bytes32) | |
{ | |
_run.requestId = bytes32(requests); | |
requests += 1; | |
_run.close(); | |
unfulfilledRequests[_run.requestId] = oracle; | |
emit ChainlinkRequested(_run.requestId); | |
require(link.transferAndCall(oracle, _amount, _run.encodeForOracle(clArgsVersion)), "unable to transferAndCall to oracle"); | |
return _run.requestId; | |
} | |
function cancelChainlinkRequest(bytes32 _requestId) | |
internal | |
{ | |
delete unfulfilledRequests[_requestId]; | |
emit ChainlinkCancelled(_requestId); | |
oracle.cancel(_requestId); | |
} | |
function LINK(uint256 _amount) internal pure returns (uint256) { | |
return _amount.mul(linkDivisibility); | |
} | |
function setOracle(address _oracle) internal { | |
oracle = IOracle(_oracle); | |
} | |
function setLinkToken(address _link) internal { | |
link = ILinkToken(_link); | |
} | |
function chainlinkToken() | |
internal | |
view | |
returns (address) | |
{ | |
return address(link); | |
} | |
function newChainlinkWithENS(address _ens, bytes32 _node) | |
internal | |
returns (address, address) | |
{ | |
ens = IENS(_ens); | |
ensNode = _node; | |
ENSResolver resolver = ENSResolver(ens.resolver(ensNode)); | |
bytes32 linkSubnode = keccak256(abi.encodePacked(ensNode, ensTokenSubname)); | |
setLinkToken(resolver.addr(linkSubnode)); | |
return (link, updateOracleWithENS()); | |
} | |
function updateOracleWithENS() | |
internal | |
returns (address) | |
{ | |
ENSResolver resolver = ENSResolver(ens.resolver(ensNode)); | |
bytes32 oracleSubnode = keccak256(abi.encodePacked(ensNode, ensOracleSubname)); | |
setOracle(resolver.addr(oracleSubnode)); | |
return oracle; | |
} | |
modifier checkChainlinkFulfillment(bytes32 _requestId) { | |
require(msg.sender == unfulfilledRequests[_requestId], "source must be the oracle of the request"); | |
_; | |
delete unfulfilledRequests[_requestId]; | |
emit ChainlinkFulfilled(_requestId); | |
} | |
} | |
// File: ../node_modules/openzeppelin-solidity/contracts/ownership/Ownable.sol | |
/** | |
* @title Ownable | |
* @dev The Ownable contract has an owner address, and provides basic authorization control | |
* functions, this simplifies the implementation of "user permissions". | |
*/ | |
contract Ownable { | |
address public owner; | |
event OwnershipRenounced(address indexed previousOwner); | |
event OwnershipTransferred( | |
address indexed previousOwner, | |
address indexed newOwner | |
); | |
/** | |
* @dev The Ownable constructor sets the original `owner` of the contract to the sender | |
* account. | |
*/ | |
constructor() public { | |
owner = msg.sender; | |
} | |
/** | |
* @dev Throws if called by any account other than the owner. | |
*/ | |
modifier onlyOwner() { | |
require(msg.sender == owner); | |
_; | |
} | |
/** | |
* @dev Allows the current owner to relinquish control of the contract. | |
* @notice Renouncing to ownership will leave the contract without an owner. | |
* It will not be possible to call the functions with the `onlyOwner` | |
* modifier anymore. | |
*/ | |
function renounceOwnership() public onlyOwner { | |
emit OwnershipRenounced(owner); | |
owner = address(0); | |
} | |
/** | |
* @dev Allows the current owner to transfer control of the contract to a newOwner. | |
* @param _newOwner The address to transfer ownership to. | |
*/ | |
function transferOwnership(address _newOwner) public onlyOwner { | |
_transferOwnership(_newOwner); | |
} | |
/** | |
* @dev Transfers control of the contract to a newOwner. | |
* @param _newOwner The address to transfer ownership to. | |
*/ | |
function _transferOwnership(address _newOwner) internal { | |
require(_newOwner != address(0)); | |
emit OwnershipTransferred(owner, _newOwner); | |
owner = _newOwner; | |
} | |
} | |
// File: ../examples/ropsten/contracts/RopstenConsumerBase.sol | |
contract PackageDelivery is Chainlinked, Ownable { | |
using SafeMath for uint256; | |
string public code; | |
address public fromAccount; | |
address public toAccount; | |
uint256 constant PAYMENT_AMOUNT_USD = 100; | |
address constant RINKEBY_LINK_ADDRESS = 0x01BE23585060835E02B77ef475b0Cc51aA1e0709; | |
address constant RINKEBY_ORACLE_ADDRESS = 0xf18455e70984e8fda0ADbe2c8dD21509DBeFA218; | |
bytes32 constant EASYPOST_SPEC_ID = bytes32("d264d69bf19743b4b7fc444a5f947b31"); | |
bytes32 constant ETH_PRICE_SPEC_ID = bytes32("75d8d0d8429c48ccbee22e24969ea1d4"); | |
bytes32 constant DELAYED_PRICE_SPEC_ID = bytes32("2faf8277aa6346619a8f585c8b169f82"); | |
bytes32 constant PRE_TRANSIT = bytes32("pre_transit"); | |
bytes32 constant IN_TRANSIT = bytes32("in_transit"); | |
bytes32 constant OUT_FOR_DELIVERY = bytes32("out_for_delivery"); | |
bytes32 constant DELIVERED = bytes32("delivered"); | |
bytes32 constant RETURN_TO_SENDER = bytes32("return_to_sender"); | |
bytes32 constant FAILURE = bytes32("failure"); | |
bytes32 constant UNKNOWN = bytes32("unknown"); | |
event ContractFunded(uint256 indexed amount); | |
event InsufficientFunds(uint256 indexed balance, uint256 indexed required); | |
constructor(address _from, address _to) public payable Ownable() { | |
setLinkToken(RINKEBY_LINK_ADDRESS); | |
setOracle(RINKEBY_ORACLE_ADDRESS); | |
fromAccount = _from; | |
toAccount = _to; | |
} | |
function() | |
external | |
payable | |
{ | |
emit ContractFunded(msg.value); | |
} | |
function requestStatus(string _code) | |
public | |
contractFunded | |
onlyAuthorized | |
returns (bytes32 requestId) | |
{ | |
code = _code; | |
ChainlinkLib.Run memory run = newRun(EASYPOST_SPEC_ID, this, "fulfillStatus(bytes32,bytes32)"); | |
run.add("car", "USPS"); | |
run.add("code", _code); | |
string[] memory path = new string[](1); | |
path[0] = "status"; | |
run.addStringArray("copyPath", path); | |
requestId = chainlinkRequest(run, LINK(1)); | |
} | |
function fulfillStatus(bytes32 _requestId, bytes32 _status) | |
public | |
checkChainlinkFulfillment(_requestId) | |
{ | |
if (_status == DELIVERED) { | |
requestEthereumPrice(); | |
} else if (_status == PRE_TRANSIT || _status == IN_TRANSIT || _status == OUT_FOR_DELIVERY) { | |
requestEthereumPrice(); | |
} else { | |
ILinkToken link = ILinkToken(chainlinkToken()); | |
require(link.transfer(owner, link.balanceOf(address(this))), "Unable to transfer"); | |
selfdestruct(fromAccount); | |
} | |
} | |
function requestEthereumPrice() internal returns (bytes32 requestId) { | |
ChainlinkLib.Run memory run = newRun(ETH_PRICE_SPEC_ID, this, "fulfillEthereumPrice(bytes32,uint256)"); | |
run.add("url", "https://min-api.cryptocompare.com/data/price?fsym=ETH&tsyms=USD"); | |
string[] memory path = new string[](1); | |
path[0] = "USD"; | |
run.addStringArray("path", path); | |
run.addInt("times", 100); | |
run.addUint("until", now + 15 minutes); | |
requestId = chainlinkRequest(run, LINK(1)); | |
} | |
function delayedRequestEthereumPrice() internal returns (bytes32 requestId) { | |
ChainlinkLib.Run memory run = newRun(ETH_PRICE_SPEC_ID, this, "fulfillEthereumPrice(bytes32,uint256)"); | |
run.add("url", "https://min-api.cryptocompare.com/data/price?fsym=ETH&tsyms=USD"); | |
string[] memory path = new string[](1); | |
path[0] = "USD"; | |
run.addStringArray("path", path); | |
run.addInt("times", 100); | |
requestId = chainlinkRequest(run, LINK(1)); | |
} | |
function fulfillEthereumPrice(bytes32 _requestId, uint256 _price) | |
public | |
checkChainlinkFulfillment(_requestId) | |
{ | |
uint256 transferAmount = PAYMENT_AMOUNT_USD.mul(10**20).div(_price); | |
if (transferAmount <= address(this).balance) { | |
toAccount.transfer(transferAmount); | |
ILinkToken link = ILinkToken(chainlinkToken()); | |
require(link.transfer(owner, link.balanceOf(address(this))), "Unable to transfer"); | |
selfdestruct(fromAccount); | |
} else { | |
emit InsufficientFunds(address(this).balance, transferAmount); | |
} | |
} | |
function withdrawLink() public onlyOwner { | |
ILinkToken link = ILinkToken(chainlinkToken()); | |
require(link.transfer(msg.sender, link.balanceOf(address(this))), "Unable to transfer"); | |
} | |
modifier contractFunded() { | |
require(address(this).balance > 0, "Funds have not been sent to contract"); | |
_; | |
} | |
modifier onlyAuthorized() { | |
require(msg.sender == owner || msg.sender == address(this) || msg.sender == fromAccount || msg.sender == toAccount, "Only authorized users may call"); | |
_; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment