Skip to content

Instantly share code, notes, and snippets.

@xlab
Last active February 6, 2019 23:40
Show Gist options
  • Save xlab/303f2c0a7b594f29f916eca113ad2612 to your computer and use it in GitHub Desktop.
Save xlab/303f2c0a7b594f29f916eca113ad2612 to your computer and use it in GitHub Desktop.
pragma solidity ^0.5.1;
interface TokenRecipient {
function receiveApproval(address _from, uint256 _value, address _token, bytes calldata _extraData) external;
}
contract ERC20Interface {
function totalSupply() public view returns (uint256);
function balanceOf(address _owner) public view returns (uint256 balance);
function transfer(address _to, uint256 _value) public returns (bool success);
function transferFrom(address _from, address _to, uint256 _value) public returns (bool success);
function approve(address _spender, uint256 _value) public returns (bool success);
function approveAndCall(address _spender, uint256 _value, bytes memory _extraData) public returns (bool success);
function allowance(address _owner, address _spender) public view returns (uint256 remaining);
function burn(uint256 _value, string memory _note) public returns (bool success);
event Transfer(address indexed _from, address indexed _to, uint256 _value);
event Approval(address indexed _owner, address indexed _spender, uint256 _value);
event Burn(address indexed _burner, uint256 _value, string _note);
}
contract LockRequestable {
uint256 public lockRequestCount;
constructor() public {
lockRequestCount = 0;
}
function generateLockId() internal returns (bytes32 lockId) {
return keccak256(
abi.encodePacked(blockhash(block.number - 1), address(this), ++lockRequestCount)
);
}
}
contract CustodianUpgradeable is LockRequestable {
struct CustodianChangeRequest {
address proposedNew;
}
address public custodian;
mapping (bytes32 => CustodianChangeRequest) public custodianChangeReqs;
constructor(address _custodian) public LockRequestable() {
custodian = _custodian;
}
modifier onlyCustodian {
require(msg.sender == custodian);
_;
}
function requestCustodianChange(address _proposedCustodian) public returns (bytes32 lockId) {
require(_proposedCustodian != address(0));
lockId = generateLockId();
custodianChangeReqs[lockId] = CustodianChangeRequest({
proposedNew: _proposedCustodian
});
emit CustodianChangeRequested(lockId, msg.sender, _proposedCustodian);
}
function confirmCustodianChange(bytes32 _lockId) public onlyCustodian {
custodian = getCustodianChangeReq(_lockId);
delete custodianChangeReqs[_lockId];
emit CustodianChangeConfirmed(_lockId, custodian);
}
function getCustodianChangeReq(bytes32 _lockId) private view returns (address _proposedNew) {
CustodianChangeRequest storage changeRequest = custodianChangeReqs[_lockId];
require(changeRequest.proposedNew != address(0));
return changeRequest.proposedNew;
}
event CustodianChangeRequested(
bytes32 _lockId,
address _msgSender,
address _proposedCustodian
);
event CustodianChangeConfirmed(bytes32 _lockId, address _newCustodian);
}
contract ERC20Impl is CustodianUpgradeable {
struct PendingPrint {
address receiver;
uint256 value;
}
struct PendingBurnLimits {
uint256 min;
uint256 max;
bool isSet;
}
ERC20Proxy public erc20Proxy;
ERC20Store public erc20Store;
uint256 public burnMin = 0;
uint256 public burnMax = 0;
address public sweeper;
bytes32 public sweepMsg;
mapping (address => bool) public sweptSet;
mapping (bytes32 => PendingPrint) public pendingPrintMap;
mapping (bytes32 => PendingBurnLimits) public pendingBurnLimitsMap;
constructor(
address _erc20Proxy,
address _erc20Store,
address _custodian,
address _sweeper
) public CustodianUpgradeable(_custodian) {
require(_sweeper != address(0));
erc20Proxy = ERC20Proxy(_erc20Proxy);
erc20Store = ERC20Store(_erc20Store);
sweeper = _sweeper;
sweepMsg = keccak256(
abi.encodePacked(address(this), "sweep")
);
}
modifier onlyProxy {
require(msg.sender == address(erc20Proxy));
_;
}
modifier onlySweeper {
require(msg.sender == sweeper);
_;
}
function approveWithSender(
address _sender,
address _spender,
uint256 _value
)
public
onlyProxy
returns (bool success)
{
require(_spender != address(0));
erc20Store.setAllowance(_sender, _spender, _value);
erc20Proxy.emitApproval(_sender, _spender, _value);
return true;
}
function approveAndCallWithSender(
address _sender,
address _spender,
uint256 _value,
bytes memory _extraData
)
public
onlyProxy
returns (bool success)
{
TokenRecipient spender = TokenRecipient(_spender);
if (approveWithSender(_sender, _spender, _value)) {
spender.receiveApproval(_sender, _value, msg.sender, _extraData);
return true;
}
}
function increaseApprovalWithSender(
address _sender,
address _spender,
uint256 _addedValue
)
public
onlyProxy
returns (bool success)
{
require(_spender != address(0));
uint256 currentAllowance = erc20Store.allowed(_sender, _spender);
uint256 newAllowance = currentAllowance + _addedValue;
require(newAllowance >= currentAllowance);
erc20Store.setAllowance(_sender, _spender, newAllowance);
erc20Proxy.emitApproval(_sender, _spender, newAllowance);
return true;
}
function decreaseApprovalWithSender(
address _sender,
address _spender,
uint256 _subtractedValue
)
public
onlyProxy
returns (bool success)
{
require(_spender != address(0));
uint256 currentAllowance = erc20Store.allowed(_sender, _spender);
uint256 newAllowance = currentAllowance - _subtractedValue;
require(newAllowance <= currentAllowance);
erc20Store.setAllowance(_sender, _spender, newAllowance);
erc20Proxy.emitApproval(_sender, _spender, newAllowance);
return true;
}
function requestPrint(address _receiver, uint256 _value) public returns (bytes32 lockId) {
require(_receiver != address(0));
lockId = generateLockId();
pendingPrintMap[lockId] = PendingPrint({
receiver: _receiver,
value: _value
});
emit PrintingLocked(lockId, _receiver, _value);
}
function confirmPrint(bytes32 _lockId) public onlyCustodian {
PendingPrint storage print = pendingPrintMap[_lockId];
address receiver = print.receiver;
require(receiver != address(0));
uint256 value = print.value;
delete pendingPrintMap[_lockId];
uint256 supply = erc20Store.totalSupply();
uint256 newSupply = supply + value;
if (newSupply >= supply) {
erc20Store.setTotalSupply(newSupply);
erc20Store.addBalance(receiver, value);
emit PrintingConfirmed(_lockId, receiver, value);
erc20Proxy.emitTransfer(address(0), receiver, value);
}
}
function print(address _receiver, uint256 _value) public onlyCustodian {
require(_receiver != address(0));
uint256 supply = erc20Store.totalSupply();
uint256 newSupply = supply + _value;
if (newSupply >= supply) {
erc20Store.setTotalSupply(newSupply);
erc20Store.addBalance(_receiver, _value);
erc20Proxy.emitTransfer(address(0), _receiver, _value);
}
}
function requestBurnLimitsChange(uint256 _newMin, uint256 _newMax) public returns (bytes32 lockId) {
require(_newMin <= _newMax, "min > max");
lockId = generateLockId();
pendingBurnLimitsMap[lockId] = PendingBurnLimits({
min: _newMin,
max: _newMax,
isSet: true
});
emit BurnLimitsChangeLocked(lockId, _newMin, _newMax);
}
function confirmBurnLimitsChange(bytes32 _lockId) public onlyCustodian {
PendingBurnLimits storage limits = pendingBurnLimitsMap[_lockId];
bool isSet = limits.isSet;
require(isSet == true, "not such lockId");
delete pendingBurnLimitsMap[_lockId];
emit BurnLimitsChangeConfirmed(_lockId, limits.min, limits.max);
burnMin = limits.min;
burnMax = limits.max;
}
function burnWithNote(address _burner, uint256 _value, string memory _note) public onlyProxy returns (bool success) {
if (burnMin > 0) {
require(_value >= burnMin, "below min burn limit");
}
if (burnMax > 0) {
require(_value <= burnMax, "exceeds max burn limit");
}
uint256 balanceOfSender = erc20Store.balances(_burner);
require(_value <= balanceOfSender);
erc20Store.setBalance(_burner, balanceOfSender - _value);
erc20Store.setTotalSupply(erc20Store.totalSupply() - _value);
erc20Proxy.emitBurn(_burner, _value, _note);
erc20Proxy.emitTransfer(_burner, address(0), _value);
return true;
}
function batchTransfer(address[] memory _tos, uint256[] memory _values) public returns (bool success) {
require(_tos.length == _values.length);
uint256 numTransfers = _tos.length;
uint256 senderBalance = erc20Store.balances(msg.sender);
for (uint256 i = 0; i < numTransfers; i++) {
address to = _tos[i];
require(to != address(0));
uint256 v = _values[i];
require(senderBalance >= v);
if (msg.sender != to) {
senderBalance -= v;
erc20Store.addBalance(to, v);
}
erc20Proxy.emitTransfer(msg.sender, to, v);
}
erc20Store.setBalance(msg.sender, senderBalance);
return true;
}
function enableSweep(uint8[] memory _vs, bytes32[] memory _rs,
bytes32[] memory _ss, address _to) public onlySweeper {
require(_to != address(0));
require((_vs.length == _rs.length) && (_vs.length == _ss.length));
uint256 numSignatures = _vs.length;
uint256 sweptBalance = 0;
for (uint256 i = 0; i < numSignatures; ++i) {
address from = ecrecover(sweepMsg, _vs[i], _rs[i], _ss[i]);
if (from != address(0)) {
sweptSet[from] = true;
uint256 fromBalance = erc20Store.balances(from);
if (fromBalance > 0) {
sweptBalance += fromBalance;
erc20Store.setBalance(from, 0);
erc20Proxy.emitTransfer(from, _to, fromBalance);
}
}
}
if (sweptBalance > 0) {
erc20Store.addBalance(_to, sweptBalance);
}
}
function replaySweep(address[] memory _froms, address _to) public onlySweeper {
require(_to != address(0));
uint256 lenFroms = _froms.length;
uint256 sweptBalance = 0;
for (uint256 i = 0; i < lenFroms; ++i) {
address from = _froms[i];
if (sweptSet[from]) {
uint256 fromBalance = erc20Store.balances(from);
if (fromBalance > 0) {
sweptBalance += fromBalance;
erc20Store.setBalance(from, 0);
erc20Proxy.emitTransfer(from, _to, fromBalance);
}
}
}
if (sweptBalance > 0) {
erc20Store.addBalance(_to, sweptBalance);
}
}
function transferFromWithSender(
address _sender,
address _from,
address _to,
uint256 _value
)
public
onlyProxy
returns (bool success)
{
require(_to != address(0));
uint256 balanceOfFrom = erc20Store.balances(_from);
require(_value <= balanceOfFrom);
uint256 senderAllowance = erc20Store.allowed(_from, _sender);
require(_value <= senderAllowance);
erc20Store.setBalance(_from, balanceOfFrom - _value);
erc20Store.addBalance(_to, _value);
erc20Store.setAllowance(_from, _sender, senderAllowance - _value);
erc20Proxy.emitTransfer(_from, _to, _value);
return true;
}
function transferWithSender(
address _sender,
address _to,
uint256 _value
)
public
onlyProxy
returns (bool success)
{
require(_to != address(0));
uint256 balanceOfSender = erc20Store.balances(_sender);
require(_value <= balanceOfSender);
erc20Store.setBalance(_sender, balanceOfSender - _value);
erc20Store.addBalance(_to, _value);
erc20Proxy.emitTransfer(_sender, _to, _value);
return true;
}
function totalSupply() public view returns (uint256) {
return erc20Store.totalSupply();
}
function balanceOf(address _owner) public view returns (uint256 balance) {
return erc20Store.balances(_owner);
}
function allowance(address _owner, address _spender) public view returns (uint256 remaining) {
return erc20Store.allowed(_owner, _spender);
}
event PrintingLocked(bytes32 _lockId, address _receiver, uint256 _value);
event PrintingConfirmed(bytes32 _lockId, address _receiver, uint256 _value);
event BurnLimitsChangeLocked(bytes32 _lockId, uint256 _newMin, uint256 _newMax);
event BurnLimitsChangeConfirmed(bytes32 _lockId, uint256 _newMin, uint256 _newMax);
}
contract ERC20ImplUpgradeable is CustodianUpgradeable {
struct ImplChangeRequest {
address proposedNew;
}
ERC20Impl public erc20Impl;
mapping (bytes32 => ImplChangeRequest) public implChangeReqs;
constructor(address _custodian) public CustodianUpgradeable(_custodian) {
erc20Impl = ERC20Impl(0x0);
}
modifier onlyImpl {
require(msg.sender == address(erc20Impl));
_;
}
function requestImplChange(address _proposedImpl) public returns (bytes32 lockId) {
require(_proposedImpl != address(0));
lockId = generateLockId();
implChangeReqs[lockId] = ImplChangeRequest({
proposedNew: _proposedImpl
});
emit ImplChangeRequested(lockId, msg.sender, _proposedImpl);
}
function confirmImplChange(bytes32 _lockId) public onlyCustodian {
erc20Impl = getImplChangeReq(_lockId);
delete implChangeReqs[_lockId];
emit ImplChangeConfirmed(_lockId, address(erc20Impl));
}
function getImplChangeReq(bytes32 _lockId) private view returns (ERC20Impl _proposedNew) {
ImplChangeRequest storage changeRequest = implChangeReqs[_lockId];
require(changeRequest.proposedNew != address(0));
return ERC20Impl(changeRequest.proposedNew);
}
event ImplChangeRequested(
bytes32 _lockId,
address _msgSender,
address _proposedImpl
);
event ImplChangeConfirmed(bytes32 _lockId, address _newImpl);
}
contract ERC20Proxy is ERC20Interface, ERC20ImplUpgradeable {
string public name;
string public symbol;
uint8 public decimals;
constructor(
string memory _name,
string memory _symbol,
uint8 _decimals,
address _custodian
) public ERC20ImplUpgradeable(_custodian) {
name = _name;
symbol = _symbol;
decimals = _decimals;
}
function totalSupply() public view returns (uint256) {
return erc20Impl.totalSupply();
}
function balanceOf(address _owner) public view returns (uint256 balance) {
return erc20Impl.balanceOf(_owner);
}
function emitTransfer(address _from, address _to, uint256 _value) public onlyImpl {
emit Transfer(_from, _to, _value);
}
function transfer(address _to, uint256 _value) public returns (bool success) {
return erc20Impl.transferWithSender(msg.sender, _to, _value);
}
function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) {
return erc20Impl.transferFromWithSender(msg.sender, _from, _to, _value);
}
function emitApproval(address _owner, address _spender, uint256 _value) public onlyImpl {
emit Approval(_owner, _spender, _value);
}
function approve(address _spender, uint256 _value) public returns (bool success) {
return erc20Impl.approveWithSender(msg.sender, _spender, _value);
}
function approveAndCall(address _spender, uint256 _value, bytes memory _extraData) public returns (bool success) {
return erc20Impl.approveAndCallWithSender(msg.sender, _spender, _value, _extraData);
}
function increaseApproval(address _spender, uint256 _addedValue) public returns (bool success) {
return erc20Impl.increaseApprovalWithSender(msg.sender, _spender, _addedValue);
}
function decreaseApproval(address _spender, uint256 _subtractedValue) public returns (bool success) {
return erc20Impl.decreaseApprovalWithSender(msg.sender, _spender, _subtractedValue);
}
function allowance(address _owner, address _spender) public view returns (uint256 remaining) {
return erc20Impl.allowance(_owner, _spender);
}
function emitBurn(address _burner, uint256 _value, string memory _note) public onlyImpl {
emit Burn(_burner, _value, _note);
}
function burn(uint256 _value, string memory _note) public returns (bool success) {
return erc20Impl.burnWithNote(msg.sender, _value, _note);
}
}
contract ERC20Store is ERC20ImplUpgradeable {
uint256 public totalSupply;
mapping (address => uint256) public balances;
mapping (address => mapping (address => uint256)) public allowed;
constructor(address _custodian) public ERC20ImplUpgradeable(_custodian) {
totalSupply = 0;
}
function setTotalSupply(
uint256 _newTotalSupply
)
public
onlyImpl
{
totalSupply = _newTotalSupply;
}
function setAllowance(
address _owner,
address _spender,
uint256 _value
)
public
onlyImpl
{
allowed[_owner][_spender] = _value;
}
function setBalance(
address _owner,
uint256 _newBalance
)
public
onlyImpl
{
balances[_owner] = _newBalance;
}
function addBalance(
address _owner,
uint256 _balanceIncrease
)
public
onlyImpl
{
balances[_owner] = balances[_owner] + _balanceIncrease;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment