-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2 from edgeandnode/dave/finish-contract
Finish the contracts with tests
- Loading branch information
Showing
17 changed files
with
10,015 additions
and
39 deletions.
There are no files selected for viewing
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,33 @@ | ||
# billing-contracts | ||
Billing project contracts for matic deployment | ||
# Billing Contract | ||
|
||
This repository contains a Billing contract to be deployed on the Polygon (previously Matic) | ||
network. It allows Users to add tokens (GRT) into the contract for the Gateway to pull when | ||
a billing period is up. | ||
|
||
## Contract Design | ||
|
||
The contract is designed with the following requirements: | ||
|
||
- The contract is owned by the `governor` | ||
- There is a privileged used named `gateway` | ||
- Users add tokens into the contract by calling `add()`. It is important to note that the contract | ||
is designed so that the users are trusting the Gateway. Once the user adds tokens, the Gateway can | ||
pull the funds whenever it wants. The Gateway is running it's own logic, not on the blockchain, that | ||
records what Users owe. | ||
- The trust risk for the User is that the Gateway would pull funds before the User spent their funds, which are query fees in The Graph Network. | ||
- The trust risk for the Gateway is that the user adds funds, spends them on queries, and then | ||
removes their tokens before the gateway pulled the tokens. | ||
- These combined trust risks make for a situation that we expect both parties to act responsibly. | ||
It will always be recommended to keep the amount of GRT in the contract low for each user, and for | ||
the Gateway to pull regularly. This way, if one side does not play nice, the funds lost won't be | ||
that large. | ||
|
||
## Using the Polygon Bridge | ||
|
||
The Billing contract will be deployed on Polygon. This is how users will have to use it: | ||
|
||
- Move GRT from Ethereum Mainnet through the Polgon POS bridge. After a short 5-7 minute period, they | ||
will get Polygon GRT on the Polygon chain. | ||
- Users will have to get MATIC to pay for transaction fees on the Polygon network. | ||
- If the User ever wants to move their Polygon GRT back to Ethereum, they must use the reverse bridge, | ||
which has about a 3 hour waiting time. |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
// SPDX-License-Identifier: MIT | ||
|
||
pragma solidity ^0.8.4; | ||
|
||
import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; | ||
import "@openzeppelin/contracts/utils/math/Math.sol"; | ||
import "./IBilling.sol"; | ||
import "./Governed.sol"; | ||
|
||
/** | ||
* @title Billing Contract | ||
* @dev The billing contract allows for Graph Tokens to be added by a user. The token can then | ||
* be pulled by a permissoned user named 'gateway'. It is owned and controlled by the 'governor'. | ||
*/ | ||
|
||
contract Billing is IBilling, Governed { | ||
IERC20 private immutable graphToken; | ||
address public gateway; | ||
|
||
// user address --> user tokens | ||
mapping(address => uint256) public userBalances; | ||
|
||
/** | ||
* @dev Constructor function | ||
* @param _gateway Gateway address | ||
* @param _token Graph Token address | ||
* @param _governor Governor address | ||
*/ | ||
constructor( | ||
address _gateway, | ||
IERC20 _token, | ||
address _governor | ||
) { | ||
Governed._initialize(_governor); | ||
_setGateway(_gateway); | ||
graphToken = _token; | ||
} | ||
|
||
/** | ||
* @dev Check if the caller is the gateway. | ||
*/ | ||
modifier onlyGateway() { | ||
require(msg.sender == gateway, "!gateway"); | ||
_; | ||
} | ||
|
||
/** | ||
* @dev Set the new gateway address | ||
* @param _newGateway New gateway address | ||
*/ | ||
function setGateway(address _newGateway) external override onlyGovernor { | ||
_setGateway(_newGateway); | ||
} | ||
|
||
/** | ||
* @dev Set the new gateway address | ||
* @param _newGateway New gateway address | ||
*/ | ||
function _setGateway(address _newGateway) internal { | ||
require(_newGateway != address(0), "gateway != 0"); | ||
gateway = _newGateway; | ||
emit GatewayUpdated(gateway); | ||
} | ||
|
||
/** | ||
* @dev Add tokens into the billing contract | ||
* @param _amount Amount of tokens to add | ||
*/ | ||
function add(uint256 _amount) external override { | ||
_add(msg.sender, msg.sender, _amount); | ||
} | ||
|
||
/** | ||
* @dev Add tokens into the billing contract for any user | ||
* @param _to Address that tokens are being added to | ||
* @param _amount Amount of tokens to add | ||
*/ | ||
function addTo(address _to, uint256 _amount) external override { | ||
_add(msg.sender, _to, _amount); | ||
} | ||
|
||
/** | ||
* @dev Add tokens into the billing contract | ||
* @param _from Address that is sending tokens | ||
* @param _user User that is adding tokens | ||
* @param _amount Amount of tokens to add | ||
*/ | ||
function _add( | ||
address _from, | ||
address _user, | ||
uint256 _amount | ||
) private { | ||
require(graphToken.transferFrom(_from, address(this), _amount), "Add transfer failed"); | ||
userBalances[_user] = userBalances[_user] + _amount; | ||
emit TokensAdded(_user, _amount); | ||
} | ||
|
||
/** | ||
* @dev Remove tokens from the billing contract | ||
* @param _user Address that tokens are being removed from | ||
* @param _amount Amount of tokens to remove | ||
*/ | ||
function remove(address _user, uint256 _amount) external override { | ||
require(userBalances[msg.sender] >= _amount, "Too much removed"); | ||
userBalances[msg.sender] = userBalances[msg.sender] - _amount; | ||
require(graphToken.transfer(_user, _amount), "Remove transfer failed"); | ||
emit TokensRemoved(msg.sender, _user, _amount); | ||
} | ||
|
||
/** | ||
* @dev Gateway pulls tokens from the billing contract | ||
* @param _user Address that tokens are being pulled from | ||
* @param _amount Amount of tokens to pull | ||
*/ | ||
function pull(address _user, uint256 _amount) external override onlyGateway { | ||
uint256 maxAmount = _pull(_user, _amount); | ||
if (maxAmount > 0) { | ||
require(graphToken.transfer(gateway, maxAmount), "Pull transfer failed"); | ||
} | ||
} | ||
|
||
/** | ||
* @dev Gateway pulls tokens from many users in the billing contract | ||
* @param _users Addresses that tokens are being pulled from | ||
* @param _amounts Amounts of tokens to pull from each user | ||
*/ | ||
function pullMany(address[] calldata _users, uint256[] calldata _amounts) external override onlyGateway { | ||
require(_users.length == _amounts.length, "Lengths not equal"); | ||
uint256 totalPulled; | ||
for (uint256 i = 0; i < _users.length; i++) { | ||
uint256 userMax = _pull(_users[i], _amounts[i]); | ||
totalPulled = totalPulled + userMax; | ||
} | ||
if (totalPulled > 0) { | ||
require(graphToken.transfer(gateway, totalPulled), "Pull Many transfer failed"); | ||
} | ||
} | ||
|
||
/** | ||
* @dev Gateway pulls tokens from the billing contract. Uses Math.min() so that it won't fail | ||
* in the event that a user removes in front of the gateway pulling | ||
* @param _user Address that tokens are being pulled from | ||
* @param _amount Amount of tokens to pull | ||
*/ | ||
function _pull(address _user, uint256 _amount) internal returns (uint256) { | ||
uint256 maxAmount = Math.min(_amount, userBalances[_user]); | ||
if (maxAmount > 0) { | ||
userBalances[_user] = userBalances[_user] - _amount; | ||
emit TokensPulled(_user, _amount); | ||
} | ||
return maxAmount; | ||
} | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
// SPDX-License-Identifier: MIT | ||
|
||
pragma solidity ^0.8.4; | ||
|
||
/** | ||
* @title Graph Governance contract | ||
* @dev Allows a contract to be owned and controlled by the 'governor' | ||
*/ | ||
contract Governed { | ||
// -- State -- | ||
|
||
address public governor; | ||
address public pendingGovernor; | ||
|
||
// -- Events -- | ||
|
||
event NewPendingOwnership(address indexed from, address indexed to); | ||
event NewOwnership(address indexed from, address indexed to); | ||
|
||
/** | ||
* @dev Check if the caller is the governor. | ||
*/ | ||
modifier onlyGovernor { | ||
require(msg.sender == governor, "Only Governor can call"); | ||
_; | ||
} | ||
|
||
/** | ||
* @dev Initialize the governor to the contract caller. | ||
*/ | ||
function _initialize(address _initGovernor) internal { | ||
governor = _initGovernor; | ||
} | ||
|
||
/** | ||
* @dev Admin function to begin change of governor. The `_newGovernor` must call | ||
* `acceptOwnership` to finalize the transfer. | ||
* @param _newGovernor Address of new `governor` | ||
*/ | ||
function transferOwnership(address _newGovernor) external onlyGovernor { | ||
require(_newGovernor != address(0), "Governor must be set"); | ||
|
||
address oldPendingGovernor = pendingGovernor; | ||
pendingGovernor = _newGovernor; | ||
|
||
emit NewPendingOwnership(oldPendingGovernor, pendingGovernor); | ||
} | ||
|
||
/** | ||
* @dev Admin function for pending governor to accept role and update governor. | ||
* This function must called by the pending governor. | ||
*/ | ||
function acceptOwnership() external { | ||
require( | ||
pendingGovernor != address(0) && msg.sender == pendingGovernor, | ||
"Caller must be pending governor" | ||
); | ||
|
||
address oldGovernor = governor; | ||
address oldPendingGovernor = pendingGovernor; | ||
|
||
governor = pendingGovernor; | ||
pendingGovernor = address(0); | ||
|
||
emit NewOwnership(oldGovernor, governor); | ||
emit NewPendingOwnership(oldPendingGovernor, pendingGovernor); | ||
} | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
// SPDX-License-Identifier: MIT | ||
|
||
pragma solidity ^0.8.4; | ||
|
||
interface IBilling { | ||
/** | ||
* @dev User adds tokens | ||
*/ | ||
event TokensAdded(address indexed user, uint256 amount); | ||
/** | ||
* @dev User removes tokens | ||
*/ | ||
event TokensRemoved(address indexed user, address indexed to, uint256 amount); | ||
|
||
/** | ||
* @dev Gateway pulled tokens from a user | ||
*/ | ||
event TokensPulled(address indexed user, uint256 amount); | ||
|
||
/** | ||
* @dev Gateway address updated | ||
*/ | ||
event GatewayUpdated(address indexed newGateway); | ||
|
||
/** | ||
* @dev Set the new gateway address | ||
* @param _newGateway New gateway address | ||
*/ | ||
function setGateway(address _newGateway) external; // onlyGateway or onlyGovernor, or something | ||
|
||
/** | ||
* @dev Add tokens into the billing contract | ||
* @param _amount Amount of tokens to add | ||
*/ | ||
function add(uint256 _amount) external; | ||
|
||
/** | ||
* @dev Add tokens into the billing contract for any user | ||
* @param _to Address that tokens are being added to | ||
* @param _amount Amount of tokens to add | ||
*/ | ||
function addTo(address _to, uint256 _amount) external; | ||
|
||
/** | ||
* @dev Remove tokens from the billing contract | ||
* @param _to Address that tokens are being removed from | ||
* @param _amount Amount of tokens to remove | ||
*/ | ||
function remove(address _to, uint256 _amount) external; | ||
|
||
/** | ||
* @dev Gateway pulls tokens from the billing contract | ||
* @param _user Address that tokens are being pulled from | ||
* @param _amount Amount of tokens to pull | ||
*/ | ||
function pull(address _user, uint256 _amount) external; | ||
|
||
/** | ||
* @dev Gateway pulls tokens from many users in the billing contract | ||
* @param _users Addresses that tokens are being pulled from | ||
* @param _amounts Amounts of tokens to pull from each user | ||
*/ | ||
function pullMany(address[] calldata _users, uint256[] calldata _amounts) external; | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
// SPDX-License-Identifier: MIT | ||
|
||
pragma solidity ^0.8.4; | ||
|
||
import "../Governed.sol"; | ||
|
||
/** | ||
* @title GovernedMock contract | ||
*/ | ||
contract GovernedMock is Governed { | ||
constructor() { | ||
Governed._initialize(msg.sender); | ||
} | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
// SPDX-License-Identifier: MIT | ||
|
||
pragma solidity ^0.8.4; | ||
|
||
import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; | ||
import "@openzeppelin/contracts/access/Ownable.sol"; | ||
|
||
/** | ||
* @title Token contract | ||
* @dev Used for testing purposes | ||
* | ||
*/ | ||
contract Token is ERC20, Ownable { | ||
/** | ||
* @dev Token Contract Constructor. | ||
* @param _initialSupply Initial supply of GRT | ||
*/ | ||
constructor(uint256 _initialSupply) ERC20("Graph Token", "GRT") { | ||
_mint(msg.sender, _initialSupply); | ||
} | ||
} |
Oops, something went wrong.