Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ERC827 with proxy proposal #4

Merged
merged 3 commits into from
Aug 9, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
}

}
52 changes: 8 additions & 44 deletions contracts/ERC827/proposals/ERC827Proxy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

pragma solidity ^0.4.24;

import "./ERC827TokenMockAllowedCallbacks.sol";

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

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

/**
* @dev Constructor
* @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
);
constructor() public {
token = address(msg.sender);
}

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

/**
* @dev Forward arbitary calls with token balance or allowance
* @dev Forward arbitary calls
* @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) public payable 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);
require(_target.call.value(msg.value)(_data));
}

}
191 changes: 191 additions & 0 deletions contracts/ERC827/proposals/ERC827TokenWithProxy.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
/* 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
*/
constructor() public {
proxy = new ERC827Proxy();
}

/**
* @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
) 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