Merge pull request #17 from status-im/test/teller

add teller network to test folder
This commit is contained in:
Iuri Matias 2019-08-28 09:56:30 -04:00 committed by GitHub
commit ff1c94790a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
57 changed files with 22879 additions and 0 deletions

View File

@ -0,0 +1,9 @@
{
"presets": [
"@babel/preset-env",
"@babel/preset-react"
],
"plugins": [
"@babel/plugin-proposal-class-properties"
]
}

View File

@ -0,0 +1 @@
SKIP_PREFLIGHT_CHECK=true

View File

@ -0,0 +1,292 @@
{
"env": {
"browser": true,
"es6": true,
"node": true,
"jest": true
},
"parser": "babel-eslint",
"extends": [
"eslint:recommended",
"plugin:react/recommended"
],
"parserOptions": {
"sourceType": "module",
"ecmaVersion": 2018,
"ecmaFeatures": {
"jsx": true
}
},
"globals": {
"__": true
},
"plugins": [
"react"
],
"rules": {
"accessor-pairs": "error",
"array-bracket-newline": "error",
"array-bracket-spacing": [
"error",
"never"
],
"array-callback-return": "off",
"array-element-newline": "off",
"arrow-body-style": "off",
"arrow-parens": "off",
"arrow-spacing": [
"error",
{
"after": true,
"before": true
}
],
"block-scoped-var": "error",
"block-spacing": "error",
"brace-style": "off",
"callback-return": "off",
"camelcase": "off",
"capitalized-comments": "off",
"class-methods-use-this": "off",
"comma-dangle": "error",
"comma-spacing": "off",
"comma-style": [
"error",
"last"
],
"complexity": "error",
"computed-property-spacing": [
"error",
"never"
],
"consistent-return": "off",
"consistent-this": "off",
"curly": "off",
"default-case": "error",
"dot-location": [
"error",
"property"
],
"dot-notation": "off",
"eol-last": "error",
"eqeqeq": "error",
"for-direction": "error",
"func-call-spacing": "error",
"func-name-matching": "error",
"func-names": "off",
"func-style": "off",
"function-paren-newline": "off",
"generator-star-spacing": "error",
"getter-return": "error",
"global-require": "off",
"guard-for-in": "off",
"handle-callback-err": "off",
"id-blacklist": "error",
"id-length": "off",
"id-match": "error",
"indent": "off",
"indent-legacy": "off",
"init-declarations": "off",
"jsx-quotes": "error",
"key-spacing": "off",
"keyword-spacing": "off",
"line-comment-position": "off",
"lines-around-comment": "error",
"lines-around-directive": "error",
"max-depth": "error",
"max-len": "off",
"max-lines": "off",
"max-params": "off",
"max-statements": "off",
"max-statements-per-line": "off",
"multiline-ternary": [
"error",
"never"
],
"new-parens": "off",
"newline-after-var": "off",
"newline-before-return": "off",
"newline-per-chained-call": "off",
"no-alert": "error",
"no-array-constructor": "error",
"no-await-in-loop": "error",
"no-bitwise": "error",
"no-buffer-constructor": "error",
"no-caller": "error",
"no-catch-shadow": "error",
"no-confusing-arrow": "error",
"no-console": "off",
"no-continue": "off",
"no-debugger": "warn",
"no-div-regex": "error",
"no-duplicate-imports": "error",
"no-else-return": 2,
"no-empty-function": "off",
"no-eq-null": "error",
"no-eval": "off",
"no-extend-native": "error",
"no-extra-bind": "error",
"no-extra-label": "error",
"no-extra-parens": "off",
"no-floating-decimal": "error",
"no-implicit-coercion": [
"error",
{
"allow": ["!!"]
}
],
"no-implicit-globals": "error",
"no-implied-eval": "error",
"no-inline-comments": "off",
"no-inner-declarations": [
"error",
"functions"
],
"no-invalid-this": "off",
"no-iterator": "error",
"no-label-var": "error",
"no-labels": "error",
"no-lone-blocks": "error",
"no-lonely-if": "off",
"no-loop-func": "off",
"no-magic-numbers": "off",
"no-mixed-operators": "error",
"no-mixed-requires": "error",
"no-multi-assign": "error",
"no-multi-spaces": "off",
"no-multi-str": "error",
"no-multiple-empty-lines": "error",
"no-native-reassign": "error",
"no-negated-condition": "off",
"no-negated-in-lhs": "error",
"no-nested-ternary": "error",
"no-new": "error",
"no-new-func": "error",
"no-new-object": "error",
"no-new-require": "error",
"no-new-wrappers": "error",
"no-octal-escape": "error",
"no-param-reassign": "off",
"no-path-concat": "error",
"no-plusplus": "off",
"no-process-env": "off",
"no-process-exit": "off",
"no-proto": "error",
"no-prototype-builtins": "off",
"no-restricted-globals": "error",
"no-restricted-imports": "error",
"no-restricted-modules": "error",
"no-restricted-properties": "error",
"no-restricted-syntax": "error",
"no-return-assign": "error",
"no-return-await": "error",
"no-script-url": "error",
"no-self-compare": "error",
"no-sequences": "error",
"no-shadow": "off",
"no-shadow-restricted-names": "error",
"no-spaced-func": "error",
"no-sync": "off",
"no-tabs": "error",
"no-template-curly-in-string": "error",
"no-ternary": "off",
"no-throw-literal": "error",
"no-trailing-spaces": "off",
"no-undef-init": "error",
"no-undefined": "off",
"no-underscore-dangle": "off",
"no-unmodified-loop-condition": "error",
"no-unneeded-ternary": "error",
"no-unused-expressions": "error",
"no-unused-vars": ["error", {
"argsIgnorePattern": "^_",
"varsIgnorePattern": "^_"
}],
"no-use-before-define": "off",
"no-useless-call": "off",
"no-useless-computed-key": "error",
"no-useless-concat": "error",
"no-useless-constructor": "error",
"no-useless-escape": "off",
"no-useless-rename": "error",
"no-useless-return": "off",
"no-var": "off",
"no-void": "error",
"no-warning-comments": "off",
"no-whitespace-before-property": "error",
"no-with": "error",
"nonblock-statement-body-position": "error",
"object-curly-newline": "off",
"object-curly-spacing": [
"off"
],
"object-property-newline": "off",
"object-shorthand": "off",
"one-var": "off",
"one-var-declaration-per-line": "off",
"operator-assignment": "off",
"operator-linebreak": "error",
"padded-blocks": "off",
"padding-line-between-statements": "error",
"prefer-arrow-callback": "off",
"prefer-const": "off",
"prefer-destructuring": "off",
"prefer-numeric-literals": "error",
"prefer-promise-reject-errors": "error",
"prefer-reflect": "off",
"prefer-rest-params": "off",
"prefer-spread": "off",
"prefer-template": "off",
"quote-props": "off",
"quotes": "off",
"radix": "error",
"require-await": "off",
"require-jsdoc": "off",
"rest-spread-spacing": "error",
"semi": "error",
"semi-spacing": [
"error",
{
"after": true,
"before": false
}
],
"semi-style": [
"error",
"last"
],
"sort-imports": "off",
"sort-keys": "off",
"sort-vars": "off",
"space-before-blocks": "off",
"space-before-function-paren": "off",
"space-in-parens": [
"error",
"never"
],
"space-infix-ops": "off",
"space-unary-ops": "error",
"spaced-comment": "off",
"strict": "error",
"switch-colon-spacing": "error",
"symbol-description": "error",
"template-curly-spacing": [
"error",
"never"
],
"template-tag-spacing": "error",
"unicode-bom": [
"error",
"never"
],
"valid-jsdoc": "error",
"vars-on-top": "off",
"wrap-iife": "error",
"wrap-regex": "error",
"yield-star-spacing": "error",
"yoda": [
"error",
"never"
]
}
}

View File

@ -0,0 +1 @@
*.sol diff linguist-language=Solidity

38
test/status-teller-network/.gitignore vendored Normal file
View File

@ -0,0 +1,38 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
.idea
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.embark
chains.json
embarkConfig/production/password
embarkConfig/livenet/password
coverage
dist
node_modules
src/embarkArtifacts
.idea
yarn-error.log
cypress/videos/*
.secret.json
crytic-export/

View File

@ -0,0 +1,3 @@
{
"extends": "solhint:default"
}

View File

@ -0,0 +1 @@
node_modules

View File

@ -0,0 +1,20 @@
{
"extends": "solium:all",
"plugins": [
"security"
],
"rules": {
"quotes": [
"error",
"double"
],
"indentation": [
"error",
4
],
"arg-overflow": [
"warning",
3
]
}
}

View File

@ -0,0 +1,22 @@
pragma solidity >=0.5.0 <0.6.0;
contract Controlled {
/// @notice The address of the controller is the only address that can call
/// a function with this modifier
modifier onlyController {
require(msg.sender == controller, "Unauthorized");
_;
}
address payable public controller;
constructor() internal {
controller = msg.sender;
}
/// @notice Changes the controller of the contract
/// @param _newController The new controller of the contract
function changeController(address payable _newController) public onlyController {
controller = _newController;
}
}

View File

@ -0,0 +1,67 @@
/* solium-disable no-empty-blocks */
/* solium-disable security/no-inline-assembly */
pragma solidity >=0.5.0 <0.6.0;
/**
* @dev Uses ethereum signed messages
*/
contract MessageSigned {
constructor() internal {}
/**
* @dev recovers address who signed the message
* @param _signHash operation ethereum signed message hash
* @param _messageSignature message `_signHash` signature
*/
function _recoverAddress(bytes32 _signHash, bytes memory _messageSignature)
internal
pure
returns(address)
{
uint8 v;
bytes32 r;
bytes32 s;
(v,r,s) = signatureSplit(_messageSignature);
return ecrecover(_signHash, v, r, s);
}
/**
* @dev Hash a hash with `"\x19Ethereum Signed Message:\n32"`
* @param _hash Sign to hash.
* @return Hash to be signed.
*/
function _getSignHash(bytes32 _hash) internal pure returns (bytes32 signHash) {
signHash = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", _hash));
}
/**
* @dev divides bytes signature into `uint8 v, bytes32 r, bytes32 s`
* @param _signature Signature string
*/
function signatureSplit(bytes memory _signature)
internal
pure
returns (uint8 v, bytes32 r, bytes32 s)
{
require(_signature.length == 65, "Bad signature length");
// The signature format is a compact form of:
// {bytes32 r}{bytes32 s}{uint8 v}
// Compact means, uint8 is not padded to 32 bytes.
assembly {
r := mload(add(_signature, 32))
s := mload(add(_signature, 64))
// Here we are loading the last 32 bytes, including 31 bytes
// of 's'. There is no 'mload8' to do this.
//
// 'byte' is not working due to the Solidity parser, so lets
// use the second best option, 'and'
v := and(mload(add(_signature, 65)), 0xff)
}
if (v < 27) {
v += 27;
}
require(v == 27 || v == 28, "Bad signature version");
}
}

View File

@ -0,0 +1,82 @@
pragma solidity >=0.5.0 <0.6.0;
/**
* @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 private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev The Ownable constructor sets the original `owner` of the contract to the sender
* account.
*/
constructor () internal {
_owner = msg.sender;
emit OwnershipTransferred(address(0), _owner);
}
/**
* @dev Get the contract's owner
* @return the address of the owner.
*/
function owner() public view returns (address) {
return _owner;
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(isOwner(), "Only the contract's owner can invoke this function");
_;
}
/**
* @dev Sets an owner address
* @param _newOwner new owner address
*/
function _setOwner(address _newOwner) internal {
_owner = _newOwner;
}
/**
* @dev is sender the owner of the contract?
* @return true if `msg.sender` is the owner of the contract.
*/
function isOwner() public view returns (bool) {
return msg.sender == _owner;
}
/**
* @dev Allows the current owner to relinquish control of the contract.
* 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() external onlyOwner {
emit OwnershipTransferred(_owner, address(0));
_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) external 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), "New owner cannot be address(0)");
emit OwnershipTransferred(_owner, _newOwner);
_owner = _newOwner;
}
}

View File

@ -0,0 +1,47 @@
pragma solidity >=0.5.0 <0.6.0;
import "./Ownable.sol";
/**
* @title Pausable
* @dev Makes contract functions pausable by the owner
*/
contract Pausable is Ownable {
event Paused();
event Unpaused();
bool public paused;
constructor () internal {
paused = false;
}
modifier whenNotPaused() {
require(!paused, "Contract must be unpaused");
_;
}
modifier whenPaused() {
require(paused, "Contract must be paused");
_;
}
/**
* @dev Disables contract functions marked with "whenNotPaused" and enables the use of functions marked with "whenPaused"
* Only the owner of the contract can invoke this function
*/
function pause() external onlyOwner whenNotPaused {
paused = true;
emit Paused();
}
/**
* @dev Enables contract functions marked with "whenNotPaused" and disables the use of functions marked with "whenPaused"
* Only the owner of the contract can invoke this function
*/
function unpause() external onlyOwner whenPaused {
paused = false;
emit Unpaused();
}
}

View File

@ -0,0 +1,140 @@
/* solium-disable security/no-block-members */
pragma solidity >=0.5.0 <0.6.0;
import "../common/Ownable.sol";
import "../token/ERC20Token.sol";
import "./KyberNetworkProxy.sol";
/**
* @title KyberFeeBurner
* @dev Contract that holds assets for the purpose of trading them to SNT and burning them
* @dev Assets come from the Escrow contract fees
*/
contract KyberFeeBurner is Ownable {
address public SNT;
address public burnAddress;
address public walletId;
KyberNetworkProxy public kyberNetworkProxy;
// In Kyber's contracts, this is the address for ETH
address constant internal ETH_TOKEN_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
/**
* @param _snt Address of the SNT contract
* @param _burnAddress Address where to burn the assets
* @param _kyberNetworkProxy License contract instance address for arbitrators
* @param _walletId Wallet address to send part of the fees to (used for the fee sharing program)
*/
constructor(address _snt, address _burnAddress, address _kyberNetworkProxy, address _walletId) public {
SNT = _snt;
burnAddress = _burnAddress;
kyberNetworkProxy = KyberNetworkProxy(_kyberNetworkProxy);
walletId = _walletId;
}
event SNTAddressChanged(address sender, address prevSNTAddress, address newSNTAddress);
/**
* @dev Changes the SNT contract address
* @param _snt New SNT contract address
*/
function setSNT(address _snt) external onlyOwner {
emit SNTAddressChanged(msg.sender, SNT, _snt);
SNT = _snt;
}
event BurnAddressChanged(address sender, address prevBurnAddress, address newBurnAddress);
/**
* @dev Changes the burn address
* @param _burnAddress New burn address
*/
function setBurnAddress(address _burnAddress) external onlyOwner {
emit BurnAddressChanged(msg.sender, burnAddress, _burnAddress);
burnAddress = _burnAddress;
}
event KyberNetworkProxyAddressChanged(address sender, address prevKyberAddress, address newKyberAddress);
/**
* @dev Changes the KyberNetworkProxy contract address
* @param _kyberNetworkProxy New KyberNetworkProxy address
*/
function setKyberNetworkProxyAddress(address _kyberNetworkProxy) external onlyOwner {
emit KyberNetworkProxyAddressChanged(msg.sender, address(kyberNetworkProxy), _kyberNetworkProxy);
kyberNetworkProxy = KyberNetworkProxy(_kyberNetworkProxy);
}
event WalletIdChanged(address sender, address prevWalletId, address newWalletId);
/**
* @dev Changes the walletId address (for the fee sharing program)
* @param _walletId New walletId address
*/
function setWalletId(address _walletId) external onlyOwner {
emit WalletIdChanged(msg.sender, walletId, _walletId);
walletId = _walletId;
}
event Swap(address sender, address srcToken, address destToken, uint srcAmount, uint destAmount);
/**
* @dev Swaps the selected asset to SNT and transfers it to the burn address
* @param _token Address of the asset to trade
*/
function swap(address _token) external {
uint balance = 0;
uint minConversionRate = 0;
uint destAmount = 0;
if (_token == address(0)) {
balance = address(this).balance;
(minConversionRate,) = kyberNetworkProxy.getExpectedRate(ETH_TOKEN_ADDRESS, SNT, balance);
destAmount = kyberNetworkProxy.trade.value(balance)(ETH_TOKEN_ADDRESS, balance, SNT, burnAddress, 0 - uint256(1), minConversionRate, walletId);
emit Swap(msg.sender, ETH_TOKEN_ADDRESS, SNT, balance, destAmount);
} else {
ERC20Token t = ERC20Token(_token);
balance = t.balanceOf(address(this));
if (_token == SNT) {
require(t.transfer(burnAddress, balance), "SNT transfer failure");
emit Swap(msg.sender, SNT, SNT, balance, balance);
return;
} else {
// Mitigate ERC20 Approve front-running attack, by initially setting allowance to 0
require(ERC20Token(_token).approve(address(kyberNetworkProxy), 0), "Failed to reset approval");
// Set the spender's token allowance to tokenQty
require(ERC20Token(_token).approve(address(kyberNetworkProxy), balance), "Failed to approve trade amount");
(minConversionRate,) = kyberNetworkProxy.getExpectedRate(_token, SNT, balance);
destAmount = kyberNetworkProxy.trade(_token, balance, SNT, burnAddress, 0 - uint256(1), minConversionRate, walletId);
emit Swap(msg.sender, _token, SNT, balance, destAmount);
}
}
}
event EscapeTriggered(address sender, address token, uint amount);
/**
* @dev Exits the selected asset to the owner
* @param _token Address of the asset to exit
*/
function escape(address _token) external onlyOwner {
if (_token == address(0)) {
uint ethBalance = address(this).balance;
address(uint160(owner())).transfer(ethBalance);
emit EscapeTriggered(msg.sender, _token, ethBalance);
} else {
ERC20Token t = ERC20Token(_token);
uint tokenBalance = t.balanceOf(address(this));
require(t.transfer(owner(), tokenBalance), "Token transfer error");
emit EscapeTriggered(msg.sender, _token, tokenBalance);
}
}
function() payable external {
}
}

View File

@ -0,0 +1,67 @@
pragma solidity >=0.5.0 <0.6.0;
/**
* @title KyberNetworkProxy
* @dev Mock of the KyberNetworkProxy. Only used in development
*/
contract KyberNetworkProxy {
constructor() public {
}
/**
* @dev Get a mocked up rate for the trade
*/
function getExpectedRate(
address /* src */,
address /* dest */,
uint /* srcQty */
)
public pure
returns(uint expectedRate, uint slippageRate)
{
return (32749000000000000000, 31766530000000000000);
}
/// @notice use token address ETH_TOKEN_ADDRESS for ether
/// @dev makes a trade between src and dest token and send dest token to destAddress
/// @param maxDestAmount A limit on the amount of dest tokens
/// @return amount of actual dest tokens
function trade(
address /* src */,
uint /* srcAmount */,
address /* dest */,
address /* destAddress */,
uint maxDestAmount,
uint /* minConversionRate */,
address /* walletId */
)
public
payable
returns(uint)
{
return maxDestAmount;
}
/// @dev makes a trade between src and dest token and send dest tokens to msg sender
/// @return amount of actual dest tokens
function swapTokenToToken(
address /* src */,
uint /* srcAmount */,
address /* dest */,
uint /* minConversionRate */
)
public pure
returns(uint)
{
return 100;
}
/// @dev makes a trade from Ether to token. Sends token to msg sender
/// @return amount of actual dest tokens
function swapEtherToToken(
address /* token */,
uint /* minConversionRate */
) public payable returns(uint) {
return 200;
}
}

View File

@ -0,0 +1,94 @@
/* solium-disable security/no-low-level-calls */
/* solium-disable security/no-inline-assembly */
pragma solidity >=0.5.0 <0.6.0;
// Adapted from
// https://github.com/zeppelinos/labs/tree/master/upgradeability_using_unstructured_storage
import './UpgradeabilityProxy.sol';
/**
* @title OwnedUpgradeabilityProxy
* @dev This contract combines an upgradeability proxy with basic authorization control functionalities
*/
contract OwnedUpgradeabilityProxy is UpgradeabilityProxy {
/**
* @dev Event to show ownership has been transferred
* @param previousOwner representing the address of the previous owner
* @param newOwner representing the address of the new owner
*/
event ProxyOwnershipTransferred(address previousOwner, address newOwner);
// Storage position of the owner of the contract
bytes32 private constant proxyOwnerPosition = keccak256("im.status.proxy.owner");
/**
* @dev the constructor sets the original owner of the contract to the sender account.
*/
constructor() public {
setUpgradeabilityOwner(msg.sender);
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyProxyOwner() {
require(msg.sender == proxyOwner(), "Only the proxy owner can invoke this function");
_;
}
/**
* @dev Tells the address of the owner
* @return the address of the owner
*/
function proxyOwner() public view returns (address owner) {
bytes32 position = proxyOwnerPosition;
assembly {
owner := sload(position)
}
}
/**
* @dev Sets the address of the owner
* @param _newProxyOwner New proxy owner address
*/
function setUpgradeabilityOwner(address _newProxyOwner) internal {
bytes32 position = proxyOwnerPosition;
assembly {
sstore(position, _newProxyOwner)
}
}
/**
* @dev Allows the current owner to transfer control of the contract to a new Owner.
* @param _newProxyOwner The address to transfer ownership to.
*/
function transferProxyOwnership(address _newProxyOwner) external onlyProxyOwner {
require(_newProxyOwner != address(0), "Invalid new owner");
emit ProxyOwnershipTransferred(proxyOwner(), _newProxyOwner);
setUpgradeabilityOwner(_newProxyOwner);
}
/**
* @dev Allows the proxy owner to upgrade the current version of the proxy.
* @param _implementation representing the address of the new implementation to be set.
*/
function upgradeTo(address _implementation) external onlyProxyOwner {
_upgradeTo(_implementation);
}
/**
* @dev Allows the proxy owner to upgrade the current version of the proxy and call the new implementation
* to initialize whatever is needed through a low level call.
* @param _implementation representing the address of the new implementation to be set.
* @param _data represents the msg.data to bet sent in the low level call. This parameter may include the function
* signature of the implementation to be called with the needed payload
*/
function upgradeToAndCall(address _implementation, bytes calldata _data) external payable onlyProxyOwner {
_upgradeTo(_implementation);
(bool success,) = _implementation.delegatecall(_data);
require(success, "Upgrade failed");
}
}

View File

@ -0,0 +1,49 @@
/* solium-disable security/no-block-members */
/* solium-disable security/no-inline-assembly */
pragma solidity >=0.5.0 <0.6.0;
// Adapted from
// https://github.com/zeppelinos/labs/tree/master/upgradeability_using_unstructured_storage
/**
* @title Proxy
* @dev Basic EIP897 proxy functionality
*/
contract Proxy {
/**
* @dev Tells the address of the implementation where every call will be delegated.
* @return address of the implementation to which it will be delegated
*/
function implementation() public view returns (address);
/**
* @dev Fallback function allowing to perform a delegatecall to the given implementation.
* This function will return whatever the implementation call returns
*/
function () external payable {
address _impl = implementation();
require(_impl != address(0), "No implementation available");
assembly {
let ptr := mload(0x40)
calldatacopy(ptr, 0, calldatasize)
let result := delegatecall(gas, _impl, ptr, calldatasize, 0, 0)
let size := returndatasize
returndatacopy(ptr, 0, size)
switch result
case 0 { revert(ptr, size) }
default { return(ptr, size) }
}
}
/**
* @dev EIP897 proxy type
* @return 1 to indicate not upgradable proxy
*/
function proxyType() public pure returns (uint256) {
return 1;
}
}

View File

@ -0,0 +1,64 @@
/* solium-disable security/no-inline-assembly */
pragma solidity >=0.5.0 <0.6.0;
// Adapted from
// https://github.com/zeppelinos/labs/tree/master/upgradeability_using_unstructured_storage
import './Proxy.sol';
/**
* @title UpgradeabilityProxy
* @dev This contract represents a proxy where the implementation address to which it will delegate can be upgraded
*/
contract UpgradeabilityProxy is Proxy {
/**
* @dev This event will be emitted every time the implementation gets upgraded
* @param implementation representing the address of the upgraded implementation
*/
event Upgraded(address indexed implementation);
// Storage position of the address of the current implementation
bytes32 private constant implementationPosition = keccak256("im.status.proxy.implementation");
/**
* @dev Tells the address of the current implementation
* @return address of the current implementation
*/
function implementation() public view returns (address impl) {
bytes32 position = implementationPosition;
assembly {
impl := sload(position)
}
}
/**
* @dev Sets the address of the current implementation
* @param _implementation address representing the new implementation to be set
*/
function _setImplementation(address _implementation) internal {
bytes32 position = implementationPosition;
assembly {
sstore(position, _implementation)
}
}
/**
* @dev Upgrades the implementation address
* @param _implementation representing the address of the new implementation to be set
*/
function _upgradeTo(address _implementation) internal {
require(implementation() != _implementation, "Current and new implementation should be different");
_setImplementation(_implementation);
emit Upgraded(_implementation);
}
/**
* @dev EIP897 proxy type
* @return 2 to indicate upgradable proxy
*/
function proxyType() public pure returns (uint256) {
return 2;
}
}

View File

@ -0,0 +1,136 @@
/* solium-disable security/no-block-members */
pragma solidity >=0.5.0 <0.6.0;
import "./ArbitrationLicense.sol";
/**
* Arbitrable
* @dev Utils for management of disputes
*/
contract Arbitrable {
enum ArbitrationResult {UNSOLVED, BUYER, SELLER}
ArbitrationLicense public arbitratorLicenses;
mapping(uint => ArbitrationCase) public arbitrationCases;
struct ArbitrationCase {
bool open;
address openBy;
address arbitrator;
ArbitrationResult result;
string motive;
}
event ArbitratorChanged(address arbitrator);
event ArbitrationCanceled(uint escrowId);
event ArbitrationRequired(uint escrowId);
event ArbitrationResolved(uint escrowId, ArbitrationResult result, address arbitrator);
/**
* @param _arbitratorLicenses Address of the Arbitrator Licenses contract
*/
constructor(address _arbitratorLicenses) public {
arbitratorLicenses = ArbitrationLicense(_arbitratorLicenses);
}
/**
* @param _escrowId Id of the escrow with an open dispute
* @param _releaseFunds Release funds to the buyer
* @param _arbitrator Address of the arbitrator solving the dispute
* @dev Abstract contract used to perform actions after a dispute has been settled
*/
function _solveDispute(uint _escrowId, bool _releaseFunds, address _arbitrator) internal;
/**
* @notice Get arbitrator of an escrow
* @return address Arbitrator address
*/
function _getArbitrator(uint _escrowId) internal view returns(address);
/**
* @notice Determine if a dispute exists/existed for an escrow
* @param _escrowId Escrow to verify
* @return bool result
*/
function isDisputed(uint _escrowId) public view returns (bool) {
return _isDisputed(_escrowId);
}
function _isDisputed(uint _escrowId) internal view returns (bool) {
return arbitrationCases[_escrowId].open || arbitrationCases[_escrowId].result != ArbitrationResult.UNSOLVED;
}
/**
* @notice Determine if a dispute existed for an escrow
* @param _escrowId Escrow to verify
* @return bool result
*/
function hadDispute(uint _escrowId) public view returns (bool) {
return arbitrationCases[_escrowId].result != ArbitrationResult.UNSOLVED;
}
/**
* @notice Cancel arbitration
* @param _escrowId Escrow to cancel
*/
function cancelArbitration(uint _escrowId) external {
require(arbitrationCases[_escrowId].openBy == msg.sender, "Arbitration can only be canceled by the opener");
require(arbitrationCases[_escrowId].result == ArbitrationResult.UNSOLVED && arbitrationCases[_escrowId].open,
"Arbitration already solved or not open");
delete arbitrationCases[_escrowId];
emit ArbitrationCanceled(_escrowId);
}
/**
* @notice Opens a dispute between a seller and a buyer
* @param _escrowId Id of the Escrow that is being disputed
* @param _openBy Address of the person opening the dispute (buyer or seller)
* @param motive Description of the problem
*/
function _openDispute(uint _escrowId, address _openBy, string memory motive) internal {
require(arbitrationCases[_escrowId].result == ArbitrationResult.UNSOLVED && !arbitrationCases[_escrowId].open,
"Arbitration already solved or has been opened before");
address arbitratorAddress = _getArbitrator(_escrowId);
require(arbitratorAddress != address(0), "Arbitrator is required");
arbitrationCases[_escrowId] = ArbitrationCase({
open: true,
openBy: _openBy,
arbitrator: arbitratorAddress,
result: ArbitrationResult.UNSOLVED,
motive: motive
});
emit ArbitrationRequired(_escrowId);
}
/**
* @notice Set arbitration result in favour of the buyer or seller and transfer funds accordingly
* @param _escrowId Id of the escrow
* @param _result Result of the arbitration
*/
function setArbitrationResult(uint _escrowId, ArbitrationResult _result) external {
require(arbitrationCases[_escrowId].open && arbitrationCases[_escrowId].result == ArbitrationResult.UNSOLVED,
"Case must be open and unsolved");
require(_result != ArbitrationResult.UNSOLVED, "Arbitration does not have result");
require(arbitratorLicenses.isLicenseOwner(msg.sender), "Only arbitrators can invoke this function");
require(arbitrationCases[_escrowId].arbitrator == msg.sender, "Invalid escrow arbitrator");
arbitrationCases[_escrowId].open = false;
arbitrationCases[_escrowId].result = _result;
emit ArbitrationResolved(_escrowId, _result, msg.sender);
if(_result == ArbitrationResult.BUYER){
_solveDispute(_escrowId, true, msg.sender);
} else {
_solveDispute(_escrowId, false, msg.sender);
}
}
}

View File

@ -0,0 +1,230 @@
/* solium-disable security/no-block-members */
pragma solidity ^0.5.8;
import "./License.sol";
/**
* @title ArbitratorLicense
* @dev Contract for management of an arbitrator license
*/
contract ArbitrationLicense is License {
enum RequestStatus {NONE,AWAIT,ACCEPTED,REJECTED,CLOSED}
struct Request{
address seller;
address arbitrator;
RequestStatus status;
uint date;
}
struct ArbitratorLicenseDetails {
uint id;
bool acceptAny;// accept any seller
}
mapping(address => ArbitratorLicenseDetails) public arbitratorlicenseDetails;
mapping(address => mapping(address => bool)) public permissions;
mapping(address => mapping(address => bool)) public blacklist;
mapping(bytes32 => Request) public requests;
event ArbitratorRequested(bytes32 id, address indexed seller, address indexed arbitrator);
event RequestAccepted(bytes32 id, address indexed arbitrator, address indexed seller);
event RequestRejected(bytes32 id, address indexed arbitrator, address indexed seller);
event RequestCanceled(bytes32 id, address indexed arbitrator, address indexed seller);
event BlacklistSeller(address indexed arbitrator, address indexed seller);
event UnBlacklistSeller(address indexed arbitrator, address indexed seller);
/**
* @param _tokenAddress Address of token used to pay for licenses (SNT)
* @param _price Amount of token needed to buy a license
* @param _burnAddress Burn address where the price of the license is sent
*/
constructor(address _tokenAddress, uint256 _price, address _burnAddress)
License(_tokenAddress, _price, _burnAddress)
public {}
/**
* @notice Buy an arbitrator license
*/
function buy() external returns(uint) {
return _buy(msg.sender, false);
}
/**
* @notice Buy an arbitrator license and set if the arbitrator accepts any seller
* @param _acceptAny When set to true, all sellers are accepted by the arbitrator
*/
function buy(bool _acceptAny) external returns(uint) {
return _buy(msg.sender, _acceptAny);
}
/**
* @notice Buy an arbitrator license and set if the arbitrator accepts any seller. Sets the arbitrator as the address in params instead of the sender
* @param _sender Address of the arbitrator
* @param _acceptAny When set to true, all sellers are accepted by the arbitrator
*/
function _buy(address _sender, bool _acceptAny) internal returns (uint id) {
id = _buyFrom(_sender);
arbitratorlicenseDetails[_sender].id = id;
arbitratorlicenseDetails[_sender].acceptAny = _acceptAny;
}
/**
* @notice Change acceptAny parameter for arbitrator
* @param _acceptAny indicates does arbitrator allow to accept any seller/choose sellers
*/
function changeAcceptAny(bool _acceptAny) public {
require(isLicenseOwner(msg.sender), "Message sender should have a valid arbitrator license");
require(arbitratorlicenseDetails[msg.sender].acceptAny != _acceptAny,
"Message sender should pass parameter different from the current one");
arbitratorlicenseDetails[msg.sender].acceptAny = _acceptAny;
}
/**
* @notice Allows arbitrator to accept a seller
* @param _arbitrator address of a licensed arbitrator
*/
function requestArbitrator(address _arbitrator) public {
require(isLicenseOwner(_arbitrator), "Arbitrator should have a valid license");
require(!arbitratorlicenseDetails[_arbitrator].acceptAny, "Arbitrator already accepts all cases");
bytes32 _id = keccak256(abi.encodePacked(_arbitrator, msg.sender));
RequestStatus _status = requests[_id].status;
require(_status != RequestStatus.AWAIT && _status != RequestStatus.ACCEPTED, "Invalid request status");
if(_status == RequestStatus.REJECTED || _status == RequestStatus.CLOSED){
require(requests[_id].date + 3 days < block.timestamp,
"Must wait 3 days before requesting the arbitrator again");
}
requests[_id] = Request({
seller: msg.sender,
arbitrator: _arbitrator,
status: RequestStatus.AWAIT,
date: block.timestamp
});
emit ArbitratorRequested(_id, msg.sender, _arbitrator);
}
/**
* @dev Get Request Id
* @param _arbitrator Arbitrator address
* @param _account Seller account
* @return Request Id
*/
function getId(address _arbitrator, address _account) external pure returns(bytes32){
return keccak256(abi.encodePacked(_arbitrator,_account));
}
/**
* @notice Allows arbitrator to accept a seller's request
* @param _id request id
*/
function acceptRequest(bytes32 _id) public {
require(isLicenseOwner(msg.sender), "Arbitrator should have a valid license");
require(requests[_id].status == RequestStatus.AWAIT, "This request is not pending");
require(!arbitratorlicenseDetails[msg.sender].acceptAny, "Arbitrator already accepts all cases");
require(requests[_id].arbitrator == msg.sender, "Invalid arbitrator");
requests[_id].status = RequestStatus.ACCEPTED;
address _seller = requests[_id].seller;
permissions[msg.sender][_seller] = true;
emit RequestAccepted(_id, msg.sender, requests[_id].seller);
}
/**
* @notice Allows arbitrator to reject a request
* @param _id request id
*/
function rejectRequest(bytes32 _id) public {
require(isLicenseOwner(msg.sender), "Arbitrator should have a valid license");
require(requests[_id].status == RequestStatus.AWAIT || requests[_id].status == RequestStatus.ACCEPTED,
"Invalid request status");
require(!arbitratorlicenseDetails[msg.sender].acceptAny, "Arbitrator accepts all cases");
require(requests[_id].arbitrator == msg.sender, "Invalid arbitrator");
requests[_id].status = RequestStatus.REJECTED;
requests[_id].date = block.timestamp;
address _seller = requests[_id].seller;
permissions[msg.sender][_seller] = false;
emit RequestRejected(_id, msg.sender, requests[_id].seller);
}
/**
* @notice Allows seller to cancel a request
* @param _id request id
*/
function cancelRequest(bytes32 _id) public {
require(requests[_id].seller == msg.sender, "This request id does not belong to the message sender");
require(requests[_id].status == RequestStatus.AWAIT || requests[_id].status == RequestStatus.ACCEPTED, "Invalid request status");
address arbitrator = requests[_id].arbitrator;
requests[_id].status = RequestStatus.CLOSED;
requests[_id].date = block.timestamp;
address _arbitrator = requests[_id].arbitrator;
permissions[_arbitrator][msg.sender] = false;
emit RequestCanceled(_id, arbitrator, requests[_id].seller);
}
/**
* @notice Allows arbitrator to blacklist a seller
* @param _seller Seller address
*/
function blacklistSeller(address _seller) public {
require(isLicenseOwner(msg.sender), "Arbitrator should have a valid license");
blacklist[msg.sender][_seller] = true;
emit BlacklistSeller(msg.sender, _seller);
}
/**
* @notice Allows arbitrator to remove a seller from the blacklist
* @param _seller Seller address
*/
function unBlacklistSeller(address _seller) public {
require(isLicenseOwner(msg.sender), "Arbitrator should have a valid license");
blacklist[msg.sender][_seller] = false;
emit UnBlacklistSeller(msg.sender, _seller);
}
/**
* @notice Checks if Arbitrator permits to use his/her services
* @param _seller sellers's address
* @param _arbitrator arbitrator's address
*/
function isAllowed(address _seller, address _arbitrator) public view returns(bool) {
return (arbitratorlicenseDetails[_arbitrator].acceptAny && !blacklist[_arbitrator][_seller]) || permissions[_arbitrator][_seller];
}
/**
* @notice Support for "approveAndCall". Callable only by `token()`.
* @param _from Who approved.
* @param _amount Amount being approved, need to be equal `price()`.
* @param _token Token being approved, need to be equal `token()`.
* @param _data Abi encoded data with selector of `buy(and)`.
*/
function receiveApproval(address _from, uint256 _amount, address _token, bytes memory _data) public {
require(_amount == price, "Wrong value");
require(_token == address(token), "Wrong token");
require(_token == address(msg.sender), "Wrong call");
require(_data.length == 4, "Wrong data length");
require(_abiDecodeBuy(_data) == bytes4(0xa6f2ae3a), "Wrong method selector"); //bytes4(keccak256("buy()"))
_buy(_from, false);
}
}

View File

@ -0,0 +1,616 @@
/* solium-disable security/no-block-members */
/* solium-disable security/no-inline-assembly */
pragma solidity >=0.5.0 <0.6.0;
import "../common/Pausable.sol";
import "../common/MessageSigned.sol";
import "../token/ERC20Token.sol";
import "./ArbitrationLicense.sol";
import "./License.sol";
import "./MetadataStore.sol";
import "./Fees.sol";
import "./Arbitrable.sol";
import "./IEscrow.sol";
/**
* @title Escrow
* @dev Escrow contract for selling ETH and ERC20 tokens
*/
contract Escrow is IEscrow, Pausable, MessageSigned, Fees, Arbitrable {
EscrowTransaction[] public transactions;
address public relayer;
License public sellerLicenses;
MetadataStore public metadataStore;
event Created(uint indexed offerId, address indexed seller, address indexed buyer, uint escrowId);
event Funded(uint indexed escrowId, uint expirationTime, uint amount);
event Paid(uint indexed escrowId);
event Released(uint indexed escrowId);
event Canceled(uint indexed escrowId);
event Rating(uint indexed offerId, address indexed buyer, uint indexed escrowId, uint rating);
bool internal _initialized;
/**
* @param _relayer EscrowRelay contract address
* @param _sellerLicenses License contract instance address for sellers
* @param _arbitratorLicenses License contract instance address for arbitrators
* @param _metadataStore MetadataStore contract address
* @param _feeDestination Address where the fees are going to be sent
* @param _feeMilliPercent Percentage applied as a fee to each escrow. 1000 == 1%
*/
constructor(
address _relayer,
address _sellerLicenses,
address _arbitratorLicenses,
address _metadataStore,
address payable _feeDestination,
uint _feeMilliPercent)
Fees(_feeDestination, _feeMilliPercent)
Arbitrable(_arbitratorLicenses)
public {
_initialized = true;
relayer = _relayer;
sellerLicenses = License(_sellerLicenses);
metadataStore = MetadataStore(_metadataStore);
}
/**
* @dev Initialize contract (used with proxy). Can only be called once
* @param _relayer EscrowRelay contract address
* @param _sellerLicenses License contract instance address for sellers
* @param _arbitratorLicenses License contract instance address for arbitrators
* @param _metadataStore MetadataStore contract address
* @param _feeDestination Address where the fees are going to be sent
* @param _feeMilliPercent Percentage applied as a fee to each escrow. 1000 == 1%
*/
function init(
address _relayer,
address _sellerLicenses,
address _arbitratorLicenses,
address _metadataStore,
address payable _feeDestination,
uint _feeMilliPercent
) external {
assert(_initialized == false);
_initialized = true;
sellerLicenses = License(_sellerLicenses);
arbitratorLicenses = ArbitrationLicense(_arbitratorLicenses);
metadataStore = MetadataStore(_metadataStore);
relayer = _relayer;
feeDestination = _feeDestination;
feeMilliPercent = _feeMilliPercent;
paused = false;
_setOwner(msg.sender);
}
/**
* @dev Update relayer contract address. Can only be called by the contract owner
* @param _relayer EscrowRelay contract address
*/
function setRelayer(address _relayer) external onlyOwner {
relayer = _relayer;
}
/**
* @dev Update license contract addresses
* @param _sellerLicenses License contract instance address for sellers
* @param _arbitratorLicenses License contract instance address for arbitrators
*/
function setLicenses(address _sellerLicenses, address _arbitratorLicenses) external onlyOwner {
sellerLicenses = License(_sellerLicenses);
arbitratorLicenses = ArbitrationLicense(_arbitratorLicenses);
}
/**
* @dev Update MetadataStore contract address
* @param _metadataStore MetadataStore contract address
*/
function setMetadataStore(address _metadataStore) external onlyOwner {
metadataStore = MetadataStore(_metadataStore);
}
/**
* @dev Escrow creation logic. Requires contract to be unpaused
* @param _buyer Buyer Address
* @param _offerId Offer
* @param _tokenAmount Amount buyer is willing to trade
* @param _assetPrice Indicates the price of the asset in the FIAT of choice
* @return Id of the Escrow
*/
function _createTransaction(
address payable _buyer,
uint _offerId,
uint _tokenAmount,
uint _assetPrice
) internal whenNotPaused returns(uint escrowId)
{
address payable seller;
address payable arbitrator;
bool deleted;
address token;
(token, , , , , , seller, arbitrator, deleted) = metadataStore.offer(_offerId);
require(!deleted, "Offer is not valid");
require(sellerLicenses.isLicenseOwner(seller), "Must be a valid seller to create escrow transactions");
require(seller != _buyer, "Seller and Buyer must be different");
require(arbitrator != _buyer && arbitrator != address(0), "Cannot buy offers where buyer is arbitrator");
require(_tokenAmount != 0 && _assetPrice != 0, "Trade amounts cannot be 0");
escrowId = transactions.length++;
EscrowTransaction storage trx = transactions[escrowId];
trx.offerId = _offerId;
trx.token = token;
trx.buyer = _buyer;
trx.seller = seller;
trx.arbitrator = arbitrator;
trx.tokenAmount = _tokenAmount;
trx.assetPrice = _assetPrice;
emit Created(
_offerId,
seller,
_buyer,
escrowId
);
}
/**
* @notice Create a new escrow
* @param _offerId Offer
* @param _tokenAmount Amount buyer is willing to trade
* @param _assetPrice Indicates the price of the asset in the FIAT of choice
* @param _pubkeyA First coordinate of Status Whisper Public Key
* @param _pubkeyB Second coordinate of Status Whisper Public Key
* @param _location The location on earth
* @param _username The username of the user
* @param _nonce The nonce for the user (from MetadataStore.user_nonce(address))
* @param _signature buyer's signature
* @return Id of the new escrow
* @dev Requires contract to be unpaused.
* The seller needs to be licensed.
*/
function createEscrow(
uint _offerId,
uint _tokenAmount,
uint _assetPrice,
bytes32 _pubkeyA,
bytes32 _pubkeyB,
string memory _location,
string memory _username,
uint _nonce,
bytes memory _signature
) public returns(uint escrowId) {
address payable _buyer = metadataStore.addOrUpdateUser(_signature, _pubkeyA, _pubkeyB, _location, _username, _nonce);
escrowId = _createTransaction(_buyer, _offerId, _tokenAmount, _assetPrice);
}
/**
* @notice Fund a new escrow
* @param _escrowId Id of the escrow
* @dev Requires contract to be unpaused.
* The seller needs to be licensed.
* The expiration time must be at least 10min in the future
* For eth transfer, _amount must be equals to msg.value, for token transfer, requires an allowance and transfer valid for _amount
*/
function fund(uint _escrowId) external payable whenNotPaused {
_fund(msg.sender, _escrowId);
}
/**
* @dev Escrow funding logic
* @param _from Seller address
* @param _escrowId Id of the escrow
* @dev Requires contract to be unpaused.
* The seller needs to be licensed.
* The expiration time must be at least 10min in the future
* For eth transfer, _amount must be equals to msg.value, for token transfer, requires an allowance and transfer valid for _amount
*/
function _fund(address _from, uint _escrowId) internal whenNotPaused {
require(transactions[_escrowId].seller == _from, "Only the seller can invoke this function");
require(transactions[_escrowId].status == EscrowStatus.CREATED, "Invalid escrow status");
require(sellerLicenses.isLicenseOwner(_from), "Must be a valid seller to fund escrow transactions");
transactions[_escrowId].expirationTime = block.timestamp + 5 days;
transactions[_escrowId].status = EscrowStatus.FUNDED;
uint tokenAmount = transactions[_escrowId].tokenAmount;
address token = transactions[_escrowId].token;
_payFee(_from, _escrowId, tokenAmount, token);
emit Funded(_escrowId, block.timestamp + 5 days, tokenAmount);
}
/**
* @notice Create and fund a new escrow, as a seller, once you get a buyer signature
* @param _offerId Offer
* @param _tokenAmount Amount buyer is willing to trade
* @param _assetPrice Indicates the price of the asset in the FIAT of choice
* @param _bPubkeyA First coordinate of Status Whisper Public Key
* @param _bPubkeyB Second coordinate of Status Whisper Public Key
* @param _bLocation The location on earth
* @param _bUsername The username of the user
* @param _bNonce The nonce for the user (from MetadataStore.user_nonce(address))
* @param _bSignature buyer's signature
* @return Id of the new escrow
* @dev Requires contract to be unpaused.
* Restrictions from escrow creation and funding applies
*/
function createAndFund (
uint _offerId,
uint _tokenAmount,
uint _assetPrice,
bytes32 _bPubkeyA,
bytes32 _bPubkeyB,
string memory _bLocation,
string memory _bUsername,
uint _bNonce,
bytes memory _bSignature
) public payable returns(uint escrowId) {
address payable _buyer = metadataStore.addOrUpdateUser(_bSignature, _bPubkeyA, _bPubkeyB, _bLocation, _bUsername, _bNonce);
escrowId = _createTransaction(_buyer, _offerId, _tokenAmount, _assetPrice);
_fund(msg.sender, escrowId);
}
/**
* @dev Buyer marks transaction as paid
* @param _sender Address marking the transaction as paid
* @param _escrowId Id of the escrow
*/
function _pay(address _sender, uint _escrowId) internal {
EscrowTransaction storage trx = transactions[_escrowId];
require(trx.status == EscrowStatus.FUNDED, "Transaction is not funded");
require(trx.expirationTime > block.timestamp, "Transaction already expired");
require(trx.buyer == _sender, "Only the buyer can invoke this function");
trx.status = EscrowStatus.PAID;
emit Paid(_escrowId);
}
/**
* @notice Mark transaction as paid
* @param _escrowId Id of the escrow
* @dev Can only be executed by the buyer
*/
function pay(uint _escrowId) external {
_pay(msg.sender, _escrowId);
}
/**
* @dev Relay function for marking a transaction as paid
* Can only be called by relayer address
* @param _sender Address marking the transaction as paid
* @param _escrowId Id of the escrow
*/
function pay_relayed(address _sender, uint _escrowId) external {
assert(msg.sender == relayer);
_pay(_sender, _escrowId);
}
/**
* @notice Obtain message hash to be signed for marking a transaction as paid
* @param _escrowId Id of the escrow
* @return message hash
* @dev Once message is signed, pass it as _signature of pay(uint256,bytes)
*/
function paySignHash(uint _escrowId) public view returns(bytes32){
return keccak256(
abi.encodePacked(
address(this),
"pay(uint256)",
_escrowId
)
);
}
/**
* @notice Mark transaction as paid (via signed message)
* @param _escrowId Id of the escrow
* @param _signature Signature of the paySignHash result.
* @dev There's a high probability of buyers not having ether to pay for the transaction.
* This allows anyone to relay the transaction.
* TODO: consider deducting funds later on release to pay the relayer (?)
*/
function pay(uint _escrowId, bytes calldata _signature) external {
address sender = _recoverAddress(_getSignHash(paySignHash(_escrowId)), _signature);
_pay(sender, _escrowId);
}
/**
* @dev Release funds to buyer
* @param _escrowId Id of the escrow
* @param _trx EscrowTransaction with data of transaction to be released
* @param _isDispute indicates if the release happened due to a dispute
*/
function _release(uint _escrowId, EscrowTransaction storage _trx, bool _isDispute) internal {
_trx.status = EscrowStatus.RELEASED;
address token = _trx.token;
if(token == address(0)){
_trx.buyer.transfer(_trx.tokenAmount);
} else {
require(ERC20Token(token).transfer(_trx.buyer, _trx.tokenAmount), "Couldn't transfer funds");
}
_releaseFee(_trx.arbitrator, _trx.tokenAmount, token, _isDispute);
emit Released(_escrowId);
}
/**
* @notice Release escrow funds to buyer
* @param _escrowId Id of the escrow
* @dev Requires contract to be unpaused.
* Can only be executed by the seller
* Transaction must not be expired, or previously canceled or released
*/
function release(uint _escrowId) external {
EscrowStatus mStatus = transactions[_escrowId].status;
require(transactions[_escrowId].seller == msg.sender, "Only the seller can invoke this function");
require(mStatus == EscrowStatus.PAID || mStatus == EscrowStatus.FUNDED, "Invalid transaction status");
require(!_isDisputed(_escrowId), "Can't release a transaction that has an arbitration process");
_release(_escrowId, transactions[_escrowId], false);
}
/**
* @dev Cancel an escrow operation
* @param _escrowId Id of the escrow
* @notice Requires contract to be unpaused.
* Can only be executed by the seller
* Transaction must be expired, or previously canceled or released
*/
function cancel(uint _escrowId) external whenNotPaused {
EscrowTransaction storage trx = transactions[_escrowId];
EscrowStatus mStatus = trx.status;
require(mStatus == EscrowStatus.FUNDED || mStatus == EscrowStatus.CREATED,
"Only transactions in created or funded state can be canceled");
require(trx.buyer == msg.sender || trx.seller == msg.sender, "Only participants can invoke this function");
if(mStatus == EscrowStatus.FUNDED){
if(msg.sender == trx.seller){
require(trx.expirationTime < block.timestamp, "Can only be canceled after expiration");
}
}
_cancel(_escrowId, trx, false);
}
// Same as cancel, but relayed by a contract so we get the sender as param
function cancel_relayed(address _sender, uint _escrowId) external {
assert(msg.sender == relayer);
EscrowTransaction storage trx = transactions[_escrowId];
EscrowStatus mStatus = trx.status;
require(trx.buyer == _sender, "Only the buyer can invoke this function");
require(mStatus == EscrowStatus.FUNDED || mStatus == EscrowStatus.CREATED,
"Only transactions in created or funded state can be canceled");
_cancel(_escrowId, trx, false);
}
/**
* @dev Cancel transaction and send funds back to seller
* @param _escrowId Id of the escrow
* @param trx EscrowTransaction with details of transaction to be marked as canceled
*/
function _cancel(uint _escrowId, EscrowTransaction storage trx, bool isDispute) internal {
if(trx.status == EscrowStatus.FUNDED){
address token = trx.token;
uint amount = trx.tokenAmount;
if (!isDispute) {
amount += _getValueOffMillipercent(trx.tokenAmount, feeMilliPercent);
}
if(token == address(0)){
trx.seller.transfer(amount);
} else {
ERC20Token erc20token = ERC20Token(token);
require(erc20token.transfer(trx.seller, amount), "Transfer failed");
}
}
trx.status = EscrowStatus.CANCELED;
emit Canceled(_escrowId);
}
/**
* @notice Rates a transaction
* @param _escrowId Id of the escrow
* @param _rate rating of the transaction from 1 to 5
* @dev Can only be executed by the buyer
* Transaction must released
*/
function rateTransaction(uint _escrowId, uint _rate) external {
require(_rate >= 1, "Rating needs to be at least 1");
require(_rate <= 5, "Rating needs to be at less than or equal to 5");
EscrowTransaction storage trx = transactions[_escrowId];
require(trx.rating == 0, "Transaction already rated");
require(trx.status == EscrowStatus.RELEASED || hadDispute(_escrowId), "Transaction not completed yet");
require(trx.buyer == msg.sender, "Only the buyer can invoke this function");
trx.rating = _rate;
emit Rating(trx.offerId, trx.buyer, _escrowId, _rate);
}
/**
* @notice Returns basic trade informations (buyer address, seller address, token address and token amount)
* @param _escrowId Id of the escrow
*/
function getBasicTradeData(uint _escrowId)
external
view
returns(address payable buyer, address payable seller, address token, uint tokenAmount) {
buyer = transactions[_escrowId].buyer;
seller = transactions[_escrowId].seller;
tokenAmount = transactions[_escrowId].tokenAmount;
token = transactions[_escrowId].token;
return (buyer, seller, token, tokenAmount);
}
/**
* @notice Open case as a buyer or seller for arbitration
* @param _escrowId Id of the escrow
* @param _motive Motive for opening the dispute
*/
function openCase(uint _escrowId, string calldata _motive) external {
EscrowTransaction storage trx = transactions[_escrowId];
require(!isDisputed(_escrowId), "Case already exist");
require(trx.buyer == msg.sender || trx.seller == msg.sender, "Only participants can invoke this function");
require(trx.status == EscrowStatus.PAID, "Cases can only be open for paid transactions");
_openDispute(_escrowId, msg.sender, _motive);
}
/**
* @dev Open case via relayer
* @param _sender Address initiating the relayed transaction
* @param _escrowId Id of the escrow
* @param _motive Motive for opening the dispute
*/
function openCase_relayed(address _sender, uint256 _escrowId, string calldata _motive) external {
assert(msg.sender == relayer);
EscrowTransaction storage trx = transactions[_escrowId];
require(!isDisputed(_escrowId), "Case already exist");
require(trx.buyer == _sender, "Only the buyer can invoke this function");
require(trx.status == EscrowStatus.PAID, "Cases can only be open for paid transactions");
_openDispute(_escrowId, _sender, _motive);
}
/**
* @notice Open case as a buyer or seller for arbitration via a relay account
* @param _escrowId Id of the escrow
* @param _motive Motive for opening the dispute
* @param _signature Signed message result of openCaseSignHash(uint256)
* @dev Consider opening a dispute in aragon court.
*/
function openCase(uint _escrowId, string calldata _motive, bytes calldata _signature) external {
EscrowTransaction storage trx = transactions[_escrowId];
require(!isDisputed(_escrowId), "Case already exist");
require(trx.status == EscrowStatus.PAID, "Cases can only be open for paid transactions");
address senderAddress = _recoverAddress(_getSignHash(openCaseSignHash(_escrowId, _motive)), _signature);
require(trx.buyer == senderAddress || trx.seller == senderAddress, "Only participants can invoke this function");
_openDispute(_escrowId, msg.sender, _motive);
}
/**
* @notice Set arbitration result in favour of the buyer or seller and transfer funds accordingly
* @param _escrowId Id of the escrow
* @param _releaseFunds Release funds to buyer or cancel escrow
* @param _arbitrator Arbitrator address
*/
function _solveDispute(uint _escrowId, bool _releaseFunds, address _arbitrator) internal {
EscrowTransaction storage trx = transactions[_escrowId];
require(trx.buyer != _arbitrator && trx.seller != _arbitrator, "Arbitrator cannot be part of transaction");
if(_releaseFunds){
_release(_escrowId, trx, true);
} else {
_cancel(_escrowId, trx, true);
_releaseFee(trx.arbitrator, trx.tokenAmount, trx.token, true);
}
}
/**
* @notice Get arbitrator
* @param _escrowId Id of the escrow
* @return Arbitrator address
*/
function _getArbitrator(uint _escrowId) internal view returns(address) {
return transactions[_escrowId].arbitrator;
}
/**
* @notice Obtain message hash to be signed for opening a case
* @param _escrowId Id of the escrow
* @return message hash
* @dev Once message is signed, pass it as _signature of openCase(uint256,bytes)
*/
function openCaseSignHash(uint _escrowId, string memory motive) public view returns(bytes32){
return keccak256(
abi.encodePacked(
address(this),
"openCase(uint256)",
_escrowId,
motive
)
);
}
/**
* @dev Support for "approveAndCall". Callable only by the fee token.
* @param _from Who approved.
* @param _amount Amount being approved, need to be equal `getPrice()`.
* @param _token Token being approved, need to be equal `token()`.
* @param _data Abi encoded data with selector of `register(bytes32,address,bytes32,bytes32)`.
*/
function receiveApproval(address _from, uint256 _amount, address _token, bytes memory _data) public {
require(_token == address(msg.sender), "Wrong call");
require(_data.length == 36, "Wrong data length");
bytes4 sig;
uint256 escrowId;
(sig, escrowId) = _abiDecodeFundCall(_data);
if (sig == bytes4(0xca1d209d)){ // fund(uint256)
uint tokenAmount = transactions[escrowId].tokenAmount;
require(_amount == tokenAmount + _getValueOffMillipercent(tokenAmount, feeMilliPercent), "Invalid amount");
_fund(_from, escrowId);
} else {
revert("Wrong method selector");
}
}
/**
* @dev Decodes abi encoded data with selector for "fund".
* @param _data Abi encoded data.
* @return Decoded registry call.
*/
function _abiDecodeFundCall(bytes memory _data) internal pure returns (bytes4 sig, uint256 escrowId) {
assembly {
sig := mload(add(_data, add(0x20, 0)))
escrowId := mload(add(_data, 36))
}
}
/**
* @dev Withdraws funds to the sellers in case of emergency
* Requires contract to be paused.
* Can be executed by anyone
* Transaction must not be canceled or released
* @param _escrowId Id of the Escrow
*/
function withdraw_emergency(uint _escrowId) external whenPaused {
EscrowTransaction storage trx = transactions[_escrowId];
require(trx.status == EscrowStatus.FUNDED, "Cannot withdraw from escrow in a stage different from FUNDED. Open a case");
_cancel(_escrowId, trx, false);
}
}

View File

@ -0,0 +1,243 @@
/* solium-disable security/no-block-members */
/* solium-disable security/no-inline-assembly */
/* solium-disable no-empty-blocks */
pragma solidity >=0.5.0 <0.6.0;
import "./IEscrow.sol";
import "./MetadataStore.sol";
import "../common/Ownable.sol";
import "tabookey-gasless/contracts/RelayRecipient.sol";
/**
* @title Escrow Relay (Gas Station Network)
*/
contract EscrowRelay is RelayRecipient, Ownable {
MetadataStore public metadataStore;
IEscrow public escrow;
address public snt;
mapping(address => uint) public lastActivity;
bytes4 constant CREATE_SIGNATURE = bytes4(keccak256("createEscrow(uint256,uint256,uint256,bytes32,bytes32,string,string,uint256,bytes)"));
bytes4 constant PAY_SIGNATURE = bytes4(keccak256("pay(uint256)"));
bytes4 constant CANCEL_SIGNATURE = bytes4(keccak256("cancel(uint256)"));
bytes4 constant OPEN_CASE_SIGNATURE = bytes4(keccak256("openCase(uint256,string)"));
uint256 constant OK = 0;
uint256 constant ERROR_ENOUGH_BALANCE = 11;
uint256 constant ERROR_INVALID_ASSET = 12;
uint256 constant ERROR_TRX_TOO_SOON = 13;
uint256 constant ERROR_INVALID_BUYER = 14;
uint256 constant ERROR_GAS_PRICE = 15;
uint256 constant ERROR = 99;
/**
* @param _metadataStore Metadata Store Address
* @param _escrow IEscrow Instance Address
* @param _snt SNT address
*/
constructor(address _metadataStore, address _escrow, address _snt) public {
metadataStore = MetadataStore(_metadataStore);
escrow = IEscrow(_escrow);
snt = _snt;
}
/**
* @notice Set metadata store address
* @dev Only contract owner can execute this function
* @param _metadataStore New metadata store address
*/
function setMetadataStore(address _metadataStore) external onlyOwner {
metadataStore = MetadataStore(_metadataStore);
}
/**
* @notice Set escrow address
* @dev Only contract owner can execute this function
* @param _escrow New escrow address
*/
function setEscrow(address _escrow) external onlyOwner {
escrow = IEscrow(_escrow);
}
/**
* @notice Set gas station network hub address
* @dev Only contract owner can execute this function
* @param _relayHub New relay hub address
*/
function setRelayHubAddress(address _relayHub) external onlyOwner {
setRelayHub(IRelayHub(_relayHub));
}
/**
* @notice Determine if the timeout for relaying a create/cancel transaction has passed
* @param _account Account to verify
* @return bool
*/
function canCreateOrCancel(address _account) external view returns(bool) {
return (lastActivity[_account] + 15 minutes) < block.timestamp;
}
/**
* @notice Create a new escrow
* @param _offerId Offer
* @param _tokenAmount Amount buyer is willing to trade
* @param _assetPrice Indicates the price of the asset in the FIAT of choice
* @param _pubkeyA First coordinate of Status Whisper Public Key
* @param _pubkeyB Second coordinate of Status Whisper Public Key
* @param _location The location on earth
* @param _username The username of the user
* @param _nonce buyer's nonce
* @param _signature buyer's signature
*/
function createEscrow(
uint _offerId,
uint _tokenAmount,
uint _assetPrice,
bytes32 _pubkeyA,
bytes32 _pubkeyB,
string memory _location,
string memory _username,
uint _nonce,
bytes memory _signature
) public returns (uint escrowId) {
lastActivity[getSender()] = block.timestamp;
escrowId = escrow.createEscrow(
_offerId,
_tokenAmount,
_assetPrice,
_pubkeyA,
_pubkeyB,
_location,
_username,
_nonce,
_signature
);
}
/**
* @notice Mark transaction as paid
* @param _escrowId Escrow to mark as paid
*/
function pay(uint _escrowId) external {
address sender = getSender();
escrow.pay_relayed(sender, _escrowId);
}
/**
* @notice Cancel an escrow
* @param _escrowId Escrow to cancel
*/
function cancel(uint _escrowId) external {
address sender = getSender();
lastActivity[sender] = block.timestamp;
escrow.cancel_relayed(sender, _escrowId);
}
/**
* @notice Open a dispute
* @param _escrowId Escrow to open a dispute
* @param _motive Motive a dispute is being opened
*/
function openCase(uint _escrowId, string memory _motive) public {
address sender = getSender();
escrow.openCase_relayed(sender, _escrowId, _motive);
}
// =======================1=================================================
// Gas station network
/**
* @notice Function returning if we accept or not the relayed call (do we pay or not for the gas)
* @param from Address of the buyer getting a free transaction
* @param encodedFunction Function that will be called on the Escrow contract
* @param gasPrice Gas price
* @dev relay and transaction_fee are useless in our relay workflow
*/
function acceptRelayedCall(
address /* relay */,
address from,
bytes calldata encodedFunction,
uint256 /* transactionFee */,
uint256 gasPrice,
uint256 /* gasLimit */,
uint256 /* nonce */,
bytes calldata /* approvalData */,
uint256 /* maxPossibleCharge */
) external view returns (uint256, bytes memory)
{
bytes memory abiEncodedFunc = encodedFunction; // Call data elements cannot be accessed directly
bytes4 fSign;
uint dataValue;
assembly {
fSign := mload(add(abiEncodedFunc, add(0x20, 0)))
dataValue := mload(add(abiEncodedFunc, 36))
}
return (_evaluateConditionsToRelay(from, gasPrice, fSign, dataValue), "");
}
/**
* @dev Evaluates if the sender conditions are valid for relaying a escrow transaction
* @param from Sender
* @param gasPrice Gas Price
* @param functionSignature Function Signature
* @param dataValue Represents the escrowId or offerId depending on the function being called
*/
function _evaluateConditionsToRelay(address from, uint gasPrice, bytes4 functionSignature, uint dataValue) internal view returns (uint256) {
address token;
if(from.balance > 600000 * gasPrice) return ERROR_ENOUGH_BALANCE;
if(gasPrice > 20000000000) return ERROR_GAS_PRICE; // 20Gwei
if(functionSignature == PAY_SIGNATURE || functionSignature == CANCEL_SIGNATURE || functionSignature == OPEN_CASE_SIGNATURE){
address payable buyer;
(buyer, , token, ) = escrow.getBasicTradeData(dataValue);
if(buyer != from) return ERROR_INVALID_BUYER;
if(token != snt && token != address(0)) return ERROR_INVALID_ASSET;
if(functionSignature == CANCEL_SIGNATURE){ // Allow activity after 15min have passed
if((lastActivity[from] + 15 minutes) > block.timestamp) return ERROR_TRX_TOO_SOON;
}
return OK;
} else if(functionSignature == CREATE_SIGNATURE) {
token = metadataStore.getAsset(dataValue);
if(token != snt && token != address(0)) return ERROR_INVALID_ASSET;
// Allow activity after 15 min have passed
if((lastActivity[from] + 15 minutes) > block.timestamp) return ERROR_TRX_TOO_SOON;
return OK;
}
return ERROR;
}
/**
* @notice Function executed before the relay. Unused by us
*/
function preRelayedCall(bytes calldata) external returns (bytes32){
// nothing to be done pre-call.
// still, we must implement this method.
}
/**
* @notice Function executed after the relay. Unused by us
*/
function postRelayedCall(
bytes calldata /*context*/,
bool /*success*/,
uint /*actualCharge*/,
bytes32 /*preRetVal*/) external {
// nothing to be done post-call.
// still, we must implement this method.
}
}

View File

@ -0,0 +1,117 @@
pragma solidity >=0.5.0 <0.6.0;
import "../token/ERC20Token.sol";
import "../common/Ownable.sol";
/**
* @title Fee utilities
* @dev Fee registry, payment and withdraw utilities.
*/
contract Fees is Ownable {
address payable public feeDestination;
uint public feeMilliPercent;
mapping(address => uint) public feeTokenBalances;
mapping(uint => bool) public feePaid;
event FeeDestinationChanged(address payable);
event FeeMilliPercentChanged(uint amount);
event FeesWithdrawn(uint amount, address token);
/**
* @param _feeDestination Address to send the fees once withdraw is called
* @param _feeMilliPercent Millipercent for the fee off teh amount sold
*/
constructor(address payable _feeDestination, uint _feeMilliPercent) public {
feeDestination = _feeDestination;
feeMilliPercent = _feeMilliPercent;
}
/**
* @dev Set Fee Destination Address.
* Can only be called by the owner of the contract
* @param _addr New address
*/
function setFeeDestinationAddress(address payable _addr) external onlyOwner {
feeDestination = _addr;
emit FeeDestinationChanged(_addr);
}
/**
* @dev Set Fee Amount
* Can only be called by the owner of the contract
* @param _feeMilliPercent New millipercent
*/
function setFeeAmount(uint _feeMilliPercent) external onlyOwner {
feeMilliPercent = _feeMilliPercent;
emit FeeMilliPercentChanged(_feeMilliPercent);
}
/**
* @dev Release fee to fee destination and arbitrator
* @param _arbitrator Arbitrator address to transfer fee to
* @param _value Value sold in the escrow
* @param _isDispute Boolean telling if it was from a dispute. With a dispute, the arbitrator gets more
*/
function _releaseFee(address payable _arbitrator, uint _value, address _tokenAddress, bool _isDispute) internal {
uint _milliPercentToArbitrator;
if (_isDispute) {
_milliPercentToArbitrator = 100000; // 100%
} else {
_milliPercentToArbitrator = 10000; // 10%
}
uint feeAmount = _getValueOffMillipercent(_value, feeMilliPercent);
uint arbitratorValue = _getValueOffMillipercent(feeAmount, _milliPercentToArbitrator);
uint destinationValue = feeAmount - arbitratorValue;
if (_tokenAddress != address(0)) {
ERC20Token tokenToPay = ERC20Token(_tokenAddress);
require(tokenToPay.transfer(_arbitrator, arbitratorValue), "Unsuccessful token transfer - arbitrator");
if (destinationValue > 0) {
require(tokenToPay.transfer(feeDestination, destinationValue), "Unsuccessful token transfer - destination");
}
} else {
_arbitrator.transfer(arbitratorValue);
if (destinationValue > 0) {
feeDestination.transfer(destinationValue);
}
}
}
/**
* @dev Calculate fee of an amount based in milliPercent
* @param _value Value to obtain the fee
* @param _milliPercent parameter to calculate the fee
* @return Fee amount for _value
*/
function _getValueOffMillipercent(uint _value, uint _milliPercent) internal pure returns(uint) {
// To get the factor, we divide like 100 like a normal percent, but we multiply that by 1000 because it's a milliPercent
// Eg: 1 % = 1000 millipercent => Factor is 0.01, so 1000 divided by 100 * 1000
return (_value * _milliPercent) / (100 * 1000);
}
/**
* @dev Pay fees for a transaction or element id
* This will only transfer funds if the fee has not been paid
* @param _from Address from where the fees are being extracted
* @param _id Escrow id or element identifier to mark as paid
* @param _value Value sold in the escrow
* @param _tokenAddress Address of the token sold in the escrow
*/
function _payFee(address _from, uint _id, uint _value, address _tokenAddress) internal {
if (feePaid[_id]) return;
feePaid[_id] = true;
uint feeAmount = _getValueOffMillipercent(_value, feeMilliPercent);
feeTokenBalances[_tokenAddress] += feeAmount;
if (_tokenAddress != address(0)) {
require(msg.value == 0, "Cannot send ETH with token address different from 0");
ERC20Token tokenToPay = ERC20Token(_tokenAddress);
require(tokenToPay.transferFrom(_from, address(this), feeAmount + _value), "Unsuccessful token transfer");
} else {
require(msg.value == (_value + feeAmount), "ETH amount is required");
}
}
}

View File

@ -0,0 +1,46 @@
pragma solidity >=0.5.0 <0.6.0;
contract IEscrow {
enum EscrowStatus {CREATED, FUNDED, PAID, RELEASED, CANCELED}
struct EscrowTransaction {
uint256 offerId;
address token;
uint256 tokenAmount;
uint256 expirationTime;
uint256 rating;
uint256 assetPrice;
address payable buyer;
address payable seller;
address payable arbitrator;
EscrowStatus status;
}
function createEscrow(
uint _offerId,
uint _tokenAmount,
uint _assetPrice,
bytes32 _pubkeyA,
bytes32 _pubkeyB,
string memory _location,
string memory _username,
uint _nonce,
bytes memory _signature
) public returns(uint escrowId);
function pay(uint _escrowId) external;
function pay_relayed(address _sender, uint _escrowId) external;
function cancel(uint _escrowId) external;
function cancel_relayed(address _sender, uint _escrowId) external;
function openCase(uint _escrowId, string calldata _motive) external;
function openCase_relayed(address _sender, uint256 _escrowId, string calldata _motive) external;
function getBasicTradeData(uint _escrowId) external view returns(address payable buyer, address payable seller, address token, uint tokenAmount);
}

View File

@ -0,0 +1,152 @@
/* solium-disable security/no-block-members */
/* solium-disable security/no-inline-assembly */
pragma solidity >=0.5.0 <0.6.0;
import "../common/Ownable.sol";
import "../token/ERC20Token.sol";
import "../token/ApproveAndCallFallBack.sol";
/**
* @title License
* @dev Contract for buying a license
*/
contract License is Ownable, ApproveAndCallFallBack {
uint256 public price;
ERC20Token token;
address burnAddress;
struct LicenseDetails {
uint price;
uint creationTime;
}
address[] public licenseOwners;
mapping(address => uint) public idxLicenseOwners;
mapping(address => LicenseDetails) public licenseDetails;
event Bought(address buyer, uint256 price);
event PriceChanged(uint256 _price);
bool internal _initialized;
/**
* @param _tokenAddress Address of token used to pay for licenses (SNT)
* @param _price Price of the licenses
* @param _burnAddress Address where the license fee is going to be sent
*/
constructor(address _tokenAddress, uint256 _price, address _burnAddress) public {
init(_tokenAddress, _price, _burnAddress);
}
/**
* @dev Initialize contract (used with proxy). Can only be called once
* @param _tokenAddress Address of token used to pay for licenses (SNT)
* @param _price Price of the licenses
* @param _burnAddress Address where the license fee is going to be sent
*/
function init(
address _tokenAddress,
uint256 _price,
address _burnAddress
) public {
assert(_initialized == false);
_initialized = true;
price = _price;
token = ERC20Token(_tokenAddress);
burnAddress = _burnAddress;
_setOwner(msg.sender);
}
/**
* @notice Check if the address already owns a license
* @param _address The address to check
* @return bool
*/
function isLicenseOwner(address _address) public view returns (bool) {
return licenseDetails[_address].price != 0 && licenseDetails[_address].creationTime != 0;
}
/**
* @notice Buy a license
* @dev Requires value to be equal to the price of the license.
* The msg.sender must not already own a license.
*/
function buy() external returns(uint) {
uint id = _buyFrom(msg.sender);
return id;
}
/**
* @notice Buy a license
* @dev Requires value to be equal to the price of the license.
* The _owner must not already own a license.
*/
function _buyFrom(address _licenseOwner) internal returns(uint) {
require(licenseDetails[_licenseOwner].creationTime == 0, "License already bought");
licenseDetails[_licenseOwner] = LicenseDetails({
price: price,
creationTime: block.timestamp
});
uint idx = licenseOwners.push(_licenseOwner);
idxLicenseOwners[_licenseOwner] = idx;
emit Bought(_licenseOwner, price);
require(token.transferFrom(_licenseOwner, burnAddress, price), "Unsuccessful token transfer");
return idx;
}
/**
* @notice Set the license price
* @param _price The new price of the license
* @dev Only the owner of the contract can perform this action
*/
function setPrice(uint256 _price) external onlyOwner {
price = _price;
emit PriceChanged(_price);
}
/**
* @dev Get number of license owners
* @return uint
*/
function getNumLicenseOwners() external view returns (uint256) {
return licenseOwners.length;
}
/**
* @notice Support for "approveAndCall". Callable only by `token()`.
* @param _from Who approved.
* @param _amount Amount being approved, need to be equal `price()`.
* @param _token Token being approved, need to be equal `token()`.
* @param _data Abi encoded data with selector of `buy(and)`.
*/
function receiveApproval(address _from, uint256 _amount, address _token, bytes memory _data) public {
require(_amount == price, "Wrong value");
require(_token == address(token), "Wrong token");
require(_token == address(msg.sender), "Wrong call");
require(_data.length == 4, "Wrong data length");
require(_abiDecodeBuy(_data) == bytes4(0xa6f2ae3a), "Wrong method selector"); //bytes4(keccak256("buy()"))
_buyFrom(_from);
}
/**
* @dev Decodes abi encoded data with selector for "buy()".
* @param _data Abi encoded data.
* @return Decoded registry call.
*/
function _abiDecodeBuy(bytes memory _data) internal pure returns(bytes4 sig) {
assembly {
sig := mload(add(_data, add(0x20, 0)))
}
}
}

View File

@ -0,0 +1,382 @@
pragma solidity >=0.5.0 <0.6.0;
import "./License.sol";
import "./ArbitrationLicense.sol";
import "../common/MessageSigned.sol";
import "../common/Ownable.sol";
/**
* @title MetadataStore
* @dev User and offers registry
*/
contract MetadataStore is MessageSigned {
struct User {
bytes32 pubkeyA;
bytes32 pubkeyB;
string location;
string username;
}
struct Offer {
int8 margin;
uint[] paymentMethods;
uint limitL;
uint limitU;
address asset;
string currency;
address payable owner;
address payable arbitrator;
bool deleted;
}
License public sellingLicenses;
ArbitrationLicense public arbitrationLicenses;
mapping(address => User) public users;
mapping(address => uint) public user_nonce;
Offer[] public offers;
mapping(address => uint256[]) public addressToOffers;
mapping(address => mapping (uint256 => bool)) public offerWhitelist;
bool internal _initialized;
event OfferAdded(
address owner,
uint256 offerId,
address asset,
string location,
string currency,
string username,
uint[] paymentMethods,
uint limitL,
uint limitU,
int8 margin
);
event OfferRemoved(address owner, uint256 offerId);
/**
* @param _sellingLicenses Sellers licenses contract address
* @param _arbitrationLicenses Arbitrators licenses contract address
*/
constructor(address _sellingLicenses, address _arbitrationLicenses) public {
init(_sellingLicenses, _arbitrationLicenses);
}
/**
* @dev Initialize contract (used with proxy). Can only be called once
* @param _sellingLicenses Sellers licenses contract address
* @param _arbitrationLicenses Arbitrators licenses contract address
*/
function init(
address _sellingLicenses,
address _arbitrationLicenses
) public {
assert(_initialized == false);
_initialized = true;
sellingLicenses = License(_sellingLicenses);
arbitrationLicenses = ArbitrationLicense(_arbitrationLicenses);
}
/**
* @dev Get datahash to be signed
* @param _username Username
* @param _pubkeyA First coordinate of Status Whisper Public Key
* @param _pubkeyB Second coordinate of Status Whisper Public Key
* @param _nonce Nonce value (obtained from user_nonce)
* @return bytes32 to sign
*/
function _dataHash(string memory _username, bytes32 _pubkeyA, bytes32 _pubkeyB, uint _nonce) internal view returns (bytes32) {
return keccak256(abi.encodePacked(address(this), _username, _pubkeyA, _pubkeyB, _nonce));
}
/**
* @notice Get datahash to be signed
* @param _username Username
* @param _pubkeyA First coordinate of Status Whisper Public Key
* @param _pubkeyB Second coordinate of Status Whisper Public Key
* @return bytes32 to sign
*/
function getDataHash(string calldata _username, bytes32 _pubkeyA, bytes32 _pubkeyB) external view returns (bytes32) {
return _dataHash(_username, _pubkeyA, _pubkeyB, user_nonce[msg.sender]);
}
/**
* @dev Get signer address from signature. This uses the signature parameters to validate the signature
* @param _username Status username
* @param _pubkeyA First coordinate of Status Whisper Public Key
* @param _pubkeyB Second coordinate of Status Whisper Public Key
* @param _nonce User nonce
* @param _signature Signature obtained from the previous parameters
* @return Signing user address
*/
function _getSigner(
string memory _username,
bytes32 _pubkeyA,
bytes32 _pubkeyB,
uint _nonce,
bytes memory _signature
) internal view returns(address) {
bytes32 signHash = _getSignHash(_dataHash(_username, _pubkeyA, _pubkeyB, _nonce));
return _recoverAddress(signHash, _signature);
}
/**
* @notice Get signer address from signature
* @param _username Status username
* @param _pubkeyA First coordinate of Status Whisper Public Key
* @param _pubkeyB Second coordinate of Status Whisper Public Key
* @param _nonce User nonce
* @param _signature Signature obtained from the previous parameters
* @return Signing user address
*/
function getMessageSigner(
string calldata _username,
bytes32 _pubkeyA,
bytes32 _pubkeyB,
uint _nonce,
bytes calldata _signature
) external view returns(address) {
return _getSigner(_username, _pubkeyA, _pubkeyB, _nonce, _signature);
}
/**
* @dev Adds or updates user information
* @param _user User address to update
* @param _pubkeyA First coordinate of Status Whisper Public Key
* @param _pubkeyB Second coordinate of Status Whisper Public Key
* @param _location New location
* @param _username New status username
*/
function _addOrUpdateUser(
address _user,
bytes32 _pubkeyA,
bytes32 _pubkeyB,
string memory _location,
string memory _username
) internal {
User storage u = users[_user];
u.pubkeyA = _pubkeyA;
u.pubkeyB = _pubkeyB;
u.location = _location;
u.username = _username;
}
/**
* @notice Adds or updates user information via signature
* @param _signature Signature
* @param _pubkeyA First coordinate of Status Whisper Public Key
* @param _pubkeyB Second coordinate of Status Whisper Public Key
* @param _location New location
* @param _username New status username
* @return Signing user address
*/
function addOrUpdateUser(
bytes calldata _signature,
bytes32 _pubkeyA,
bytes32 _pubkeyB,
string calldata _location,
string calldata _username,
uint _nonce
) external returns(address payable _user) {
_user = address(uint160(_getSigner(_username, _pubkeyA, _pubkeyB, _nonce, _signature)));
require(_nonce == user_nonce[_user], "Invalid nonce");
user_nonce[_user]++;
_addOrUpdateUser(_user, _pubkeyA, _pubkeyB, _location, _username);
return _user;
}
/**
* @notice Adds or updates user information
* @param _pubkeyA First coordinate of Status Whisper Public Key
* @param _pubkeyB Second coordinate of Status Whisper Public Key
* @param _location New location
* @param _username New status username
* @return Signing user address
*/
function addOrUpdateUser(
bytes32 _pubkeyA,
bytes32 _pubkeyB,
string calldata _location,
string calldata _username
) external {
_addOrUpdateUser(msg.sender, _pubkeyA, _pubkeyB, _location, _username);
}
/**
* @dev Add a new offer with a new user if needed to the list
* @param _asset The address of the erc20 to exchange, pass 0x0 for Eth
* @param _pubkeyA First coordinate of Status Whisper Public Key
* @param _pubkeyB Second coordinate of Status Whisper Public Key
* @param _location The location on earth
* @param _currency The currency the user want to receive (USD, EUR...)
* @param _username The username of the user
* @param _paymentMethods The list of the payment methods the user accept
* @param _limitL Lower limit accepted
* @param _limitU Upper limit accepted
* @param _margin The margin for the user from 0 to 100
* @param _arbitrator The arbitrator used by the offer
*/
function addOffer(
address _asset,
bytes32 _pubkeyA,
bytes32 _pubkeyB,
string memory _location,
string memory _currency,
string memory _username,
uint[] memory _paymentMethods,
uint _limitL,
uint _limitU,
int8 _margin,
address payable _arbitrator
) public {
require(sellingLicenses.isLicenseOwner(msg.sender), "Not a license owner");
require(arbitrationLicenses.isAllowed(msg.sender, _arbitrator), "Arbitrator does not allow this transaction");
require(_margin <= 100, "Margin too high");
require(_margin >= -100, "Margin too low");
require(_limitL <= _limitU, "Invalid limits");
require(msg.sender != _arbitrator, "Cannot arbitrate own offers");
_addOrUpdateUser(
msg.sender,
_pubkeyA,
_pubkeyB,
_location,
_username
);
Offer memory newOffer = Offer(
_margin,
_paymentMethods,
_limitL,
_limitU,
_asset,
_currency,
msg.sender,
_arbitrator,
false
);
uint256 offerId = offers.push(newOffer) - 1;
offerWhitelist[msg.sender][offerId] = true;
addressToOffers[msg.sender].push(offerId);
emit OfferAdded(
msg.sender,
offerId,
_asset,
_location,
_currency,
_username,
_paymentMethods,
_limitL,
_limitU,
_margin);
}
/**
* @notice Remove user offer
* @dev Removed offers are marked as deleted instead of being deleted
* @param _offerId Id of the offer to remove
*/
function removeOffer(uint256 _offerId) external {
require(offerWhitelist[msg.sender][_offerId], "Offer does not exist");
offers[_offerId].deleted = true;
offerWhitelist[msg.sender][_offerId] = false;
emit OfferRemoved(msg.sender, _offerId);
}
/**
* @notice Get the offer by Id
* @dev normally we'd access the offers array, but it would not return the payment methods
* @param _id Offer id
* @return Offer data (see Offer struct)
*/
function offer(uint256 _id) external view returns (
address asset,
string memory currency,
int8 margin,
uint[] memory paymentMethods,
uint limitL,
uint limitH,
address payable owner,
address payable arbitrator,
bool deleted
) {
Offer memory theOffer = offers[_id];
// In case arbitrator rejects the seller
address payable offerArbitrator = theOffer.arbitrator;
if(!arbitrationLicenses.isAllowed(theOffer.owner, offerArbitrator)){
offerArbitrator = address(0);
}
return (
theOffer.asset,
theOffer.currency,
theOffer.margin,
theOffer.paymentMethods,
theOffer.limitL,
theOffer.limitU,
theOffer.owner,
offerArbitrator,
theOffer.deleted
);
}
/**
* @notice Get the offer's owner by Id
* @dev Helper function
* @param _id Offer id
* @return Seller address
*/
function getOfferOwner(uint256 _id) external view returns (address payable) {
return (offers[_id].owner);
}
/**
* @notice Get the offer's asset by Id
* @dev Helper function
* @param _id Offer id
* @return Token address used in the offer
*/
function getAsset(uint256 _id) external view returns (address) {
return (offers[_id].asset);
}
/**
* @notice Get the offer's arbitrator by Id
* @dev Helper function
* @param _id Offer id
* @return Arbitrator address
*/
function getArbitrator(uint256 _id) external view returns (address payable) {
return (offers[_id].arbitrator);
}
/**
* @notice Get the size of the offers
* @return Number of offers stored in the contract
*/
function offersSize() external view returns (uint256) {
return offers.length;
}
/**
* @notice Get all the offer ids of the address in params
* @param _address Address of the offers
* @return Array of offer ids for a specific address
*/
function getOfferIds(address _address) external view returns (uint256[] memory) {
return addressToOffers[_address];
}
}

View File

@ -0,0 +1,149 @@
pragma solidity ^0.5.7;
import "../token/ERC20Token.sol";
/**
* @notice Standard ERC20 token for tests
*/
contract StandardToken is ERC20Token {
uint256 private supply;
mapping (address => uint256) balances;
mapping (address => mapping (address => uint256)) allowed;
constructor() public { }
/**
* @notice send `_value` token to `_to` from `msg.sender`
* @param _to The address of the recipient
* @param _value The amount of token to be transferred
* @return Whether the transfer was successful or not
*/
function transfer(
address _to,
uint256 _value
)
external
returns (bool success)
{
return transfer(msg.sender, _to, _value);
}
/**
* @notice `msg.sender` approves `_spender` to spend `_value` tokens
* @param _spender The address of the account able to transfer the tokens
* @param _value The amount of tokens to be approved for transfer
* @return Whether the approval was successful or not
*/
function approve(address _spender, uint256 _value)
external
returns (bool success)
{
allowed[msg.sender][_spender] = _value;
emit Approval(msg.sender, _spender, _value);
return true;
}
/**
* @notice send `_value` token to `_to` from `_from` on the condition it is approved by `_from`
* @param _from The address of the sender
* @param _to The address of the recipient
* @param _value The amount of token to be transferred
* @return Whether the transfer was successful or not
*/
function transferFrom(
address _from,
address _to,
uint256 _value
)
external
returns (bool success)
{
if (balances[_from] >= _value &&
allowed[_from][msg.sender] >= _value &&
_value > 0) {
allowed[_from][msg.sender] -= _value;
return transfer(_from, _to, _value);
} else {
return false;
}
}
/**
* @param _owner The address from which the balance will be retrieved
* @param _spender The address of the account able to spend the tokens
* @return The balance
*/
function allowance(address _owner, address _spender)
external
view
returns (uint256 remaining)
{
return allowed[_owner][_spender];
}
/**
* @param _owner The address of the account owning tokens
* @return Amount of remaining tokens allowed to spent
*/
function balanceOf(address _owner)
external
view
returns (uint256 balance)
{
return balances[_owner];
}
/**
* @notice return total supply of tokens
*/
function totalSupply()
external
view
returns(uint256 currentTotalSupply)
{
return supply;
}
/**
* @notice Mints tokens for testing
*/
function mint(
address _to,
uint256 _amount
)
public
{
balances[_to] += _amount;
supply += _amount;
emit Transfer(address(0), _to, _amount);
}
/**
* @notice send `_value` token to `_to` from `_from`
* @param _from The address of the sender
* @param _to The address of the recipient
* @param _value The amount of token to be transferred
* @return Whether the transfer was successful or not
*/
function transfer(
address _from,
address _to,
uint256 _value
)
internal
returns (bool success)
{
if (balances[_from] >= _value && _value > 0) {
balances[_from] -= _value;
balances[_to] += _value;
emit Transfer(_from, _to, _value);
return true;
} else {
return false;
}
}
}

View File

@ -0,0 +1,35 @@
/* solium-disable security/no-block-members */
/* solium-disable no-empty-blocks */
pragma solidity >=0.5.0 <0.6.0;
import "../teller-network/Escrow.sol";
/**
* @title Test Escrow Upgrade contract
*/
contract TestEscrowUpgrade is Escrow {
constructor (
address _relayer,
address _license,
address _arbitrationLicense,
address _metadataStore,
address payable _feeDestination,
uint _feeMilliPercent
)
Escrow(_relayer, _license, _arbitrationLicense, _metadataStore, _feeDestination, _feeMilliPercent)
public {
}
uint private val;
function getVal() public view returns (uint) {
return val;
}
function setVal(uint _val) public {
val = _val;
}
}

View File

@ -0,0 +1,5 @@
pragma solidity ^0.5.7;
contract ApproveAndCallFallBack {
function receiveApproval(address from, uint256 _amount, address _token, bytes memory _data) public;
}

View File

@ -0,0 +1,53 @@
pragma solidity ^0.5.7;
// Abstract contract for the full ERC 20 Token standard
// https://github.com/ethereum/EIPs/issues/20
interface ERC20Token {
/**
* @notice send `_value` token to `_to` from `msg.sender`
* @param _to The address of the recipient
* @param _value The amount of token to be transferred
* @return Whether the transfer was successful or not
*/
function transfer(address _to, uint256 _value) external returns (bool success);
/**
* @notice `msg.sender` approves `_spender` to spend `_value` tokens
* @param _spender The address of the account able to transfer the tokens
* @param _value The amount of tokens to be approved for transfer
* @return Whether the approval was successful or not
*/
function approve(address _spender, uint256 _value) external returns (bool success);
/**
* @notice send `_value` token to `_to` from `_from` on the condition it is approved by `_from`
* @param _from The address of the sender
* @param _to The address of the recipient
* @param _value The amount of token to be transferred
* @return Whether the transfer was successful or not
*/
function transferFrom(address _from, address _to, uint256 _value) external returns (bool success);
/**
* @param _owner The address from which the balance will be retrieved
* @return The balance
*/
function balanceOf(address _owner) external view returns (uint256 balance);
/**
* @param _owner The address of the account owning tokens
* @param _spender The address of the account able to transfer the tokens
* @return Amount of remaining tokens allowed to spent
*/
function allowance(address _owner, address _spender) external view returns (uint256 remaining);
/**
* @notice return total supply of tokens
*/
function totalSupply() external view returns (uint256 supply);
event Transfer(address indexed _from, address indexed _to, uint256 _value);
event Approval(address indexed _owner, address indexed _spender, uint256 _value);
}

View File

@ -0,0 +1,637 @@
pragma solidity ^0.5.7;
/*
Copyright 2016, Jordi Baylina
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* @title MiniMeToken Contract
* @author Jordi Baylina
* @dev This token contract's goal is to make it easy for anyone to clone this
* token using the token distribution at a given block, this will allow DAO's
* and DApps to upgrade their features in a decentralized manner without
* affecting the original token
* @dev It is ERC20 compliant, but still needs to under go further testing.
*/
import "../common/Controlled.sol";
import "./TokenController.sol";
import "./ApproveAndCallFallBack.sol";
import "./MiniMeTokenInterface.sol";
import "./TokenFactory.sol";
/**
* @dev The actual token contract, the default controller is the msg.sender
* that deploys the contract, so usually this token will be deployed by a
* token controller contract, which Giveth will call a "Campaign"
*/
contract MiniMeToken is MiniMeTokenInterface, Controlled {
string public name; //The Token's name: e.g. DigixDAO Tokens
uint8 public decimals; //Number of decimals of the smallest unit
string public symbol; //An identifier: e.g. REP
string public version = "MMT_0.1"; //An arbitrary versioning scheme
/**
* @dev `Checkpoint` is the structure that attaches a block number to a
* given value, the block number attached is the one that last changed the
* value
*/
struct Checkpoint {
// `fromBlock` is the block number that the value was generated from
uint128 fromBlock;
// `value` is the amount of tokens at a specific block number
uint128 value;
}
// `parentToken` is the Token address that was cloned to produce this token;
// it will be 0x0 for a token that was not cloned
MiniMeToken public parentToken;
// `parentSnapShotBlock` is the block number from the Parent Token that was
// used to determine the initial distribution of the Clone Token
uint public parentSnapShotBlock;
// `creationBlock` is the block number that the Clone Token was created
uint public creationBlock;
// `balances` is the map that tracks the balance of each address, in this
// contract when the balance changes the block number that the change
// occurred is also included in the map
mapping (address => Checkpoint[]) balances;
// `allowed` tracks any extra transfer rights as in all ERC20 tokens
mapping (address => mapping (address => uint256)) allowed;
// Tracks the history of the `totalSupply` of the token
Checkpoint[] totalSupplyHistory;
// Flag that determines if the token is transferable or not.
bool public transfersEnabled;
// The factory used to create new clone tokens
TokenFactory public tokenFactory;
////////////////
// Constructor
////////////////
/**
* @notice Constructor to create a MiniMeToken
* @param _tokenFactory The address of the MiniMeTokenFactory contract that
* will create the Clone token contracts, the token factory needs to be
* deployed first
* @param _parentToken Address of the parent token, set to 0x0 if it is a
* new token
* @param _parentSnapShotBlock Block of the parent token that will
* determine the initial distribution of the clone token, set to 0 if it
* is a new token
* @param _tokenName Name of the new token
* @param _decimalUnits Number of decimals of the new token
* @param _tokenSymbol Token Symbol for the new token
* @param _transfersEnabled If true, tokens will be able to be transferred
*/
constructor(
address _tokenFactory,
address _parentToken,
uint _parentSnapShotBlock,
string memory _tokenName,
uint8 _decimalUnits,
string memory _tokenSymbol,
bool _transfersEnabled
)
public
{
tokenFactory = TokenFactory(_tokenFactory);
name = _tokenName; // Set the name
decimals = _decimalUnits; // Set the decimals
symbol = _tokenSymbol; // Set the symbol
parentToken = MiniMeToken(address(uint160(_parentToken)));
parentSnapShotBlock = _parentSnapShotBlock;
transfersEnabled = _transfersEnabled;
creationBlock = block.number;
}
///////////////////
// ERC20 Methods
///////////////////
/**
* @notice Send `_amount` tokens to `_to` from `msg.sender`
* @param _to The address of the recipient
* @param _amount The amount of tokens to be transferred
* @return Whether the transfer was successful or not
*/
function transfer(address _to, uint256 _amount) public returns (bool success) {
require(transfersEnabled);
return doTransfer(msg.sender, _to, _amount);
}
/**
* @notice Send `_amount` tokens to `_to` from `_from` on the condition it
* is approved by `_from`
* @param _from The address holding the tokens being transferred
* @param _to The address of the recipient
* @param _amount The amount of tokens to be transferred
* @return True if the transfer was successful
*/
function transferFrom(
address _from,
address _to,
uint256 _amount
)
public
returns (bool success)
{
// The controller of this contract can move tokens around at will,
// this is important to recognize! Confirm that you trust the
// controller of this contract, which in most situations should be
// another open source smart contract or 0x0
if (msg.sender != controller) {
require(transfersEnabled);
// The standard ERC 20 transferFrom functionality
if (allowed[_from][msg.sender] < _amount) {
return false;
}
allowed[_from][msg.sender] -= _amount;
}
return doTransfer(_from, _to, _amount);
}
/**
* @dev This is the actual transfer function in the token contract, it can
* only be called by other functions in this contract.
* @param _from The address holding the tokens being transferred
* @param _to The address of the recipient
* @param _amount The amount of tokens to be transferred
* @return True if the transfer was successful
*/
function doTransfer(
address _from,
address _to,
uint _amount
)
internal
returns(bool)
{
if (_amount == 0) {
return true;
}
require(parentSnapShotBlock < block.number);
// Do not allow transfer to 0x0 or the token contract itself
require((_to != address(0)) && (_to != address(this)));
// If the amount being transfered is more than the balance of the
// account the transfer returns false
uint256 previousBalanceFrom = balanceOfAt(_from, block.number);
if (previousBalanceFrom < _amount) {
return false;
}
// Alerts the token controller of the transfer
if (isContract(controller)) {
require(TokenController(controller).onTransfer(_from, _to, _amount));
}
// First update the balance array with the new value for the address
// sending the tokens
updateValueAtNow(balances[_from], previousBalanceFrom - _amount);
// Then update the balance array with the new value for the address
// receiving the tokens
uint256 previousBalanceTo = balanceOfAt(_to, block.number);
require(previousBalanceTo + _amount >= previousBalanceTo); // Check for overflow
updateValueAtNow(balances[_to], previousBalanceTo + _amount);
// An event to make the transfer easy to find on the blockchain
emit Transfer(_from, _to, _amount);
return true;
}
function doApprove(
address _from,
address _spender,
uint256 _amount
)
internal
returns (bool)
{
require(transfersEnabled);
// To change the approve amount you first have to reduce the addresses`
// allowance to zero by calling `approve(_spender,0)` if it is not
// already 0 to mitigate the race condition described here:
// https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
require((_amount == 0) || (allowed[_from][_spender] == 0));
// Alerts the token controller of the approve function call
if (isContract(controller)) {
require(TokenController(controller).onApprove(_from, _spender, _amount));
}
allowed[_from][_spender] = _amount;
emit Approval(_from, _spender, _amount);
return true;
}
/**
* @param _owner The address that's balance is being requested
* @return The balance of `_owner` at the current block
*/
function balanceOf(address _owner) external view returns (uint256 balance) {
return balanceOfAt(_owner, block.number);
}
/**
* @notice `msg.sender` approves `_spender` to spend `_amount` tokens on
* its behalf. This is a modified version of the ERC20 approve function
* to be a little bit safer
* @param _spender The address of the account able to transfer the tokens
* @param _amount The amount of tokens to be approved for transfer
* @return True if the approval was successful
*/
function approve(address _spender, uint256 _amount) external returns (bool success) {
return doApprove(msg.sender, _spender, _amount);
}
/**
* @dev This function makes it easy to read the `allowed[]` map
* @param _owner The address of the account that owns the token
* @param _spender The address of the account able to transfer the tokens
* @return Amount of remaining tokens of _owner that _spender is allowed
* to spend
*/
function allowance(
address _owner,
address _spender
)
external
view
returns (uint256 remaining)
{
return allowed[_owner][_spender];
}
/**
* @notice `msg.sender` approves `_spender` to send `_amount` tokens on
* its behalf, and then a function is triggered in the contract that is
* being approved, `_spender`. This allows users to use their tokens to
* interact with contracts in one function call instead of two
* @param _spender The address of the contract able to transfer the tokens
* @param _amount The amount of tokens to be approved for transfer
* @return True if the function call was successful
*/
function approveAndCall(
address _spender,
uint256 _amount,
bytes memory _extraData
)
public
returns (bool success)
{
require(doApprove(msg.sender, _spender, _amount));
ApproveAndCallFallBack(_spender).receiveApproval(
msg.sender,
_amount,
address(this),
_extraData
);
return true;
}
/**
* @dev This function makes it easy to get the total number of tokens
* @return The total number of tokens
*/
function totalSupply() external view returns (uint) {
return totalSupplyAt(block.number);
}
////////////////
// Query balance and totalSupply in History
////////////////
/**
* @dev Queries the balance of `_owner` at a specific `_blockNumber`
* @param _owner The address from which the balance will be retrieved
* @param _blockNumber The block number when the balance is queried
* @return The balance at `_blockNumber`
*/
function balanceOfAt(
address _owner,
uint _blockNumber
)
public
view
returns (uint)
{
// These next few lines are used when the balance of the token is
// requested before a check point was ever created for this token, it
// requires that the `parentToken.balanceOfAt` be queried at the
// genesis block for that token as this contains initial balance of
// this token
if ((balances[_owner].length == 0)
|| (balances[_owner][0].fromBlock > _blockNumber)) {
if (address(parentToken) != address(0)) {
return parentToken.balanceOfAt(_owner, min(_blockNumber, parentSnapShotBlock));
} else {
// Has no parent
return 0;
}
// This will return the expected balance during normal situations
} else {
return getValueAt(balances[_owner], _blockNumber);
}
}
/**
* @notice Total amount of tokens at a specific `_blockNumber`.
* @param _blockNumber The block number when the totalSupply is queried
* @return The total amount of tokens at `_blockNumber`
*/
function totalSupplyAt(uint _blockNumber) public view returns(uint) {
// These next few lines are used when the totalSupply of the token is
// requested before a check point was ever created for this token, it
// requires that the `parentToken.totalSupplyAt` be queried at the
// genesis block for this token as that contains totalSupply of this
// token at this block number.
if ((totalSupplyHistory.length == 0)
|| (totalSupplyHistory[0].fromBlock > _blockNumber)) {
if (address(parentToken) != address(0)) {
return parentToken.totalSupplyAt(min(_blockNumber, parentSnapShotBlock));
} else {
return 0;
}
// This will return the expected totalSupply during normal situations
} else {
return getValueAt(totalSupplyHistory, _blockNumber);
}
}
////////////////
// Clone Token Method
////////////////
/**
* @notice Creates a new clone token with the initial distribution being
* this token at `snapshotBlock`
* @param _cloneTokenName Name of the clone token
* @param _cloneDecimalUnits Number of decimals of the smallest unit
* @param _cloneTokenSymbol Symbol of the clone token
* @param _snapshotBlock Block when the distribution of the parent token is
* copied to set the initial distribution of the new clone token;
* if the block is zero than the actual block, the current block is used
* @param _transfersEnabled True if transfers are allowed in the clone
* @return The address of the new MiniMeToken Contract
*/
function createCloneToken(
string memory _cloneTokenName,
uint8 _cloneDecimalUnits,
string memory _cloneTokenSymbol,
uint _snapshotBlock,
bool _transfersEnabled
)
public
returns(address)
{
uint snapshotBlock = _snapshotBlock;
if (snapshotBlock == 0) {
snapshotBlock = block.number;
}
MiniMeToken cloneToken = MiniMeToken(tokenFactory.createCloneToken(
address(this),
snapshotBlock,
_cloneTokenName,
_cloneDecimalUnits,
_cloneTokenSymbol,
_transfersEnabled
));
cloneToken.changeController(msg.sender);
// An event to make the token easy to find on the blockchain
emit NewCloneToken(address(cloneToken), snapshotBlock);
return address(cloneToken);
}
////////////////
// Generate and destroy tokens
////////////////
/**
* @notice Generates `_amount` tokens that are assigned to `_owner`
* @param _owner The address that will be assigned the new tokens
* @param _amount The quantity of tokens generated
* @return True if the tokens are generated correctly
*/
function generateTokens(
address _owner,
uint _amount
)
public
onlyController
returns (bool)
{
uint curTotalSupply = totalSupplyAt(block.number);
require(curTotalSupply + _amount >= curTotalSupply); // Check for overflow
uint previousBalanceTo = balanceOfAt(_owner, block.number);
require(previousBalanceTo + _amount >= previousBalanceTo); // Check for overflow
updateValueAtNow(totalSupplyHistory, curTotalSupply + _amount);
updateValueAtNow(balances[_owner], previousBalanceTo + _amount);
emit Transfer(address(0), _owner, _amount);
return true;
}
/**
* @notice Burns `_amount` tokens from `_owner`
* @param _owner The address that will lose the tokens
* @param _amount The quantity of tokens to burn
* @return True if the tokens are burned correctly
*/
function destroyTokens(
address _owner,
uint _amount
)
public
onlyController
returns (bool)
{
uint curTotalSupply = totalSupplyAt(block.number);
require(curTotalSupply >= _amount);
uint previousBalanceFrom = balanceOfAt(_owner, block.number);
require(previousBalanceFrom >= _amount);
updateValueAtNow(totalSupplyHistory, curTotalSupply - _amount);
updateValueAtNow(balances[_owner], previousBalanceFrom - _amount);
emit Transfer(_owner, address(0), _amount);
return true;
}
////////////////
// Enable tokens transfers
////////////////
/**
* @notice Enables token holders to transfer their tokens freely if true
* @param _transfersEnabled True if transfers are allowed in the clone
*/
function enableTransfers(bool _transfersEnabled) public onlyController {
transfersEnabled = _transfersEnabled;
}
////////////////
// Internal helper functions to query and set a value in a snapshot array
////////////////
/**
* @dev `getValueAt` retrieves the number of tokens at a given block number
* @param checkpoints The history of values being queried
* @param _block The block number to retrieve the value at
* @return The number of tokens being queried
*/
function getValueAt(
Checkpoint[] storage checkpoints,
uint _block
)
internal
view
returns (uint)
{
if (checkpoints.length == 0) {
return 0;
}
// Shortcut for the actual value
if (_block >= checkpoints[checkpoints.length-1].fromBlock) {
return checkpoints[checkpoints.length-1].value;
}
if (_block < checkpoints[0].fromBlock) {
return 0;
}
// Binary search of the value in the array
uint min = 0;
uint max = checkpoints.length-1;
while (max > min) {
uint mid = (max + min + 1) / 2;
if (checkpoints[mid].fromBlock<=_block) {
min = mid;
} else {
max = mid-1;
}
}
return checkpoints[min].value;
}
/**
* @dev `updateValueAtNow` used to update the `balances` map and the
* `totalSupplyHistory`
* @param checkpoints The history of data being updated
* @param _value The new number of tokens
*/
function updateValueAtNow(Checkpoint[] storage checkpoints, uint _value) internal {
if ((checkpoints.length == 0)
|| (checkpoints[checkpoints.length -1].fromBlock < block.number)) {
Checkpoint storage newCheckPoint = checkpoints[checkpoints.length++];
newCheckPoint.fromBlock = uint128(block.number);
newCheckPoint.value = uint128(_value);
} else {
Checkpoint storage oldCheckPoint = checkpoints[checkpoints.length-1];
oldCheckPoint.value = uint128(_value);
}
}
/**
* @dev Internal function to determine if an address is a contract
* @param _addr The address being queried
* @return True if `_addr` is a contract
*/
function isContract(address _addr) internal view returns(bool) {
uint size;
if (_addr == address(0)){
return false;
}
assembly {
size := extcodesize(_addr)
}
return size>0;
}
/**
* @dev Helper function to return a min betwen the two uints
*/
function min(uint a, uint b) internal pure returns (uint) {
return a < b ? a : b;
}
/**
* @notice The fallback function: If the contract's controller has not been
* set to 0, then the `proxyPayment` method is called which relays the
* ether and creates tokens as described in the token controller contract
*/
function () external payable {
require(isContract(controller));
require(TokenController(controller).proxyPayment.value(msg.value)(msg.sender));
}
//////////
// Safety Methods
//////////
/**
* @notice This method can be used by the controller to extract mistakenly
* sent tokens to this contract.
* @param _token The address of the token contract that you want to recover
* set to 0 in case you want to extract ether.
*/
function claimTokens(address _token) public onlyController {
if (_token == address(0)) {
controller.transfer(address(this).balance);
return;
}
MiniMeToken token = MiniMeToken(address(uint160(_token)));
uint balance = token.balanceOf(address(this));
token.transfer(controller, balance);
emit ClaimedTokens(_token, controller, balance);
}
////////////////
// Events
////////////////
event ClaimedTokens(address indexed _token, address indexed _controller, uint _amount);
event Transfer(address indexed _from, address indexed _to, uint256 _amount);
event NewCloneToken(address indexed _cloneToken, uint snapshotBlock);
event Approval(
address indexed _owner,
address indexed _spender,
uint256 _amount
);
}

View File

@ -0,0 +1,50 @@
pragma solidity ^0.5.7;
import "./TokenFactory.sol";
import "./MiniMeToken.sol";
////////////////
// MiniMeTokenFactory
////////////////
/**
* @dev This contract is used to generate clone contracts from a contract.
* In solidity this is the way to create a contract from a contract of the
* same class
*/
contract MiniMeTokenFactory is TokenFactory {
/**
* @notice Update the DApp by creating a new token with new functionalities
* the msg.sender becomes the controller of this clone token
* @param _parentToken Address of the token being cloned
* @param _snapshotBlock Block of the parent token that will
* determine the initial distribution of the clone token
* @param _tokenName Name of the new token
* @param _decimalUnits Number of decimals of the new token
* @param _tokenSymbol Token Symbol for the new token
* @param _transfersEnabled If true, tokens will be able to be transferred
* @return The address of the new token contract
*/
function createCloneToken(
address _parentToken,
uint _snapshotBlock,
string memory _tokenName,
uint8 _decimalUnits,
string memory _tokenSymbol,
bool _transfersEnabled
) public returns (address payable) {
MiniMeToken newToken = new MiniMeToken(
address(this),
_parentToken,
_snapshotBlock,
_tokenName,
_decimalUnits,
_tokenSymbol,
_transfersEnabled
);
newToken.changeController(msg.sender);
return address(newToken);
}
}

View File

@ -0,0 +1,108 @@
pragma solidity ^0.5.7;
import "./ERC20Token.sol";
contract MiniMeTokenInterface is ERC20Token {
/**
* @notice `msg.sender` approves `_spender` to send `_amount` tokens on
* its behalf, and then a function is triggered in the contract that is
* being approved, `_spender`. This allows users to use their tokens to
* interact with contracts in one function call instead of two
* @param _spender The address of the contract able to transfer the tokens
* @param _amount The amount of tokens to be approved for transfer
* @return True if the function call was successful
*/
function approveAndCall(
address _spender,
uint256 _amount,
bytes memory _extraData
)
public
returns (bool success);
/**
* @notice Creates a new clone token with the initial distribution being
* this token at `_snapshotBlock`
* @param _cloneTokenName Name of the clone token
* @param _cloneDecimalUnits Number of decimals of the smallest unit
* @param _cloneTokenSymbol Symbol of the clone token
* @param _snapshotBlock Block when the distribution of the parent token is
* copied to set the initial distribution of the new clone token;
* if the block is zero than the actual block, the current block is used
* @param _transfersEnabled True if transfers are allowed in the clone
* @return The address of the new MiniMeToken Contract
*/
function createCloneToken(
string memory _cloneTokenName,
uint8 _cloneDecimalUnits,
string memory _cloneTokenSymbol,
uint _snapshotBlock,
bool _transfersEnabled
)
public
returns(address);
/**
* @notice Generates `_amount` tokens that are assigned to `_owner`
* @param _owner The address that will be assigned the new tokens
* @param _amount The quantity of tokens generated
* @return True if the tokens are generated correctly
*/
function generateTokens(
address _owner,
uint _amount
)
public
returns (bool);
/**
* @notice Burns `_amount` tokens from `_owner`
* @param _owner The address that will lose the tokens
* @param _amount The quantity of tokens to burn
* @return True if the tokens are burned correctly
*/
function destroyTokens(
address _owner,
uint _amount
)
public
returns (bool);
/**
* @notice Enables token holders to transfer their tokens freely if true
* @param _transfersEnabled True if transfers are allowed in the clone
*/
function enableTransfers(bool _transfersEnabled) public;
/**
* @notice This method can be used by the controller to extract mistakenly
* sent tokens to this contract.
* @param _token The address of the token contract that you want to recover
* set to 0 in case you want to extract ether.
*/
function claimTokens(address _token) public;
/**
* @dev Queries the balance of `_owner` at a specific `_blockNumber`
* @param _owner The address from which the balance will be retrieved
* @param _blockNumber The block number when the balance is queried
* @return The balance at `_blockNumber`
*/
function balanceOfAt(
address _owner,
uint _blockNumber
)
public
view
returns (uint);
/**
* @notice Total amount of tokens at a specific `_blockNumber`.
* @param _blockNumber The block number when the totalSupply is queried
* @return The total amount of tokens at `_blockNumber`
*/
function totalSupplyAt(uint _blockNumber) public view returns(uint);
}

View File

@ -0,0 +1,33 @@
pragma solidity ^0.5.7;
/**
* @dev The token controller contract must implement these functions
*/
interface TokenController {
/**
* @notice Called when `_owner` sends ether to the MiniMe Token contract
* @param _owner The address that sent the ether to create tokens
* @return True if the ether is accepted, false if it throws
*/
function proxyPayment(address _owner) external payable returns(bool);
/**
* @notice Notifies the controller about a token transfer allowing the
* controller to react if desired
* @param _from The origin of the transfer
* @param _to The destination of the transfer
* @param _amount The amount of the transfer
* @return False if the controller does not authorize the transfer
*/
function onTransfer(address _from, address _to, uint _amount) external returns(bool);
/**
* @notice Notifies the controller about an approval allowing the
* controller to react if desired
* @param _owner The address that calls `approve()`
* @param _spender The spender in the `approve()` call
* @param _amount The amount in the `approve()` call
* @return False if the controller does not authorize the approval
*/
function onApprove(address _owner, address _spender, uint _amount) external
returns(bool);
}

View File

@ -0,0 +1,12 @@
pragma solidity ^0.5.7;
contract TokenFactory {
function createCloneToken(
address _parentToken,
uint _snapshotBlock,
string memory _tokenName,
uint8 _decimalUnits,
string memory _tokenSymbol,
bool _transfersEnabled
) public returns (address payable);
}

View File

@ -0,0 +1,24 @@
{
"contracts": [
"contracts/**"
],
"buildDir": "dist/",
"config": "embarkConfig/",
"versions": {
"web3": "1.0.0-beta",
"solc": "0.5.10",
"ipfs-api": "17.2.4"
},
"plugins": {
"embark-solium": {},
"embarkjs-connector-web3": {},
"embark-solc": {}
},
"options": {
"solc": {
"optimize": true,
"optimize-runs": 200
}
},
"generationDir": "src/embarkArtifacts"
}

View File

@ -0,0 +1,131 @@
module.exports = {
// applies to all environments
default: {
enabled: true,
rpcHost: "localhost", // HTTP-RPC server listening interface (default: "localhost")
rpcPort: 8545, // HTTP-RPC server listening port (default: 8545)
rpcCorsDomain: "auto", // Comma separated list of domains from which to accept cross origin requests (browser enforced)
// When set to "auto", Embark will automatically set the cors to the address of the webserver
wsRPC: true, // Enable the WS-RPC server
wsOrigins: "auto", // Origins from which to accept websockets requests
// When set to "auto", Embark will automatically set the cors to the address of the webserver
wsHost: "localhost", // WS-RPC server listening interface (default: "localhost")
wsPort: 8546, // WS-RPC server listening port (default: 8546)
// Accounts to use as node accounts
// The order here corresponds to the order of `web3.eth.getAccounts`, so the first one is the `defaultAccount`
accounts: [
{
nodeAccounts: true, // Accounts use for the node
numAddresses: 2, // Number of addresses/accounts (defaults to 1)
password: "embarkConfig/development/password" // Password file for the accounts
}
]
},
// default environment, merges with the settings in default
// assumed to be the intended environment by `embark run` and `embark blockchain`
development: {
ethereumClientName: "geth", // Can be geth or parity (default:geth)
//ethereumClientBin: "geth", // path to the client binary. Useful if it is not in the global PATH
networkType: "custom", // Can be: testnet, rinkeby, livenet or custom, in which case, it will use the specified networkId
networkId: 1337, // Network id used when networkType is custom
isDev: true, // Uses and ephemeral proof-of-authority network with a pre-funded developer account, mining enabled
datadir: ".embark/development/datadir", // Data directory for the databases and keystore (Geth 1.8.15 and Parity 2.0.4 can use the same base folder, till now they does not conflict with each other)
mineWhenNeeded: true, // Uses our custom script (if isDev is false) to mine only when needed
nodiscover: true, // Disables the peer discovery mechanism (manual peer addition)
maxpeers: 0, // Maximum number of network peers (network disabled if set to 0) (default: 25)
proxy: true, // Proxy is used to present meaningful information about transactions
targetGasLimit: 8000000, // Target gas limit sets the artificial target gas floor for the blocks to mine
simulatorBlocktime: 0 // Specify blockTime in seconds for automatic mining. Default is 0 and no auto-mining.
},
// merges with the settings in default
// used with "embark run privatenet" and/or "embark blockchain privatenet"
privatenet: {
networkType: "custom",
networkId: 1337,
isDev: false,
datadir: ".embark/privatenet/datadir",
// -- mineWhenNeeded --
// This options is only valid when isDev is false.
// Enabling this option uses our custom script to mine only when needed.
// Embark creates a development account for you (using `geth account new`) and funds the account. This account can be used for
// development (and even imported in to MetaMask). To enable correct usage, a password for this account must be specified
// in the `account > password` setting below.
// NOTE: once `mineWhenNeeded` is enabled, you must run an `embark reset` on your dApp before running
// `embark blockchain` or `embark run` for the first time.
mineWhenNeeded: true,
// -- genesisBlock --
// This option is only valid when mineWhenNeeded is true (which is only valid if isDev is false).
// When enabled, geth uses POW to mine transactions as it would normally, instead of using POA as it does in --dev mode.
// On the first `embark blockchain or embark run` after this option is enabled, geth will create a new chain with a
// genesis block, which can be configured using the `genesisBlock` configuration option below.
genesisBlock: "embarkConfig/privatenet/genesis.json", // Genesis block to initiate on first creation of a development node
nodiscover: true,
maxpeers: 0,
proxy: true,
accounts: [
{
nodeAccounts: true,
password: "embarkConfig/privatenet/password" // Password to unlock the account
}
],
targetGasLimit: 8000000,
simulatorBlocktime: 0
},
privateparitynet: {
ethereumClientName: "parity",
networkType: "custom",
networkId: 1337,
isDev: false,
genesisBlock: "embarkConfig/privatenet/genesis-parity.json", // Genesis block to initiate on first creation of a development node
datadir: ".embark/privatenet/datadir",
mineWhenNeeded: false,
nodiscover: true,
maxpeers: 0,
proxy: true,
accounts: [
{
nodeAccounts: true,
password: "embarkConfig/privatenet/password"
}
],
targetGasLimit: 8000000,
simulatorBlocktime: 0
},
// merges with the settings in default
// used with "embark run testnet" and/or "embark blockchain testnet"
testnet: {
networkType: "testnet",
syncMode: "light",
accounts: [
{
nodeAccounts: true,
password: "embarkConfig/testnet/password"
}
]
},
// merges with the settings in default
// used with "embark run livenet" and/or "embark blockchain livenet"
livenet: {
networkType: "livenet",
syncMode: "light",
rpcCorsDomain: "http://localhost:8000",
wsOrigins: "http://localhost:8000",
accounts: [
{
nodeAccounts: true,
password: "embarkConfig/livenet/password"
}
]
}
// you can name an environment with specific settings and then specify with
// "embark run custom_name" or "embark blockchain custom_name"
//custom_name: {
//}
};

View File

@ -0,0 +1,46 @@
module.exports = {
// default applies to all environments
default: {
enabled: true,
provider: "whisper", // Communication provider. Currently, Embark only supports whisper
available_providers: ["whisper"], // Array of available providers
},
// default environment, merges with the settings in default
// assumed to be the intended environment by `embark run`
development: {
connection: {
host: "localhost", // Host of the blockchain node
port: 8546, // Port of the blockchain node
type: "ws" // Type of connection (ws or rpc)
}
},
// merges with the settings in default
// used with "embark run privatenet"
privatenet: {
},
// merges with the settings in default
// used with "embark run testnet"
testnet: {
},
// merges with the settings in default
// used with "embark run livenet"
livenet: {
},
// you can name an environment with specific settings and then specify with
// "embark run custom_name"
//custom_name: {
//}
// Use this section when you need a specific symmetric or private keys in whisper
/*
,keys: {
symmetricKey: "your_symmetric_key",// Symmetric key for message decryption
privateKey: "your_private_key" // Private Key to be used as a signing key and for message decryption
}
*/
//}
};

View File

@ -0,0 +1,291 @@
const LICENSE_PRICE = "10000000000000000000"; // 10 * Math.pow(10, 18)
const ARB_LICENSE_PRICE = "10000000000000000000"; // 10 * Math.pow(10, 18)
const FEE_MILLI_PERCENT = "1000"; // 1 percent
const BURN_ADDRESS = "0x0000000000000000000000000000000000000002";
const dataMigration = require('./data.js');
let secret = {};
try {
secret = require('../.secret.json');
} catch(err) {
console.dir("warning: .secret.json file not found; this is only needed to deploy to testnet or livenet etc..");
}
module.exports = {
// default applies to all environments
default: {
// Blockchain node to deploy the contracts
deployment: {
host: "localhost", // Host of the blockchain node
port: 8546, // Port of the blockchain node
type: "ws" // Type of connection (ws or rpc),
// Accounts to use instead of the default account to populate your wallet
// The order here corresponds to the order of `web3.eth.getAccounts`, so the first one is the `defaultAccount`
/*,accounts: [
{
privateKey: "your_private_key",
balance: "5 ether" // You can set the balance of the account in the dev environment
// Balances are in Wei, but you can specify the unit with its name
},
{
privateKeyFile: "path/to/file", // Either a keystore or a list of keys, separated by , or ;
password: "passwordForTheKeystore" // Needed to decrypt the keystore file
},
{
mnemonic: "12 word mnemonic",
addressIndex: "0", // Optionnal. The index to start getting the address
numAddresses: "1", // Optionnal. The number of addresses to get
hdpath: "m/44'/60'/0'/0/" // Optionnal. HD derivation path
},
{
"nodeAccounts": true // Uses the Ethereum node's accounts
}
]*/
},
// order of connections the dapp should connect to
dappConnection: [
"$WEB3", // uses pre existing web3 object if available (e.g in Mist)
"ws://localhost:8546",
"http://localhost:8545"
],
// Automatically call `ethereum.enable` if true.
// If false, the following code must run before sending any transaction: `await EmbarkJS.enableEthereum();`
// Default value is true.
// dappAutoEnable: true,
gas: "auto",
// Strategy for the deployment of the contracts:
// - implicit will try to deploy all the contracts located inside the contracts directory
// or the directory configured for the location of the contracts. This is default one
// when not specified
// - explicit will only attempt to deploy the contracts that are explicity specified inside the
// contracts section.
strategy: 'explicit',
contracts: {
OwnedUpgradeabilityProxy: {
deploy: false
},
License: {
deploy: false
},
SellerLicense: {
instanceOf: "License",
args: [
"$SNT",
LICENSE_PRICE,
BURN_ADDRESS // TODO: replace with "$StakingPool"
]
},
SellerLicenseProxy: {
instanceOf: "OwnedUpgradeabilityProxy"
},
ArbitrationLicense: {
args: [
"$SNT",
ARB_LICENSE_PRICE,
BURN_ADDRESS // TODO: replace with "$StakingPool"
]
},
ArbitrationLicenseProxy: {
instanceOf: "OwnedUpgradeabilityProxy"
},
"MetadataStore": {
args: ["$SellerLicense", "$ArbitrationLicense"]
},
MetadataStoreProxy: {
instanceOf: "OwnedUpgradeabilityProxy"
},
"RLPReader": {
file: 'tabookey-gasless/contracts/RLPReader.sol'
},
"RelayHub": {
file: 'tabookey-gasless/contracts/RelayHub.sol'
},
EscrowRelay: {
args: ["$MetadataStoreProxy", "$EscrowProxy", "$SNT"],
deps: ['RelayHub'],
onDeploy: [
"EscrowRelay.methods.setRelayHubAddress('$RelayHub').send()",
"RelayHub.methods.depositFor('$EscrowRelay').send({value: 1000000000000000000})"
]
},
Escrow: {
args: ["0x0000000000000000000000000000000000000000", "$SellerLicense", "$ArbitrationLicense", "$MetadataStore", "$KyberFeeBurner", FEE_MILLI_PERCENT]
},
EscrowProxy: {
instanceOf: "OwnedUpgradeabilityProxy"
},
"MiniMeToken": { "deploy": false },
"MiniMeTokenFactory": { },
"Fees": {
"deploy": false
},
"SNT": {
"instanceOf": "MiniMeToken",
"args": [
"$MiniMeTokenFactory",
"0x0000000000000000000000000000000000000000",
0,
"TestMiniMeToken",
18,
"STT",
true
]
},
/*
"StakingPool": {
file: 'staking-pool/contracts/StakingPool.sol',
args: [
"$SNT"
]
},
*/
KyberNetworkProxy: {
},
KyberFeeBurner: { // TODO: replace BURN_ADDRESS with "$StakingPool"
args: ["$SNT", BURN_ADDRESS, "$KyberNetworkProxy", "0x0000000000000000000000000000000000000000"]
}
}
},
// default environment, merges with the settings in default
// assumed to be the intended environment by `embark run`
development: {
contracts: {
StandardToken: { },
DAI: { instanceOf: "StandardToken", onDeploy: ["DAI.methods.mint('$accounts[0]', '20000000000000000000').send()"] },
MKR: { instanceOf: "StandardToken", onDeploy: ["MKR.methods.mint('$accounts[0]', '20000000000000000000').send()"] }
},
deployment: {
// The order here corresponds to the order of `web3.eth.getAccounts`, so the first one is the `defaultAccount`
accounts: [
{
nodeAccounts: true
},
{
mnemonic: "foster gesture flock merge beach plate dish view friend leave drink valley shield list enemy",
balance: "5 ether",
numAddresses: "10"
}
]
},
afterDeploy: dataMigration.bind(null, LICENSE_PRICE, ARB_LICENSE_PRICE, FEE_MILLI_PERCENT, BURN_ADDRESS)
},
// merges with the settings in default
// used with "embark run privatenet"
privatenet: {
},
// merges with the settings in default
// used with "embark run testnet"
testnet: {
tracking: 'shared.rinkeby.json',
deployment: {
accounts: [
{
mnemonic: secret.mnemonic,
hdpath: secret.hdpath || "m/44'/60'/0'/0/",
numAddresses: "10"
}
],
host: `rinkeby.infura.io/${secret.infuraKey}`,
port: false,
protocol: 'https',
type: "rpc"
},
afterDeploy: dataMigration.bind(null, LICENSE_PRICE, ARB_LICENSE_PRICE, FEE_MILLI_PERCENT, BURN_ADDRESS),
dappConnection: ["$WEB3"],
contracts: {
StandardToken: { },
DAI: { instanceOf: "StandardToken", onDeploy: ["DAI.methods.mint('$accounts[0]', '20000000000000000000').send()"] },
MKR: { instanceOf: "StandardToken", onDeploy: ["MKR.methods.mint('$accounts[0]', '20000000000000000000').send()"] },
KyberNetworkProxy: {
// https://developer.kyber.network/docs/Environments-Rinkeby/
address: "0xF77eC7Ed5f5B9a5aee4cfa6FFCaC6A4C315BaC76"
},
RelayHub: {
address: '0xd216153c06e857cd7f72665e0af1d7d82172f494'
},
EscrowRelay: {
args: ["$MetadataStoreProxy", "$EscrowProxy", "$SNT"],
deps: ['RelayHub'],
onDeploy: [
"EscrowRelay.methods.setRelayHubAddress('$RelayHub').send()",
"RelayHub.methods.depositFor('$EscrowRelay').send({value: 10000000000000000})"
]
}
}
},
ropsten: {
gasPrice: "10000000000",
tracking: 'shared.ropsten.json',
contracts: {
EscrowRelay: {
args: ["$MetadataStoreProxy", "$EscrowProxy", "$SNT"],
deps: ['RelayHub'],
onDeploy: [
"EscrowRelay.methods.setRelayHubAddress('$RelayHub').send({gasPrice: 20000000000, gas: 1000000})",
"RelayHub.methods.depositFor('$EscrowRelay').send({gasPrice: 20000000000, value: 100000000000000000, gas: 1000000})"
]
},
Escrow: {
args: ["0x0000000000000000000000000000000000000000", "$SellerLicenseProxy", "$ArbitrationLicenseProxy", "$MetadataStoreProxy", BURN_ADDRESS, FEE_MILLI_PERCENT]
},
SNT: {
address: "0xc55cf4b03948d7ebc8b9e8bad92643703811d162"
},
"RLPReader": {
deploy: false
},
"RelayHub": {
address: "0x1349584869A1C7b8dc8AE0e93D8c15F5BB3B4B87"
},
"MiniMeTokenFactory": {
deploy: false
},
KyberNetworkProxy: {
// https://developer.kyber.network/docs/Environments-Ropsten/
address: "0x818E6FECD516Ecc3849DAf6845e3EC868087B755"
}
},
deployment: {
accounts: [
{
mnemonic: secret.mnemonic,
hdpath: secret.hdpath || "m/44'/60'/0'/0/",
numAddresses: "10"
}
],
host: `ropsten.infura.io/${secret.infuraKey}`,
port: false,
protocol: 'https',
type: "rpc"
},
afterDeploy: dataMigration.bind(null, LICENSE_PRICE, ARB_LICENSE_PRICE, FEE_MILLI_PERCENT, BURN_ADDRESS),
dappConnection: ["$WEB3"]
},
// merges with the settings in default
// used with "embark run livenet"
livenet: {
contracts: {
KyberNetworkProxy: {
// https://developer.kyber.network/docs/Environments-Mainnet/
address: "0x818E6FECD516Ecc3849DAf6845e3EC868087B755"
}
}
}
// you can name an environment with specific settings and then specify with
// "embark run custom_name" or "embark blockchain custom_name"
//custom_name: {
//}
};

View File

@ -0,0 +1,274 @@
module.exports = async (licensePrice, arbitrationLicensePrice, feeMilliPercent, burnAddress, deps) => {
try {
const addresses = await deps.web3.eth.getAccounts();
const main = addresses[0];
{
console.log('Setting the initial SellerLicense "template", and calling the init() function');
const abiEncode = deps.contracts.SellerLicense.methods.init(
deps.contracts.SNT.options.address,
licensePrice,
burnAddress
).encodeABI();
const receipt = await deps.contracts.SellerLicenseProxy.methods.upgradeToAndCall(deps.contracts.SellerLicense.options.address, abiEncode).send({from: main, gas: 1000000});
console.log(`Setting done and was a ${(receipt.status === true || receipt.status === 1) ? 'success' : 'failure'}`);
}
{
console.log('Setting the initial ArbitrationLicense "template", and calling the init() function');
const abiEncode = deps.contracts.ArbitrationLicense.methods.init(
deps.contracts.SNT.options.address,
arbitrationLicensePrice,
burnAddress
).encodeABI();
const receipt = await deps.contracts.ArbitrationLicenseProxy.methods.upgradeToAndCall(deps.contracts.ArbitrationLicense.options.address, abiEncode).send({from: main, gas: 1000000});
console.log(`Setting done and was a ${(receipt.status === true || receipt.status === 1) ? 'success' : 'failure'}`);
}
{
console.log('Setting the initial MetadataStore "template", and calling the init() function');
const abiEncode = deps.contracts.MetadataStore.methods.init(
deps.contracts.SellerLicenseProxy.options.address,
deps.contracts.ArbitrationLicenseProxy.options.address
).encodeABI();
const receipt = await deps.contracts.MetadataStoreProxy.methods.upgradeToAndCall(deps.contracts.MetadataStore.options.address, abiEncode).send({from: main, gas: 1000000});
console.log(`Setting done and was a ${(receipt.status === true || receipt.status === 1) ? 'success' : 'failure'}`);
}
{
console.log('Setting the initial Escrow "template", and calling the init() function');
const abiEncode = deps.contracts.Escrow.methods.init(
deps.contracts.EscrowRelay.options.address,
deps.contracts.SellerLicenseProxy.options.address,
deps.contracts.ArbitrationLicenseProxy.options.address,
deps.contracts.MetadataStoreProxy.options.address,
burnAddress, // TODO: replace with StakingPool address
feeMilliPercent
).encodeABI();
// Here we are setting the initial "template", and calling the init() function
const receipt = await deps.contracts.EscrowProxy.methods.upgradeToAndCall(deps.contracts.Escrow.options.address, abiEncode).send({from: main, gas: 1000000});
console.log(`Setting done and was a ${(receipt.status === true || receipt.status === 1) ? 'success' : 'failure'}`);
}
deps.contracts.Escrow.options.address = deps.contracts.EscrowProxy.options.address;
deps.contracts.SellerLicense.options.address = deps.contracts.SellerLicenseProxy.options.address;
deps.contracts.ArbitrationLicense.options.address = deps.contracts.ArbitrationLicenseProxy.options.address;
deps.contracts.MetadataStore.options.address = deps.contracts.MetadataStoreProxy.options.address;
const arbitrator = addresses[9];
const sntToken = 10000000;
const balance = await deps.contracts.SNT.methods.balanceOf(main).call();
if (balance !== '0') {
return;
}
console.log("Seeding data...");
console.log('Send ETH...');
const value = 100 * Math.pow(10, 18);
await Promise.all(addresses.slice(1, 10).map(async (address) => {
return deps.web3.eth.sendTransaction({
to: address,
from: main,
value: value.toString()
});
}));
console.log('Generate SNT...');
await Promise.all(addresses.map(async (address) => {
const generateToken = deps.contracts.SNT.methods.generateTokens(address, sntToken + '000000000000000000');
const gas = await generateToken.estimateGas({from: main});
return generateToken.send({from: main, gas});
}));
console.log('Generate Standard Tokens');
const weiToken = "5000000000000";
await Promise.all(addresses.slice(0, 9).map(async (address) => {
const generateToken = deps.contracts.StandardToken.methods.mint(address, weiToken.toString());
const gas = await generateToken.estimateGas({from: main});
return generateToken.send({from: main, gas});
}));
console.log("Buy arbitration licenses");
{
const buyLicense = deps.contracts.ArbitrationLicense.methods.buy().encodeABI();
let toSend = deps.contracts.SNT.methods.approveAndCall(deps.contracts.ArbitrationLicense._address, arbitrationLicensePrice, buyLicense);
let gas = await toSend.estimateGas({from: arbitrator});
await toSend.send({from: arbitrator, gas});
toSend = deps.contracts.SNT.methods.approveAndCall(deps.contracts.ArbitrationLicense._address, arbitrationLicensePrice, buyLicense);
gas = await toSend.estimateGas({from: addresses[8]});
await toSend.send({from: addresses[8], gas});
// Accepting everyone
toSend = deps.contracts.ArbitrationLicense.methods.changeAcceptAny(true);
gas = await toSend.estimateGas({from: arbitrator});
await toSend.send({from: arbitrator, gas});
}
console.log('Buy Licenses...');
await Promise.all(addresses.slice(1, 7).map(async (address) => {
const buyLicense = deps.contracts.SellerLicense.methods.buy().encodeABI();
const toSend = deps.contracts.SNT.methods.approveAndCall(deps.contracts.SellerLicense._address, licensePrice, buyLicense);
const gas = await toSend.estimateGas({from: address});
return toSend.send({from: address, gas});
}));
console.log('Generating Offers...');
const tokens = [deps.contracts.SNT._address, '0x0000000000000000000000000000000000000000'];
const paymentMethods = [1, 2, 3];
const usernames = ['Jonathan', 'Iuri', 'Anthony', 'Barry', 'Richard', 'Ricardo'];
const locations = ['London', 'Montreal', 'Paris', 'Berlin'];
const currencies = ['USD', 'EUR'];
const offerStartIndex = 1;
const offerReceipts = await Promise.all(addresses.slice(offerStartIndex, offerStartIndex + 5).map(async (address) => {
const addOffer = deps.contracts.MetadataStore.methods.addOffer(
tokens[1],
// TODO un hardcode token and add `approve` in the escrow creation below
// tokens[Math.floor(Math.random() * tokens.length)],
address,
address,
locations[Math.floor(Math.random() * locations.length)],
currencies[Math.floor(Math.random() * currencies.length)],
usernames[Math.floor(Math.random() * usernames.length)],
[paymentMethods[Math.floor(Math.random() * paymentMethods.length)]],
0,
0,
Math.floor(Math.random() * 100),
arbitrator
);
const gas = await addOffer.estimateGas({from: address});
return addOffer.send({from: address, gas});
}));
console.log('Creating escrows and rating them...');
const val = 1000;
const feeAmount = Math.round(val * (feeMilliPercent / (100 * 1000)));
const buyerAddress = addresses[offerStartIndex];
const escrowStartIndex = offerStartIndex + 1;
let receipt, hash, signature, nonce, created, escrowId;
const PUBKEY_A = "0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
const PUBKEY_B = "0xBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB";
console.log('START', escrowStartIndex);
console.log('RECiptys', offerReceipts.length);
await Promise.all(addresses.slice(escrowStartIndex, escrowStartIndex + 1).map(async (creatorAddress, idx) => {
console.log('Index = ', idx - offerStartIndex + escrowStartIndex);
console.log('Address used:; ', creatorAddress);
console.log('OWNER', offerReceipts[idx - offerStartIndex + escrowStartIndex].events.OfferAdded.returnValues.owner);
const ethOfferId = offerReceipts[idx - offerStartIndex + escrowStartIndex].events.OfferAdded.returnValues.offerId;
// TODO when we re-enable creating tokens too, use this to know
// const token = offerReceipts[idx - offerStartIndex + escrowStartIndex].events.OfferAdded.returnValues.asset;
let gas;
// Create
hash = await deps.contracts.MetadataStore.methods.getDataHash(usernames[offerStartIndex], PUBKEY_A, PUBKEY_B).call({from: buyerAddress});
signature = await deps.web3.eth.sign(hash, buyerAddress);
nonce = await deps.contracts.MetadataStore.methods.user_nonce(buyerAddress).call();
const creation = deps.contracts.Escrow.methods.createEscrow(ethOfferId, val, 140, PUBKEY_A, PUBKEY_B, locations[offerStartIndex], usernames[offerStartIndex], nonce, signature);
gas = await creation.estimateGas({from: creatorAddress});
receipt = await creation.send({from: creatorAddress, gas: gas + 1000});
created = receipt.events.Created;
escrowId = created.returnValues.escrowId;
// Fund
const fund = deps.contracts.Escrow.methods.fund(escrowId);
gas = await fund.estimateGas({from: creatorAddress, value: val + feeAmount});
receipt = await fund.send({from: creatorAddress, gas: gas + 1000, value: val + feeAmount});
// Release
const release = deps.contracts.Escrow.methods.release(escrowId);
gas = await release.estimateGas({from: creatorAddress});
receipt = await release.send({from: creatorAddress, gas: gas + 1000});
// Rate
const rating = Math.floor(Math.random() * 5) + 1;
const rate = deps.contracts.Escrow.methods.rateTransaction(escrowId, rating);
gas = await rate.estimateGas({from: buyerAddress});
await rate.send({from: buyerAddress, gas: gas + 1000});
}));
/*
console.log('Creating arbitrations');
await Promise.all(addresses.slice(escrowStartIndex, 5).map(async (creatorAddress, idx) => {
const ethOfferId = offerReceipts[idx - offerStartIndex + escrowStartIndex].events.OfferAdded.returnValues.offerId;
let gas, receipt;
const creation = deps.contracts.Escrow.methods.create_and_fund(buyerAddress, ethOfferId, val, expirationTime, FIAT, 13555);
gas = await creation.estimateGas({from: creatorAddress, value: val + feeAmount});
receipt = await creation.send({from: creatorAddress, value: val + feeAmount, gas: gas + 1000});
const created = receipt.events.Created;
const escrowId = created.returnValues.escrowId;
const pay = deps.contracts.Escrow.methods.pay(escrowId);
gas = await pay.estimateGas({from: buyerAddress});
receipt = await pay.send({from: buyerAddress, gas: gas + 1000});
const openCase = deps.contracts.Escrow.methods.openCase(escrowId, 'My Motive is...');
gas = await openCase.estimateGas({from: buyerAddress});
receipt = await openCase.send({from: buyerAddress, gas: gas + 1000});
}));
*/
const accounts = await Promise.all(addresses.map(async(address) => {
const ethBalance = await deps.web3.eth.getBalance(address);
const sntBalance = await deps.contracts.SNT.methods.balanceOf(address).call();
const isLicenseOwner = await deps.contracts.SellerLicense.methods.isLicenseOwner(address).call();
let user = {};
let offers = [];
const isUser = await deps.contracts.MetadataStore.methods.users(address).call();
if (isUser) {
user = await deps.contracts.MetadataStore.methods.users(address).call();
const offerIds = await deps.contracts.MetadataStore.methods.getOfferIds(address).call();
offers = await Promise.all(offerIds.map(async(offerId) => (
deps.contracts.MetadataStore.methods.offer(offerId).call()
)));
}
return {
address,
isLicenseOwner,
isUser,
user,
offers,
ethBalance: deps.web3.utils.fromWei(ethBalance),
sntBalance: deps.web3.utils.fromWei(sntBalance)
};
}));
console.log('Summary:');
console.log('######################');
accounts.forEach((account) => {
console.log(`Address: ${account.address}:`);
console.log(`License Owner: ${account.isLicenseOwner} ETH: ${account.ethBalance} SNT: ${account.sntBalance}`);
console.log(`Is User: ${account.isUser} Username: ${account.user.username || 'N/A'} Offers: ${account.offers.length}`);
console.log('');
});
} catch (e) {
console.log("------- data seeding error ------- ");
console.dir(e);
}
};

View File

@ -0,0 +1 @@
dev_password

View File

@ -0,0 +1,39 @@
module.exports = {
// default applies to all environments
default: {
enabled: true,
available_providers: ["ens"],
provider: "ens"
},
// default environment, merges with the settings in default
// assumed to be the intended environment by `embark run`
development: {
register: {
rootDomain: "embark.eth",
subdomains: {
'status': '0x1a2f3b98e434c02363f3dac3174af93c1d690914'
}
}
},
// merges with the settings in default
// used with "embark run privatenet"
privatenet: {
},
// merges with the settings in default
// used with "embark run testnet"
testnet: {
},
// merges with the settings in default
// used with "embark run livenet"
livenet: {
},
// you can name an environment with specific settings and then specify with
// "embark run custom_name" or "embark blockchain custom_name"
//custom_name: {
//}
};

View File

@ -0,0 +1,3 @@
module.exports = {
enabled: true
};

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,18 @@
{
"config": {
"homesteadBlock": 0,
"byzantiumBlock": 0,
"daoForkSupport": true
},
"nonce": "0x0000000000000042",
"difficulty": "0x0",
"alloc": {
"0x3333333333333333333333333333333333333333": {"balance": "15000000000000000000"}
},
"mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"coinbase": "0x3333333333333333333333333333333333333333",
"timestamp": "0x00",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"extraData": "0x",
"gasLimit": "0x7a1200"
}

View File

@ -0,0 +1 @@
dev_password

View File

@ -0,0 +1,60 @@
module.exports = {
// default applies to all environments
default: {
enabled: true,
ipfs_bin: "ipfs",
available_providers: ["ipfs"],
upload: {
provider: "ipfs",
host: "localhost",
port: 5001
},
dappConnection: [
{
provider: "ipfs",
host: "localhost",
port: 5001,
getUrl: "http://localhost:8080/ipfs/"
}
]
// Configuration to start Swarm in the same terminal as `embark run`
/*,account: {
address: "YOUR_ACCOUNT_ADDRESS", // Address of account accessing Swarm
password: "PATH/TO/PASSWORD/FILE" // File containing the password of the account
},
swarmPath: "PATH/TO/SWARM/EXECUTABLE" // Path to swarm executable (default: swarm)*/
},
// default environment, merges with the settings in default
// assumed to be the intended environment by `embark run`
development: {
enabled: true,
upload: {
provider: "ipfs",
host: "localhost",
port: 5001,
getUrl: "http://localhost:8080/ipfs/"
}
},
// merges with the settings in default
// used with "embark run privatenet"
privatenet: {
},
// merges with the settings in default
// used with "embark run testnet"
testnet: {
enabled: false
},
// merges with the settings in default
// used with "embark run livenet"
livenet: {
},
// you can name an environment with specific settings and then specify with
// "embark run custom_name"
//custom_name: {
//}
};

View File

@ -0,0 +1 @@
test_password

View File

@ -0,0 +1,3 @@
module.exports = {
enabled: false
};

View File

@ -0,0 +1,42 @@
{
"name": "status-teller-network",
"version": "0.0.1",
"description": "",
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"author": "",
"license": "ISC",
"homepage": "",
"devDependencies": {},
"dependencies": {
"babel-loader": "^8.0.5",
"bn.js": "^4.11.8",
"clone-deep": "^4.0.1",
"elliptic": "^6.5.0",
"embark": "^4.0.2",
"embark-solc": "^4.0.3",
"embark-solium": "0.1.0",
"embarkjs-connector-web3": "^4.0.0",
"eth-ens-namehash": "^2.0.8",
"ethereumjs-util": "^6.0.0",
"staking-pool": "^0.0.1",
"tabookey-gasless": "^0.4.0-beta2",
"web3": "1.0.0-beta.37",
"react": "^16.8.2",
"react-dom": "^16.8.2",
"react-scripts": "2.1.5"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": [
">0.2%",
"not dead",
"not ie <= 11",
"not op_mini all"
]
}

View File

@ -0,0 +1,25 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no"
/>
<title>Status Teller Network</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

View File

@ -0,0 +1,226 @@
{
"0x6341fd3daf94b748c72ced5a5b26028f2474f5f00d824504e4fa37a75767e177": {
"contracts": {
"0x3043b04ad856d169c8f0b0509c0bc63192dc7edd92d6933c58708298a0e381be": {
"name": "ENSRegistry",
"address": "0xe7410170f87102DF0055eB195163A03B7F2Bff4A"
},
"0x1b577600819d19882762a47860e0d23dad953ca145edee5e9d2e3d59a50ef0a5": {
"name": "KyberNetworkProxy",
"address": "0xF77eC7Ed5f5B9a5aee4cfa6FFCaC6A4C315BaC76"
},
"0xab973d34eccbfa264b59d1aff6bc7f8bc0f75d2a89d7e1bbad63d8a2cf73e765": {
"name": "RelayHub",
"address": "0xd216153c06e857cd7f72665e0af1d7d82172f494"
},
"0xc7736f10a733af68ff770bcb372acd39c464e9ac528c9bfdc053cdb33d289246": {
"name": "RLPReader",
"address": "0xae3940E52b649a1314f7d3025Ef94d6f337cCE92"
},
"0x6d4b2f1548974e845feb3ccce8937c463340f5d84922ab1720814fe9bb2862c3": {
"name": "StandardToken",
"address": "0x851F554A9Aa205182ca143F2A4c547785C5b9C37"
},
"0xe63becc7e5231b3f667f849428112631ddfddf638a1428bce5367ca054ad8286": {
"name": "DAI",
"address": "0x49BF147F3403879B829322cDE32919CA01960658"
},
"0x0a0933bde1f838ba180c1501539168600f549319d5ce32cbb9c7d0dc73ff0758": {
"name": "SellerLicenseProxy",
"address": "0xf714F5f462E0e68ad901F8C13e298569F6Fd661f"
},
"0x2593ad087110ec0f4c738870abc12933973759dd2b0aab743574cb2a92367107": {
"name": "ArbitrationLicenseProxy",
"address": "0xAbBC6D3990DB702ed00A51889248086327aF1A5A"
},
"0x66bc64129f6447bfd0b73a3909a7c3e7c598cf01e1d0c79b08f19d559fe34fd4": {
"name": "MiniMeTokenFactory",
"address": "0xbDe5EC593CBD896f4f7f01A8578792d96fae0486"
},
"0xc6fafd3c65e7e1c7b0addf0e6ba081862d057db14cea992db689d98e85dcdc9f": {
"name": "MKR",
"address": "0xf454112E88B6d7Fa2fE7538F4b84CfC5fD86CE54"
},
"0x2c3b03b9c312c790c5bd7fc46d6fc7ac647e42bf8ace12b8d64394abdad8fb21": {
"name": "MetadataStoreProxy",
"address": "0xF5b4F9d637c6b55152C5aaf1D5B58ae21c54a875"
},
"0x5c2c412b3c9f50bd52d751cd8b2622f0765e2a2a97d8f3bbb3e90e56b83a519e": {
"name": "EscrowProxy",
"address": "0x7d6394104f5175CB14bfAC56e807348F20Dc29f9"
},
"0x74573ee56f292d0d5e59f699e0225f72c68858d0677f62d52f4bea8e8651accc": {
"name": "SNT",
"address": "0x3C36db79598e7902b5D726af7C7d406d5Da8aF14"
},
"0x29e50f90a18564fa4e93664f08c1a5ea3508443297423be37673a9cd6dd875ed": {
"name": "SellerLicense",
"address": "0x1bcA5BCbf2aA8aafF2429e15a9A9C8B6a0681325"
},
"0xedf3e9504fbcdfca9661216a25c48edb4a02640bd9244bef4f74fb7c82dd64e5": {
"name": "EscrowRelay",
"address": "0x22CDf9a803d791638782fC152E0E4F472EA660f9"
},
"0x4e5c08aa149c247d984571083361f972365ec846ad1e1d5c6bcf58474d5fa536": {
"name": "KyberFeeBurner",
"address": "0x1e9420E2Ab23cC5f1fb4f7e2077A586A09Bb69aF"
},
"0xf396e0f6562aa338281d33b54e17230616bcaf844b80b15d5834786868372045": {
"name": "ArbitrationLicense",
"address": "0x53202def8E6e04a7Fb743532e5e1646f32BB120d"
},
"0x5518671e17ebae7843ec9b113429b8b8379d7cffc827d2259c9c0174dcf8e238": {
"name": "MetadataStore",
"address": "0x3b5b0820328Aa60B17c2192e94a8bba4F5020CCA"
},
"0x01d5f876801e3d01e55439fa123643fc75c09bb1d5779ed5869841c99b8b3a7a": {
"name": "Escrow",
"address": "0x5b8608DF47d4d5c16ED1ac9acc4EFC57C6E3453b"
},
"0xa8c1e6b830fe9e3d5c3314352b7db90e124aa361eb8e838555ec37a91f8562b2": {
"name": "KyberNetworkProxy",
"address": "0xF77eC7Ed5f5B9a5aee4cfa6FFCaC6A4C315BaC76"
},
"0xbb4a95581b19198f074b391c4cfacd9b64ce50c44bd14d3baa65c8c730517bc1": {
"name": "RelayHub",
"address": "0xd216153c06e857cd7f72665e0af1d7d82172f494"
},
"0x98398cd906dca8ecdc57c37049ae0ef1ee803489aab046e7a3bade70c755c403": {
"name": "DAI",
"address": "0x27FFe7e38D36e32EcE6E1FF12f93D3980b06CA3c"
},
"0xec59f5b11a29acafcfb616cb84d3985a918f6d816476c93c04d913150b0a499c": {
"name": "MKR",
"address": "0xec61AE9f48d7714802f7252F66e46a72Fc79Fc4C"
},
"0xad3c5e94cf912009bf4940e44a7e06b2942bed2ad736f253638b03de9bcea914": {
"name": "SellerLicenseProxy",
"address": "0x65A765505793d4B7D0872Ac266b5a8B745C2Ac0c"
},
"0x8a78d902010f6152d9d2394bf2318c3a848fdeada26be6b5ef781b6a84d6ea46": {
"name": "ArbitrationLicenseProxy",
"address": "0xF3F8501044A6519981b0763659bCAD0763d9a911"
},
"0x2ce4f7945512a0e430f4d73c95e955a937f9ca2abb9fe6c062b60d287c07d93b": {
"name": "EscrowProxy",
"address": "0x8B8034110DDB679746CFd2eA2B3aa35bBc6A8A5B"
},
"0x58c7aa33d79ff2ac4be5b4a4a031dba002bbb4d1cfad405cd0067c5cb1260306": {
"name": "RLPReader",
"address": "0x99f3191669Ce05d42E1912733985D2810a903DaA"
},
"0xadd39b686828e5e86cd78830e086b6f46b855f0d8267fddc054f46dc6ebf362f": {
"name": "StandardToken",
"address": "0x5E130075834810ce0e984132Ca7165382CB87e94"
},
"0x26114ac0d7c8eb9376fca18080a5b8aa87da83a49dbfa789a2299329b6d92836": {
"name": "MetadataStoreProxy",
"address": "0xB28126346603506Ea40a032765A9b851fE89c1eF"
},
"0x4a5a82fac72b1cc19c644e794a0229286d721daf2e345b95ebf43ec7d8de9fbe": {
"name": "MiniMeTokenFactory",
"address": "0xd232aba419100552276fF63A7863F3Fba6412f5d"
},
"0x6a1df6e1075bdf381d1bda8c3c8fb9b6ab603cebe981876048ef312b5ca5420d": {
"name": "SNT",
"address": "0xFEdeC04E4cde2c4cd27a199ba21f6736171A3C39"
},
"0x04081290301fe85cdb2b7c17f9af493bea3ea9e18348b415aa23153ca96e6ba7": {
"name": "KyberFeeBurner",
"address": "0x16292D56F1F9a4de4f09b2D154577984D7e87417"
},
"0x05f432711eaf049294f51b5af77f22ef5812202604dc9b307bba44405934b772": {
"name": "ArbitrationLicense",
"address": "0xE1Fa7c2857074cEdc20bf4070F24B48834360C75"
},
"0xa37eeba57e75f8e44fb930146800660c3dd2568891f662f6e6b013367572e80e": {
"name": "EscrowRelay",
"address": "0x9cB4d8dD0eE94Aa41E229b465952129dF93FBC2f"
},
"0x4919ec5e817e014dcfb4714e993a4eabd4a769afca5ee030d707e57540afff2c": {
"name": "SellerLicense",
"address": "0xf3963Ef61e3b380C6aF1FD197A14bDAc34860645"
},
"0x963bf16d22d118df1d09bca15c9902faee61dcb3011e0c9eacfe445f9de22e4b": {
"name": "MetadataStore",
"address": "0xf72213839a52028b4fc84f8cA90a0D049c1eD314"
},
"0xdb63d70594ddec6de1a9da2d6ca73a16c66ac4e46beb6d2532b1679052b90015": {
"name": "Escrow",
"address": "0xF63290032cfb043B704a22CC1aFaf90A810db2A5"
},
"0x4baef1235421717d9c68feba5ece50f02d404b33a5635fdba26c595395741f50": {
"name": "KyberNetworkProxy",
"address": "0xF77eC7Ed5f5B9a5aee4cfa6FFCaC6A4C315BaC76"
},
"0x669233c46987267322461347ed5f13edcd498da58f719976dc14d71223307173": {
"name": "RelayHub",
"address": "0xd216153c06e857cd7f72665e0af1d7d82172f494"
},
"0x0805905c13ba69e6d0109c73318eb64b5cfe02a9ba8a263d01fa70a18003d329": {
"name": "RLPReader",
"address": "0x455193E1A0434C6dBE00E90Ba198e36acEd86A4b"
},
"0xa42358beddec6294a6d64026e2456ff06910f73e12b4766043c97a52f60f1dd9": {
"name": "EscrowProxy",
"address": "0x98395d91FC1B8066C3B97b7c28663CA81Fb1531b"
},
"0x656bcc57b1941554e2398dac4529a4a239bf1c46e1f748394e95beadee770748": {
"name": "DAI",
"address": "0x087BAA82f7204Ed930ddD40c3f1b2c9aA25B68a1"
},
"0x7230d856dc70e1ec6e3b006a3456dd2d3eb06b2642e299c394c579f8244b560d": {
"name": "SellerLicenseProxy",
"address": "0x028De9aaE8Ab19C7ab643fBe8d5487DB0E492943"
},
"0x6ff69b61337b8ce3f6f7d26db2fb4dd8ed7571afa418a26cc392a74e513a3393": {
"name": "ArbitrationLicenseProxy",
"address": "0x3637Bac17065b06058D24527f578Ad8D04deEa4e"
},
"0x39849cbac15f0403188407c078f7eff4b5b96d87a95ca1f46060ae7af16dba9a": {
"name": "MetadataStoreProxy",
"address": "0x2a19a72D09b67967f066927A7fC52042e357dedc"
},
"0xfe5c3d5f91ffe42e177bc63eac53820d4ae48e7966d60db540cd51ae514cd55a": {
"name": "MKR",
"address": "0x701815622f4b46C50d19D1540525FEc243477CA2"
},
"0xd696f0155d503f9bbe17d0eca3abe4c52df7d2e19ca4db5887ac9cb769025040": {
"name": "MiniMeTokenFactory",
"address": "0x1f37e6243EeaEB2241C2e6a6D8944B81075Eb48D"
},
"0xc3c3cda956a344ef1c3e2d589bc5a6743c7c656ba5e1640e57568c620bdbb6ac": {
"name": "StandardToken",
"address": "0xd0e91f9CA8A4514001F7735158258eFf5C22d93D"
},
"0xf8b89ba816f68f551ef153790cd06aeba42314490d4c75545fa589d0d470b90d": {
"name": "SNT",
"address": "0x1e828cdABdf7F9a60654F8090900Fee89c8A2965"
},
"0xc74ae73d1af6af27ff89d7dffa1a4af26a5c3727514d33fb2d31adfc70e909b7": {
"name": "KyberFeeBurner",
"address": "0xE215B1eb240666BA7E7b845c849A55a110056345"
},
"0x9eda55b2b844000b7eb0ea55a110704b0209d25ecfe468367eab055479c055db": {
"name": "SellerLicense",
"address": "0x10A18eBfB1792C229DE82c08e33212896A50c201"
},
"0xeef7ea6c52b68a48279ee6706ca43b85afd3a117a3535002831d9aa805bb47dd": {
"name": "EscrowRelay",
"address": "0x3b039b735572ECa9B620541c65657F67D007F392"
},
"0xdf0516e57e1ff6ffcf014460c65e7c97004153dacb13763a67b2c5c44a07b887": {
"name": "ArbitrationLicense",
"address": "0x2F5D839162A3b59D9a6FCf2df0f5b9f035AE36ff"
},
"0x6a7ffacd295a88fd56ec93448f3b68061ea4f03fcba323130f051882791a69be": {
"name": "MetadataStore",
"address": "0x41578fB96e021A84a79447cbA9bc4D2A4523bbb8"
},
"0x84d2578600c1c6bc229531ff93e87c52f9379091fffed2fb4e2bd2dfcb63ab99": {
"name": "Escrow",
"address": "0xBb1C1407989936B787F7413F4360C71bE715BDd0"
}
}
}
}

View File

@ -0,0 +1,62 @@
{
"0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d": {
"contracts": {
"0x3043b04ad856d169c8f0b0509c0bc63192dc7edd92d6933c58708298a0e381be": {
"name": "ENSRegistry",
"address": "0x112234455c3a32fd11230c42e7bccd4a84e02010"
},
"0x4af953c2758aa8edcbcbeef13926c1cf17e7c29aea055ec99022260ca7c3d0c7": {
"name": "KyberNetworkProxy",
"address": "0x818E6FECD516Ecc3849DAf6845e3EC868087B755"
},
"0x72ef506bf1c7f442f2d4c5bfc97ba6061d9fb1bbbc345adbe7eb18ee583b9329": {
"name": "SNT",
"address": "0xc55cf4b03948d7ebc8b9e8bad92643703811d162"
},
"0x4f96b534860cd72e9388b7373c14f4d9bdc201a81bdccdc1b2922865de029497": {
"name": "RelayHub",
"address": "0x1349584869A1C7b8dc8AE0e93D8c15F5BB3B4B87"
},
"0x68dfb5ad82a1f92f56b313d36ed2f79376c88b2f37d58d2846e66491282eaf13": {
"name": "KyberFeeBurner",
"address": "0xb009e41d5c7daC38e02fCDb6ebb97f53ed6A950c"
},
"0x084dcee6b38fbfeacf8371e068678793efc36cf8a9a1d83427e70ef8613852d3": {
"name": "EscrowProxy",
"address": "0x06Ea1813Aa7CFf6D3eF109Be941C469F0c3e6cc6"
},
"0x33ce98f7b733f04dce6121e52d066acaedb6ca1e83be5ccc73d20391c02537f5": {
"name": "MetadataStoreProxy",
"address": "0xB7d1bC73F6220E214CFE2bE795462637f67d2023"
},
"0x9d4080fcd5bf7499bba97bd8c330b11cc064c604c2aa6cc79aef7ec7783f354b": {
"name": "SellerLicenseProxy",
"address": "0xB84b30c98BA8ff2A7f5DB3a14b438863FaBdDDDf"
},
"0xf7748af69578cd769431eec4a0daa1e13b6a51968170bcf37fd93a60edd5b249": {
"name": "SellerLicense",
"address": "0xbDEBeE4F6F4c64540309a8fE827891ea70177539"
},
"0xdc769c37e39d0464d5178e6c94a8d95bde8de0328f9ff694ab059c1ccf004fc6": {
"name": "ArbitrationLicense",
"address": "0x1315b1bC9532c08c44B5C4Ddc5F9c6A1831D272f"
},
"0xb61ac3f971c25164c5cf00fe4616c8c9ad0759848d62c9fb803bdb4914af19eb": {
"name": "ArbitrationLicenseProxy",
"address": "0x79647AD1d4bAFBbDb326A096A113aFc92158b03d"
},
"0x90be19004e959c27194e39559cbc7461b427237b7feec7de5166fae42877463a": {
"name": "EscrowRelay",
"address": "0x04d54b422DC59b8A539F4C2745b62649bb7541Ee"
},
"0xc605b00d6ddfbced735137ff16dc632d76770a1d1a2f3c68420de840c4b31e28": {
"name": "MetadataStore",
"address": "0x8bA9762695CAf7828DaFB279781f4574a5e7CAa0"
},
"0xa5d7276422517e35c151dad3d6eb01fbbde1e0594927ef876d7df8a9b5518bf2": {
"name": "Escrow",
"address": "0x11b32dA9418d82292dB926d32E558d62337Fd855"
}
}
}
}

View File

@ -0,0 +1,4 @@
import React from 'react';
import ReactDOM from 'react-dom';
ReactDOM.render(<h1>Test</h1>, document.getElementById('root'));

File diff suppressed because it is too large Load Diff