Skip to content

Commit 86590de

Browse files
committed
Add ERC827 with proxy proposal
1 parent e574b35 commit 86590de

File tree

5 files changed

+767
-41
lines changed

5 files changed

+767
-41
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/* solium-disable security/no-low-level-calls */
2+
3+
pragma solidity ^0.4.24;
4+
5+
import "./ERC827TokenMockAllowedCallbacks.sol";
6+
7+
/**
8+
* @title ERC827AllowedCallbacksProxy
9+
*
10+
* @dev Proxy to forward tokens balance and allowance with arbitrary calls
11+
*/
12+
contract ERC827AllowedCallbacksProxy {
13+
14+
ERC827TokenAllowedCallbacks public token;
15+
16+
/**
17+
* @dev Constructor
18+
*/
19+
constructor(ERC827TokenAllowedCallbacks _token) public {
20+
token = _token;
21+
bytes4 makeCallSig = bytes4(keccak256('makeCall(address,bytes)'));
22+
token.allowCallback(address(0), makeCallSig,
23+
ERC827TokenAllowedCallbacks.FunctionType.Approve
24+
);
25+
token.allowCallback(address(0), makeCallSig,
26+
ERC827TokenAllowedCallbacks.FunctionType.Transfer
27+
);
28+
token.allowCallback(address(0), makeCallSig,
29+
ERC827TokenAllowedCallbacks.FunctionType.TransferFrom
30+
);
31+
}
32+
33+
/**
34+
* @dev Fallback function that give back all tokens received
35+
*/
36+
function() {
37+
forwardTokens(msg.sender);
38+
}
39+
40+
/**
41+
* @dev Forward arbitary calls with token balance or allowance
42+
* @param _target address The address which you want to transfer to
43+
* @param _data bytes The data to be executed in the call
44+
*/
45+
function makeCall(address _target, bytes _data) public returns (bool) {
46+
require(msg.sender == address(token));
47+
48+
forwardTokens(_target);
49+
50+
// solium-disable-next-line security/no-call-value
51+
return _target.call.value(msg.value)(_data);
52+
}
53+
54+
/**
55+
* @dev Give back all tokens balance and allowance to address
56+
* @param to address The address which you want to transfer to
57+
*/
58+
function forwardTokens(address to) internal {
59+
uint256 callerBalance = token.balanceOf(address(this));
60+
uint256 callerAllowance = token.allowance(to, address(this));
61+
62+
// Give back token balance
63+
if (callerBalance > 0)
64+
token.transfer(to, callerBalance);
65+
66+
// Give back token allowance
67+
if (callerAllowance > 0)
68+
token.transferFrom(address(this), to, callerAllowance);
69+
}
70+
71+
}

Diff for: contracts/ERC827/proposals/ERC827Proxy.sol

+17-38
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
pragma solidity ^0.4.24;
44

5-
import "./ERC827TokenMockAllowedCallbacks.sol";
5+
import "../ERC827.sol";
66

77
/**
88
* @title ERC827Proxy
@@ -11,61 +11,40 @@ import "./ERC827TokenMockAllowedCallbacks.sol";
1111
*/
1212
contract ERC827Proxy {
1313

14-
ERC827TokenAllowedCallbacks public token;
14+
ERC827 public token;
15+
bytes4 public makeCallSig = bytes4(keccak256('makeCall(address,bytes)'));
1516

1617
/**
17-
* @dev Constructor
18+
* @dev Set the token address, can be called only once
19+
* @param _token The ERC827 token to be used for the proxy
1820
*/
19-
constructor(ERC827TokenAllowedCallbacks _token) public {
21+
function setToken(ERC827 _token) public {
22+
require(token == address(0));
23+
require(_token != address(0));
2024
token = _token;
21-
bytes4 makeCallSig = bytes4(keccak256('makeCall(address,bytes)'));
22-
token.allowCallback(address(0), makeCallSig,
23-
ERC827TokenAllowedCallbacks.FunctionType.Approve
24-
);
25-
token.allowCallback(address(0), makeCallSig,
26-
ERC827TokenAllowedCallbacks.FunctionType.Transfer
27-
);
28-
token.allowCallback(address(0), makeCallSig,
29-
ERC827TokenAllowedCallbacks.FunctionType.TransferFrom
30-
);
31-
}
32-
33-
/**
34-
* @dev Fallback function that give back all tokens received
35-
*/
36-
function() {
37-
forwardTokens(msg.sender);
3825
}
3926

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

48-
forwardTokens(_target);
49-
50-
// solium-disable-next-line security/no-call-value
51-
return _target.call.value(msg.value)(_data);
52-
}
53-
54-
/**
55-
* @dev Give back all tokens balance and allowance to address
56-
* @param to address The address which you want to transfer to
57-
*/
58-
function forwardTokens(address to) internal {
5935
uint256 callerBalance = token.balanceOf(address(this));
60-
uint256 callerAllowance = token.allowance(to, address(this));
36+
uint256 callerAllowance = token.allowance(_target, address(this));
6137

62-
// Give back token balance
38+
// Transfer token balance
6339
if (callerBalance > 0)
64-
token.transfer(to, callerBalance);
40+
token.transfer(_target, callerBalance);
6541

66-
// Give back token allowance
42+
// Transfer token allowance
6743
if (callerAllowance > 0)
68-
token.transferFrom(address(this), to, callerAllowance);
44+
token.transferFrom(address(this), _target, callerAllowance);
45+
46+
// solium-disable-next-line security/no-call-value
47+
require(_target.call.value(msg.value)(_data));
6948
}
7049

7150
}

Diff for: contracts/ERC827/proposals/ERC827TokenWithProxy.sol

+192
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
/* solium-disable security/no-low-level-calls */
2+
3+
pragma solidity ^0.4.24;
4+
5+
import "../ERC827.sol";
6+
import "../../ERC20/StandardToken.sol";
7+
import "./ERC827Proxy.sol";
8+
9+
/**
10+
* @title ERC827, an extension of ERC20 token standard
11+
*
12+
* @dev Implementation the ERC827, following the ERC20 standard with extra
13+
* methods to transfer value and data and execute calls in transfers and
14+
* approvals. Uses OpenZeppelin StandardToken and ERC827Proxy.
15+
*/
16+
contract ERC827TokenWithProxy is ERC827, StandardToken {
17+
18+
ERC827Proxy public proxy;
19+
20+
/**
21+
* @dev Constructor
22+
* @param _proxy The address of the ERC827 proxy to be used
23+
*/
24+
constructor(ERC827Proxy _proxy) public {
25+
proxy = _proxy;
26+
}
27+
28+
/**
29+
* @dev Addition to ERC20 token methods. It allows to
30+
* approve the transfer of value and execute a call with the sent data.
31+
* Beware that changing an allowance with this method brings the risk that
32+
* someone may use both the old and the new allowance by unfortunate
33+
* transaction ordering. One possible solution to mitigate this race condition
34+
* is to first reduce the spender's allowance to 0 and set the desired value
35+
* afterwards:
36+
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
37+
* @param _spender The address that will spend the funds.
38+
* @param _value The amount of tokens to be spent.
39+
* @param _data ABI-encoded contract call to call `_spender` address.
40+
* @return true if the call function was executed successfully
41+
*/
42+
function approveAndCall(
43+
address _spender,
44+
uint256 _value,
45+
bytes _data
46+
)
47+
public
48+
payable
49+
returns (bool)
50+
{
51+
require(_spender != address(this));
52+
53+
super.approve(_spender, _value);
54+
55+
// solium-disable-next-line security/no-call-value
56+
require(address(proxy).call.value(msg.value)(
57+
abi.encodeWithSelector(proxy.makeCallSig(), _spender, _data))
58+
);
59+
return true;
60+
}
61+
62+
/**
63+
* @dev Addition to ERC20 token methods. Transfer tokens to a specified
64+
* address and execute a call with the sent data on the same transaction
65+
* @param _to address The address which you want to transfer to
66+
* @param _value uint256 the amout of tokens to be transfered
67+
* @param _data ABI-encoded contract call to call `_to` address.
68+
* @return true if the call function was executed successfully
69+
*/
70+
function transferAndCall(
71+
address _to,
72+
uint256 _value,
73+
bytes _data
74+
)
75+
public
76+
payable
77+
returns (bool)
78+
{
79+
require(_to != address(this));
80+
81+
super.transfer(_to, _value);
82+
83+
// solium-disable-next-line security/no-call-value
84+
require(address(proxy).call.value(msg.value)(
85+
abi.encodeWithSelector(proxy.makeCallSig(), _to, _data))
86+
);
87+
return true;
88+
}
89+
90+
/**
91+
* @dev Addition to ERC20 token methods. Transfer tokens from one address to
92+
* another and make a contract call on the same transaction
93+
* @param _from The address which you want to send tokens from
94+
* @param _to The address which you want to transfer to
95+
* @param _value The amout of tokens to be transferred
96+
* @param _data ABI-encoded contract call to call `_to` address.
97+
* @return true if the call function was executed successfully
98+
*/
99+
function transferFromAndCall(
100+
address _from,
101+
address _to,
102+
uint256 _value,
103+
bytes _data
104+
)
105+
public payable returns (bool)
106+
{
107+
require(_to != address(this));
108+
109+
super.transferFrom(_from, _to, _value);
110+
111+
// solium-disable-next-line security/no-call-value
112+
require(address(proxy).call.value(msg.value)(
113+
abi.encodeWithSelector(proxy.makeCallSig(), _to, _data))
114+
);
115+
return true;
116+
}
117+
118+
/**
119+
* @dev Addition to StandardToken methods. Increase the amount of tokens that
120+
* an owner allowed to a spender and execute a call with the sent data.
121+
* approve should be called when allowed[_spender] == 0. To increment
122+
* allowed value is better to use this function to avoid 2 calls (and wait until
123+
* the first transaction is mined)
124+
* From MonolithDAO Token.sol
125+
* @param _spender The address which will spend the funds.
126+
* @param _addedValue The amount of tokens to increase the allowance by.
127+
* @param _data ABI-encoded contract call to call `_spender` address.
128+
*/
129+
function increaseApprovalAndCall(
130+
address _spender,
131+
uint _addedValue,
132+
bytes _data
133+
)
134+
public
135+
payable
136+
returns (bool)
137+
{
138+
require(_spender != address(this));
139+
140+
super.increaseApproval(_spender, _addedValue);
141+
142+
// solium-disable-next-line security/no-call-value
143+
require(address(proxy).call.value(msg.value)(
144+
abi.encodeWithSelector(proxy.makeCallSig(), _spender, _data))
145+
);
146+
return true;
147+
}
148+
149+
/**
150+
* @dev Addition to StandardToken methods. Decrease the amount of tokens that
151+
* an owner allowed to a spender and execute a call with the sent data.
152+
* approve should be called when allowed[_spender] == 0. To decrement
153+
* allowed value is better to use this function to avoid 2 calls (and wait until
154+
* the first transaction is mined)
155+
* From MonolithDAO Token.sol
156+
* @param _spender The address which will spend the funds.
157+
* @param _subtractedValue The amount of tokens to decrease the allowance by.
158+
* @param _data ABI-encoded contract call to call `_spender` address.
159+
*/
160+
function decreaseApprovalAndCall(
161+
address _spender,
162+
uint _subtractedValue,
163+
bytes _data
164+
)
165+
public
166+
payable
167+
returns (bool)
168+
{
169+
require(_spender != address(this));
170+
171+
super.decreaseApproval(_spender, _subtractedValue);
172+
173+
// solium-disable-next-line security/no-call-value
174+
require(address(proxy).call.value(msg.value)(
175+
abi.encodeWithSelector(proxy.makeCallSig(), _spender, _data))
176+
);
177+
return true;
178+
}
179+
180+
}
181+
182+
// mock class using ERC827 Token with proxy
183+
contract ERC827TokenWithProxyMock is ERC827TokenWithProxy {
184+
185+
constructor(
186+
address initialAccount, uint256 initialBalance, ERC827Proxy proxy
187+
) ERC827TokenWithProxy(proxy) public {
188+
balances[initialAccount] = initialBalance;
189+
totalSupply_ = initialBalance;
190+
}
191+
192+
}

Diff for: test/proposals/ERC827Proxy.js renamed to test/proposals/AllowedCallbacksProxy.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import EVMRevert from '../helpers/EVMRevert';
33
var Message = artifacts.require('./mocks/MessageHelper');
44
var ERC827TokenMock = artifacts.require('./ERC827/proposals/ERC827TokenMockAllowedCallbacks');
5-
var ERC827Proxy = artifacts.require('./ERC827/proposals/ERC827Proxy');
5+
var ERC827AllowedCallbacksProxy = artifacts.require('./ERC827/proposals/ERC827AllowedCallbacksProxy');
66

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

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

2929
let makeCallData = proxy.contract.makeCall.getData(message.address, messageData);
3030
let makeCallSig = makeCallData.substring(0, 10);

0 commit comments

Comments
 (0)