Skip to content

Commit

Permalink
Add ERC827 with proxy proposal
Browse files Browse the repository at this point in the history
  • Loading branch information
AugustoL committed Jul 17, 2018
1 parent e574b35 commit 401b175
Show file tree
Hide file tree
Showing 5 changed files with 767 additions and 41 deletions.
71 changes: 71 additions & 0 deletions contracts/ERC827/proposals/ERC827AllowedCallbacksProxy.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/* solium-disable security/no-low-level-calls */

pragma solidity ^0.4.24;

import "./ERC827TokenMockAllowedCallbacks.sol";

/**
* @title ERC827AllowedCallbacksProxy
*
* @dev Proxy to forward tokens balance and allowance with arbitrary calls
*/
contract ERC827AllowedCallbacksProxy {

ERC827TokenAllowedCallbacks public token;

/**
* @dev Constructor
*/
constructor(ERC827TokenAllowedCallbacks _token) public {
token = _token;
bytes4 makeCallSig = bytes4(keccak256('makeCall(address,bytes)'));
token.allowCallback(address(0), makeCallSig,
ERC827TokenAllowedCallbacks.FunctionType.Approve
);
token.allowCallback(address(0), makeCallSig,
ERC827TokenAllowedCallbacks.FunctionType.Transfer
);
token.allowCallback(address(0), makeCallSig,
ERC827TokenAllowedCallbacks.FunctionType.TransferFrom
);
}

/**
* @dev Fallback function that give back all tokens received
*/
function() {
forwardTokens(msg.sender);
}

/**
* @dev Forward arbitary calls with token balance or allowance
* @param _target address The address which you want to transfer to
* @param _data bytes The data to be executed in the call
*/
function makeCall(address _target, bytes _data) public returns (bool) {
require(msg.sender == address(token));

forwardTokens(_target);

// solium-disable-next-line security/no-call-value
return _target.call.value(msg.value)(_data);
}

/**
* @dev Give back all tokens balance and allowance to address
* @param to address The address which you want to transfer to
*/
function forwardTokens(address to) internal {
uint256 callerBalance = token.balanceOf(address(this));
uint256 callerAllowance = token.allowance(to, address(this));

// Give back token balance
if (callerBalance > 0)
token.transfer(to, callerBalance);

// Give back token allowance
if (callerAllowance > 0)
token.transferFrom(address(this), to, callerAllowance);
}

}
55 changes: 17 additions & 38 deletions contracts/ERC827/proposals/ERC827Proxy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

pragma solidity ^0.4.24;

import "./ERC827TokenMockAllowedCallbacks.sol";
import "../ERC827.sol";

/**
* @title ERC827Proxy
Expand All @@ -11,61 +11,40 @@ import "./ERC827TokenMockAllowedCallbacks.sol";
*/
contract ERC827Proxy {

ERC827TokenAllowedCallbacks public token;
ERC827 public token;
bytes4 public makeCallSig = bytes4(keccak256('makeCall(address,bytes)'));

/**
* @dev Constructor
* @dev Set the token address, can be called only once
* @param _token The ERC827 token to be used for the proxy
*/
constructor(ERC827TokenAllowedCallbacks _token) public {
function setToken(ERC827 _token) public {
require(token == address(0));
require(_token != address(0));
token = _token;
bytes4 makeCallSig = bytes4(keccak256('makeCall(address,bytes)'));
token.allowCallback(address(0), makeCallSig,
ERC827TokenAllowedCallbacks.FunctionType.Approve
);
token.allowCallback(address(0), makeCallSig,
ERC827TokenAllowedCallbacks.FunctionType.Transfer
);
token.allowCallback(address(0), makeCallSig,
ERC827TokenAllowedCallbacks.FunctionType.TransferFrom
);
}

/**
* @dev Fallback function that give back all tokens received
*/
function() {
forwardTokens(msg.sender);
}

/**
* @dev Forward arbitary calls with token balance or allowance
* @param _target address The address which you want to transfer to
* @param _data bytes The data to be executed in the call
*/
function makeCall(address _target, bytes _data) public returns (bool) {
function makeCall(address _target, bytes _data) payable public returns (bool) {
require(msg.sender == address(token));

forwardTokens(_target);

// solium-disable-next-line security/no-call-value
return _target.call.value(msg.value)(_data);
}

/**
* @dev Give back all tokens balance and allowance to address
* @param to address The address which you want to transfer to
*/
function forwardTokens(address to) internal {
uint256 callerBalance = token.balanceOf(address(this));
uint256 callerAllowance = token.allowance(to, address(this));
uint256 callerAllowance = token.allowance(_target, address(this));

// Give back token balance
// Transfer token balance
if (callerBalance > 0)
token.transfer(to, callerBalance);
token.transfer(_target, callerBalance);

// Give back token allowance
// Transfer token allowance
if (callerAllowance > 0)
token.transferFrom(address(this), to, callerAllowance);
token.transferFrom(address(this), _target, callerAllowance);

// solium-disable-next-line security/no-call-value
require(_target.call.value(msg.value)(_data));
}

}
192 changes: 192 additions & 0 deletions contracts/ERC827/proposals/ERC827TokenMockWithProxy.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
/* solium-disable security/no-low-level-calls */

pragma solidity ^0.4.24;

import "../ERC827.sol";
import "../../ERC20/StandardToken.sol";
import "./ERC827Proxy.sol";

/**
* @title ERC827, an extension of ERC20 token standard
*
* @dev Implementation the ERC827, following the ERC20 standard with extra
* methods to transfer value and data and execute calls in transfers and
* approvals. Uses OpenZeppelin StandardToken and ERC827Proxy.
*/
contract ERC827TokenWithProxy is ERC827, StandardToken {

ERC827Proxy public proxy;

/**
* @dev Constructor
* @param _proxy The address of the ERC827 proxy to be used
*/
constructor(ERC827Proxy _proxy) public {
proxy = _proxy;
}

/**
* @dev Addition to ERC20 token methods. It allows to
* approve the transfer of value and execute a call with the sent data.
* 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
* @param _spender The address that will spend the funds.
* @param _value The amount of tokens to be spent.
* @param _data ABI-encoded contract call to call `_spender` address.
* @return true if the call function was executed successfully
*/
function approveAndCall(
address _spender,
uint256 _value,
bytes _data
)
public
payable
returns (bool)
{
require(_spender != address(this));

super.approve(_spender, _value);

// solium-disable-next-line security/no-call-value
require(address(proxy).call.value(msg.value)(
abi.encodeWithSelector(proxy.makeCallSig(), _spender, _data))
);
return true;
}

/**
* @dev Addition to ERC20 token methods. Transfer tokens to a specified
* address and execute a call with the sent data on the same transaction
* @param _to address The address which you want to transfer to
* @param _value uint256 the amout of tokens to be transfered
* @param _data ABI-encoded contract call to call `_to` address.
* @return true if the call function was executed successfully
*/
function transferAndCall(
address _to,
uint256 _value,
bytes _data
)
public
payable
returns (bool)
{
require(_to != address(this));

super.transfer(_to, _value);

// solium-disable-next-line security/no-call-value
require(address(proxy).call.value(msg.value)(
abi.encodeWithSelector(proxy.makeCallSig(), _to, _data))
);
return true;
}

/**
* @dev Addition to ERC20 token methods. Transfer tokens from one address to
* another and make a contract call on the same transaction
* @param _from The address which you want to send tokens from
* @param _to The address which you want to transfer to
* @param _value The amout of tokens to be transferred
* @param _data ABI-encoded contract call to call `_to` address.
* @return true if the call function was executed successfully
*/
function transferFromAndCall(
address _from,
address _to,
uint256 _value,
bytes _data
)
public payable returns (bool)
{
require(_to != address(this));

super.transferFrom(_from, _to, _value);

// solium-disable-next-line security/no-call-value
require(address(proxy).call.value(msg.value)(
abi.encodeWithSelector(proxy.makeCallSig(), _to, _data))
);
return true;
}

/**
* @dev Addition to StandardToken methods. Increase the amount of tokens that
* an owner allowed to a spender and execute a call with the sent data.
* approve should be called when allowed[_spender] == 0. To increment
* allowed value is better to use this function to avoid 2 calls (and wait until
* the first transaction is mined)
* From MonolithDAO Token.sol
* @param _spender The address which will spend the funds.
* @param _addedValue The amount of tokens to increase the allowance by.
* @param _data ABI-encoded contract call to call `_spender` address.
*/
function increaseApprovalAndCall(
address _spender,
uint _addedValue,
bytes _data
)
public
payable
returns (bool)
{
require(_spender != address(this));

super.increaseApproval(_spender, _addedValue);

// solium-disable-next-line security/no-call-value
require(address(proxy).call.value(msg.value)(
abi.encodeWithSelector(proxy.makeCallSig(), _spender, _data))
);
return true;
}

/**
* @dev Addition to StandardToken methods. Decrease the amount of tokens that
* an owner allowed to a spender and execute a call with the sent data.
* approve should be called when allowed[_spender] == 0. To decrement
* allowed value is better to use this function to avoid 2 calls (and wait until
* the first transaction is mined)
* From MonolithDAO Token.sol
* @param _spender The address which will spend the funds.
* @param _subtractedValue The amount of tokens to decrease the allowance by.
* @param _data ABI-encoded contract call to call `_spender` address.
*/
function decreaseApprovalAndCall(
address _spender,
uint _subtractedValue,
bytes _data
)
public
payable
returns (bool)
{
require(_spender != address(this));

super.decreaseApproval(_spender, _subtractedValue);

// solium-disable-next-line security/no-call-value
require(address(proxy).call.value(msg.value)(
abi.encodeWithSelector(proxy.makeCallSig(), _spender, _data))
);
return true;
}

}

// mock class using ERC827 Token with proxy
contract ERC827TokenWithProxyMock is ERC827TokenWithProxy {

constructor(
address initialAccount, uint256 initialBalance, ERC827Proxy proxy
) ERC827TokenWithProxy(proxy) public {
balances[initialAccount] = initialBalance;
totalSupply_ = initialBalance;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import EVMRevert from '../helpers/EVMRevert';
var Message = artifacts.require('./mocks/MessageHelper');
var ERC827TokenMock = artifacts.require('./ERC827/proposals/ERC827TokenMockAllowedCallbacks');
var ERC827Proxy = artifacts.require('./ERC827/proposals/ERC827Proxy');
var ERC827AllowedCallbacksProxy = artifacts.require('./ERC827/proposals/ERC827AllowedCallbacksProxy');

var BigNumber = web3.BigNumber;
require('chai')
Expand All @@ -23,8 +23,8 @@ contract('ERC827 Proxy for allowed callbacks', function (accounts) {
token = await ERC827TokenMock.new(accounts[0], 100);
});

it('should forward token balance correctly with ERC827Proxy', async function () {
let proxy = await ERC827Proxy.new(token.address);
it('should forward token balance correctly with ERC827AllowedCallbacksProxy', async function () {
let proxy = await ERC827AllowedCallbacksProxy.new(token.address);

let makeCallData = proxy.contract.makeCall.getData(message.address, messageData);
let makeCallSig = makeCallData.substring(0, 10);
Expand Down
Loading

0 comments on commit 401b175

Please sign in to comment.