Contract Name:
SyntheticTokenV1
Contract Source Code:
// SPDX-License-Identifier: MIT
pragma solidity ^0.5.16;
pragma experimental ABIEncoderV2;
import {IERC20} from "../token/IERC20.sol";
import {SafeMath} from "../lib/SafeMath.sol";
import {Amount} from "../lib/Amount.sol";
import {SyntheticStorage} from "./SyntheticStorage.sol";
import {Adminable} from "../lib/Adminable.sol";
contract SyntheticTokenV1 is Adminable, SyntheticStorage, IERC20 {
using SafeMath for uint256;
using Amount for Amount.Principal;
/* ========== Events ========== */
event MinterAdded(address _minter, uint256 _limit);
event MinterRemoved(address _minter);
event MinterLimitUpdated(address _minter, uint256 _limit);
event MetadataChanged();
/* ========== Modifiers ========== */
modifier onlyMinter() {
require(
_minters[msg.sender] == true,
"SyntheticToken: only callable by minter"
);
_;
}
/* ========== Init Function ========== */
/**
* @dev Initialise the synthetic token
*
* @param name The name of the token
* @param symbol The symbol of the token
* @param version The version number of this token
*/
function init(
string memory name,
string memory symbol,
uint8 version
)
public
onlyAdmin
{
_name = name;
_symbol = symbol;
_version = version;
}
/* ========== View Functions ========== */
function name()
external
view
returns (string memory)
{
return _name;
}
function symbol()
external
view
returns (string memory)
{
return _symbol;
}
function decimals()
external
pure
returns (uint8)
{
return 18;
}
function version()
external
view
returns (uint8)
{
return _version;
}
function totalSupply()
public
view
returns (uint256)
{
return _totalSupply;
}
function balanceOf(
address account
)
public
view
returns (uint256)
{
return _balances[account];
}
function allowance(
address owner,
address spender
)
public
view
returns (uint256)
{
return _allowances[owner][spender];
}
function getAllMinters()
external
view
returns (address[] memory)
{
return _mintersArray;
}
function isValidMinter(
address _minter
)
external
view
returns (bool)
{
return _minters[_minter];
}
function getMinterIssued(
address _minter
)
external
view
returns (Amount.Principal memory)
{
return _minterIssued[_minter];
}
function getMinterLimit(
address _minter
)
external
view
returns (uint256)
{
return _minterLimits[_minter];
}
/* ========== Admin Functions ========== */
/**
* @dev Add a new minter to the synthetic token.
*
* @param _minter The address of the minter to add
* @param _limit The starting limit for how much this synth can mint
*/
function addMinter(
address _minter,
uint256 _limit
)
external
onlyAdmin
{
require(
_minters[_minter] != true,
"Minter already exists"
);
_mintersArray.push(_minter);
_minters[_minter] = true;
_minterLimits[_minter] = _limit;
emit MinterAdded(_minter, _limit);
}
/**
* @dev Remove a minter from the synthetic token
*
* @param _minter Address to remove the minter
*/
function removeMinter(
address _minter
)
external
onlyAdmin
{
require(
_minters[_minter] == true,
"Minter does not exist"
);
for (uint i = 0; i < _mintersArray.length; i++) {
if (address(_mintersArray[i]) == _minter) {
delete _mintersArray[i];
_mintersArray[i] = _mintersArray[_mintersArray.length - 1];
_mintersArray.length--;
break;
}
}
delete _minters[_minter];
delete _minterLimits[_minter];
emit MinterRemoved(_minter);
}
/**
* @dev Update the limit of the minter
*
* @param _minter The address of the minter to set
* @param _limit The new limit to set for this address
*/
function updateMinterLimit(
address _minter,
uint256 _limit
)
public
onlyAdmin
{
require(
_minters[_minter] == true,
"Minter does not exist"
);
_minterLimits[_minter] = _limit;
emit MinterLimitUpdated(_minter, _limit);
}
/* ========== Minter Functions ========== */
/**
* @dev Mint synthetic tokens
*
* @notice Can only be called by a valid minter.
*
* @param to The destination to mint the synth to
* @param value The amount of synths to mint
*/
function mint(
address to,
uint256 value
)
external
onlyMinter
{
Amount.Principal memory issuedAmount = _minterIssued[msg.sender].add(
Amount.Principal({ sign: true, value: value })
);
require(
issuedAmount.value <= _minterLimits[msg.sender] || issuedAmount.sign == false,
"Minter limit reached"
);
_minterIssued[msg.sender] = issuedAmount;
_mint(to, value);
}
/**
* @dev Burn synthetic tokens
*
* @notice Can only be called by a valid minter.
*
* @param from The destination to burn the synth from
* @param value The amount of the synth to burn
*/
function burn(
address from,
uint256 value
)
external
onlyMinter
{
_minterIssued[msg.sender] = _minterIssued[msg.sender].sub(
Amount.Principal({ sign: true, value: value })
);
_burn(from, value);
}
/**
* @dev Transfer any collateral held to another address
*
* @param token The address of the token to transfer
* @param to The destination to send the collateral to
* @param value The amount of the tokens to transfer
*/
function transferCollateral(
address token,
address to,
uint256 value
)
external
onlyMinter
returns (bool)
{
return IERC20(token).transfer(
to,
value
);
}
/* ========== ERC20 Functions ========== */
function transfer(
address recipient,
uint256 amount
)
public
returns (bool)
{
_transfer(msg.sender, recipient, amount);
return true;
}
function approve(
address spender,
uint256 amount
)
public
returns (bool)
{
_approve(msg.sender, spender, amount);
return true;
}
function transferFrom(
address sender,
address recipient,
uint256 amount
)
public
returns (bool)
{
_transfer(sender, recipient, amount);
_approve(
sender,
msg.sender,
_allowances[sender][msg.sender].sub(amount)
);
return true;
}
/* ========== Internal Functions ========== */
function _transfer(
address sender,
address recipient,
uint256 amount
)
internal
{
require(
sender != address(0),
"ERC20: transfer from the zero address"
);
require(
recipient != address(0),
"ERC20: transfer to the zero address"
);
_balances[sender] = _balances[sender].sub(amount);
_balances[recipient] = _balances[recipient].add(amount);
emit Transfer(sender, recipient, amount);
}
function _mint(
address account,
uint256 amount
)
internal
{
require(account != address(0), "ERC20: mint to the zero address");
_totalSupply = _totalSupply.add(amount);
_balances[account] = _balances[account].add(amount);
emit Transfer(address(0), account, amount);
}
function _burn(
address account,
uint256 amount
)
internal
{
require(account != address(0), "ERC20: burn from the zero address");
_balances[account] = _balances[account].sub(amount);
_totalSupply = _totalSupply.sub(amount);
emit Transfer(account, address(0), amount);
}
function _approve(
address owner,
address spender,
uint256 amount
)
internal
{
require(owner != address(0), "ERC20: approve from the zero address");
require(spender != address(0), "ERC20: approve to the zero address");
_allowances[owner][spender] = amount;
emit Approval(owner, spender, amount);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.5.16;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `recipient`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(
address recipient,
uint256 amount
)
external
returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(
address owner,
address spender
)
external
view
returns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(
address spender,
uint256 amount
)
external
returns (bool);
/**
* @dev Moves `amount` tokens from `sender` to `recipient` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(
address sender,
address recipient,
uint256 amount
)
external
returns (bool);
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(
address indexed from,
address indexed to,
uint256 value
);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(
address indexed owner,
address indexed spender,
uint256 value
);
}
pragma solidity ^0.5.16;
library SafeMath {
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
if (a == 0) {
return 0;
}
uint256 c = a * b;
require(c / a == b, "SafeMath: multiplication overflow");
return c;
}
function div(uint256 a, uint256 b) internal pure returns (uint256) {
require(b > 0, "SafeMath: division by zero");
uint256 c = a / b;
return c;
}
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
require(b <= a, "SafeMath: subtraction overflow");
uint256 c = a - b;
return c;
}
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a, "SafeMath: addition overflow");
return c;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.5.16;
pragma experimental ABIEncoderV2;
import {SafeMath} from "../lib/SafeMath.sol";
import {Math} from "../lib/Math.sol";
library Amount {
using Math for uint256;
using SafeMath for uint256;
// ============ Constants ============
uint256 constant BASE = 10**18;
// A Principal Amount is an amount that's been adjusted by an index
struct Principal {
bool sign; // true if positive
uint256 value;
}
function zero()
internal
pure
returns (Principal memory)
{
return Principal({
sign: false,
value: 0
});
}
function sub(
Principal memory a,
Principal memory b
)
internal
pure
returns (Principal memory)
{
return add(a, negative(b));
}
function add(
Principal memory a,
Principal memory b
)
internal
pure
returns (Principal memory)
{
Principal memory result;
if (a.sign == b.sign) {
result.sign = a.sign;
result.value = SafeMath.add(a.value, b.value);
} else {
if (a.value >= b.value) {
result.sign = a.sign;
result.value = SafeMath.sub(a.value, b.value);
} else {
result.sign = b.sign;
result.value = SafeMath.sub(b.value, a.value);
}
}
return result;
}
function equals(
Principal memory a,
Principal memory b
)
internal
pure
returns (bool)
{
if (a.value == b.value) {
if (a.value == 0) {
return true;
}
return a.sign == b.sign;
}
return false;
}
function negative(
Principal memory a
)
internal
pure
returns (Principal memory)
{
return Principal({
sign: !a.sign,
value: a.value
});
}
function calculateAdjusted(
Principal memory a,
uint256 index
)
internal
pure
returns (uint256)
{
return Math.getPartial(a.value, index, BASE);
}
function calculatePrincipal(
uint256 value,
uint256 index,
bool sign
)
internal
pure
returns (Principal memory)
{
return Principal({
sign: sign,
value: Math.getPartial(value, BASE, index)
});
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.5.16;
pragma experimental ABIEncoderV2;
import {SafeMath} from "./SafeMath.sol";
/**
* @title Math
*
* Library for non-standard Math functions
*/
library Math {
using SafeMath for uint256;
// ============ Library Functions ============
/*
* Return target * (numerator / denominator).
*/
function getPartial(
uint256 target,
uint256 numerator,
uint256 denominator
)
internal
pure
returns (uint256)
{
return target.mul(numerator).div(denominator);
}
function to128(
uint256 number
)
internal
pure
returns (uint128)
{
uint128 result = uint128(number);
require(
result == number,
"Math: Unsafe cast to uint128"
);
return result;
}
function min(
uint256 a,
uint256 b
)
internal
pure
returns (uint256)
{
return a < b ? a : b;
}
function max(
uint256 a,
uint256 b
)
internal
pure
returns (uint256)
{
return a > b ? a : b;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.5.16;
pragma experimental ABIEncoderV2;
import {Amount} from "../lib/Amount.sol";
contract SyntheticStorageV1 {
/**
* @dev ERC20 Properties
*/
uint8 internal _version;
string internal _name;
string internal _symbol;
uint256 internal _totalSupply;
mapping (address => uint256) internal _balances;
mapping (address => mapping (address => uint256)) internal _allowances;
/**
* @dev Minter Properties
*/
address[] internal _mintersArray;
mapping(address => bool) internal _minters;
mapping(address => uint256) internal _minterLimits;
mapping(address => Amount.Principal) internal _minterIssued;
}
contract SyntheticStorage is SyntheticStorageV1 { /* solium-disable-line no-empty-blocks */ }
// SPDX-License-Identifier: MIT
pragma solidity ^0.5.16;
pragma experimental ABIEncoderV2;
import { Storage } from "./Storage.sol";
/**
* @title Adminable
* @author dYdX
*
* @dev EIP-1967 Proxy Admin contract.
*/
contract Adminable {
/**
* @dev Storage slot with the admin of the contract.
* This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1.
*/
bytes32 internal constant ADMIN_SLOT =
0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
/**
* @dev Modifier to check whether the `msg.sender` is the admin.
* If it is, it will run the function. Otherwise, it will revert.
*/
modifier onlyAdmin() {
require(
msg.sender == getAdmin(),
"Adminable: caller is not admin"
);
_;
}
/**
* @return The EIP-1967 proxy admin
*/
function getAdmin()
public
view
returns (address)
{
return address(uint160(uint256(Storage.load(ADMIN_SLOT))));
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.5.16;
pragma experimental ABIEncoderV2;
library Storage {
/**
* @dev Performs an SLOAD and returns the data in the slot.
*/
function load(
bytes32 slot
)
internal
view
returns (bytes32)
{
bytes32 result;
/* solium-disable-next-line security/no-inline-assembly */
assembly {
result := sload(slot)
}
return result;
}
/**
* @dev Performs an SSTORE to save the value to the slot.
*/
function store(
bytes32 slot,
bytes32 value
)
internal
{
/* solium-disable-next-line security/no-inline-assembly */
assembly {
sstore(slot, value)
}
}
}