Skip to content

Commit 6f5c5d3

Browse files
committed
Implemented EIP-2612 Permit & Elastic token interface methods
1 parent a5b1696 commit 6f5c5d3

File tree

4 files changed

+695
-0
lines changed

4 files changed

+695
-0
lines changed

contracts/satellite-chain/xc-ampleforth/XCAmple.sol

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,20 @@ contract XCAmple is IERC20Upgradeable, OwnableUpgradeable {
7676
// because the gons xcAmple conversion might change before it's fully paid.
7777
mapping(address => mapping(address => uint256)) private _allowedXCAmples;
7878

79+
// EIP-2612: permit – 712-signed approvals
80+
// https://eips.ethereum.org/EIPS/eip-2612
81+
bytes public constant EIP712_REVISION = "1";
82+
bytes32 public constant EIP712_DOMAIN = keccak256(
83+
"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"
84+
);
85+
bytes32 public constant PERMIT_TYPEHASH = keccak256(
86+
"Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
87+
);
88+
bytes32 public DOMAIN_SEPARATOR;
89+
90+
// EIP-2612: keeps track of number of permits per address
91+
mapping(address => uint256) private _nonces;
92+
7993
/**
8094
* @param controller_ The address of the controller contract to use for authentication.
8195
*/
@@ -130,6 +144,20 @@ contract XCAmple is IERC20Upgradeable, OwnableUpgradeable {
130144
_totalSupply = 0;
131145

132146
_gonsPerAMPL = TOTAL_GONS.div(globalAMPLSupply);
147+
148+
uint256 chainId;
149+
assembly {
150+
chainId := chainid()
151+
}
152+
DOMAIN_SEPARATOR = keccak256(
153+
abi.encode(
154+
EIP712_DOMAIN,
155+
keccak256(bytes(name)),
156+
keccak256(EIP712_REVISION),
157+
chainId,
158+
address(this)
159+
)
160+
);
133161
}
134162

135163
/**
@@ -175,6 +203,28 @@ contract XCAmple is IERC20Upgradeable, OwnableUpgradeable {
175203
return _gonBalances[who].div(_gonsPerAMPL);
176204
}
177205

206+
/**
207+
* @param who The address to query.
208+
* @return The gon balance of the specified address.
209+
*/
210+
function scaledBalanceOf(address who) external view returns (uint256) {
211+
return _gonBalances[who];
212+
}
213+
214+
/**
215+
* @return the total number of gons.
216+
*/
217+
function scaledTotalSupply() external pure returns (uint256) {
218+
return TOTAL_GONS;
219+
}
220+
221+
/**
222+
* @return The number of successful permits by the specified address.
223+
*/
224+
function nonces(address who) public view returns (uint256) {
225+
return _nonces[who];
226+
}
227+
178228
/**
179229
* @dev Transfer tokens to a specified address.
180230
* @param to The address to transfer to.
@@ -188,8 +238,26 @@ contract XCAmple is IERC20Upgradeable, OwnableUpgradeable {
188238
returns (bool)
189239
{
190240
uint256 gonValue = value.mul(_gonsPerAMPL);
241+
191242
_gonBalances[msg.sender] = _gonBalances[msg.sender].sub(gonValue);
192243
_gonBalances[to] = _gonBalances[to].add(gonValue);
244+
245+
emit Transfer(msg.sender, to, value);
246+
return true;
247+
}
248+
249+
/**
250+
* @dev Transfer all of the sender's wallet balance to a specified address.
251+
* @param to The address to transfer to.
252+
* @return True on success, false otherwise.
253+
*/
254+
function transferAll(address to) external validRecipient(to) returns (bool) {
255+
uint256 gonValue = _gonBalances[msg.sender];
256+
uint256 value = gonValue.div(_gonsPerAMPL);
257+
258+
delete _gonBalances[msg.sender];
259+
_gonBalances[to] = _gonBalances[to].add(gonValue);
260+
193261
emit Transfer(msg.sender, to, value);
194262
return true;
195263
}
@@ -218,10 +286,29 @@ contract XCAmple is IERC20Upgradeable, OwnableUpgradeable {
218286
_allowedXCAmples[from][msg.sender] = _allowedXCAmples[from][msg.sender].sub(value);
219287

220288
uint256 gonValue = value.mul(_gonsPerAMPL);
289+
221290
_gonBalances[from] = _gonBalances[from].sub(gonValue);
222291
_gonBalances[to] = _gonBalances[to].add(gonValue);
292+
223293
emit Transfer(from, to, value);
294+
return true;
295+
}
296+
297+
/**
298+
* @dev Transfer all balance tokens from one address to another.
299+
* @param from The address you want to send tokens from.
300+
* @param to The address you want to transfer to.
301+
*/
302+
function transferAllFrom(address from, address to) external validRecipient(to) returns (bool) {
303+
uint256 gonValue = _gonBalances[from];
304+
uint256 value = gonValue.div(_gonsPerAMPL);
305+
306+
_allowedXCAmples[from][msg.sender] = _allowedXCAmples[from][msg.sender].sub(value);
307+
308+
delete _gonBalances[from];
309+
_gonBalances[to] = _gonBalances[to].add(gonValue);
224310

311+
emit Transfer(from, to, value);
225312
return true;
226313
}
227314

@@ -238,6 +325,7 @@ contract XCAmple is IERC20Upgradeable, OwnableUpgradeable {
238325
*/
239326
function approve(address spender, uint256 value) external override returns (bool) {
240327
_allowedXCAmples[msg.sender][spender] = value;
328+
241329
emit Approval(msg.sender, spender, value);
242330
return true;
243331
}
@@ -253,6 +341,7 @@ contract XCAmple is IERC20Upgradeable, OwnableUpgradeable {
253341
_allowedXCAmples[msg.sender][spender] = _allowedXCAmples[msg.sender][spender].add(
254342
addedValue
255343
);
344+
256345
emit Approval(msg.sender, spender, _allowedXCAmples[msg.sender][spender]);
257346
return true;
258347
}
@@ -270,6 +359,7 @@ contract XCAmple is IERC20Upgradeable, OwnableUpgradeable {
270359
} else {
271360
_allowedXCAmples[msg.sender][spender] = oldValue.sub(subtractedValue);
272361
}
362+
273363
emit Approval(msg.sender, spender, _allowedXCAmples[msg.sender][spender]);
274364
return true;
275365
}
@@ -306,4 +396,41 @@ contract XCAmple is IERC20Upgradeable, OwnableUpgradeable {
306396

307397
emit Transfer(who, address(0), xcAmpleAmount);
308398
}
399+
400+
/**
401+
* @dev Allows for approvals to be made via secp256k1 signatures.
402+
* @param owner The owner of the funds
403+
* @param spender The spender
404+
* @param value The amount
405+
* @param deadline The deadline timestamp, type(uint256).max for max deadline
406+
* @param v Signature param
407+
* @param s Signature param
408+
* @param r Signature param
409+
*/
410+
function permit(
411+
address owner,
412+
address spender,
413+
uint256 value,
414+
uint256 deadline,
415+
uint8 v,
416+
bytes32 r,
417+
bytes32 s
418+
) public {
419+
require(block.timestamp <= deadline);
420+
421+
uint256 ownerNonce = _nonces[owner];
422+
bytes32 permitDataDigest = keccak256(
423+
abi.encode(PERMIT_TYPEHASH, owner, spender, value, ownerNonce, deadline)
424+
);
425+
bytes32 digest = keccak256(
426+
abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR, permitDataDigest)
427+
);
428+
429+
require(owner == ecrecover(digest, v, r, s));
430+
431+
_nonces[owner] = ownerNonce.add(1);
432+
433+
_allowedXCAmples[owner][spender] = value;
434+
emit Approval(owner, spender, value);
435+
}
309436
}

test/unit/_utilities/signatures.js

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
// https://github.com/albertocuestacanada/ERC20Permit/blob/master/utils/signatures.ts
2+
const {
3+
keccak256,
4+
defaultAbiCoder,
5+
toUtf8Bytes,
6+
solidityPack
7+
} = require('ethers/lib/utils');
8+
const { ecsign } = require('ethereumjs-util');
9+
10+
const EIP712_DOMAIN_TYPEHASH = keccak256(
11+
toUtf8Bytes(
12+
'EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)',
13+
),
14+
);
15+
16+
const EIP712_DOMAIN_TYPE = [
17+
{ name: 'name', type: 'string' },
18+
{ name: 'version', type: 'string' },
19+
{ name: 'chainId', type: 'uint256' },
20+
{ name: 'verifyingContract', type: 'address' }
21+
];
22+
23+
const EIP2612_PERMIT_TYPEHASH = keccak256(
24+
toUtf8Bytes(
25+
'Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)',
26+
),
27+
);
28+
29+
const EIP2612_PERMIT_TYPE = [
30+
{ name: 'owner', type: 'address' },
31+
{ name: 'spender', type: 'address' },
32+
{ name: 'value', type: 'uint256' },
33+
{ name: 'nonce', type: 'uint256' },
34+
{ name: 'deadline', type: 'uint256' }
35+
];
36+
37+
// Gets the EIP712 domain separator
38+
function getDomainSeparator (version, name, contractAddress, chainId) {
39+
return keccak256(
40+
defaultAbiCoder.encode(
41+
['bytes32', 'bytes32', 'bytes32', 'uint256', 'address'],
42+
[
43+
EIP712_DOMAIN_TYPEHASH,
44+
keccak256(toUtf8Bytes(name)),
45+
keccak256(toUtf8Bytes(version)),
46+
chainId,
47+
contractAddress
48+
],
49+
),
50+
);
51+
}
52+
53+
// Returns the EIP712 hash which should be signed by the user
54+
// in order to make a call to `permit`
55+
function getPermitDigest (
56+
version,
57+
name,
58+
address,
59+
chainId,
60+
owner,
61+
spender,
62+
value,
63+
nonce,
64+
deadline,
65+
) {
66+
const DOMAIN_SEPARATOR = getDomainSeparator(version, name, address, chainId);
67+
const permitHash = keccak256(
68+
defaultAbiCoder.encode(
69+
['bytes32', 'address', 'address', 'uint256', 'uint256', 'uint256'],
70+
[EIP2612_PERMIT_TYPEHASH, owner, spender, value, nonce, deadline],
71+
),
72+
);
73+
const hash = keccak256(
74+
solidityPack(
75+
['bytes1', 'bytes1', 'bytes32', 'bytes32'],
76+
['0x19', '0x01', DOMAIN_SEPARATOR, permitHash],
77+
),
78+
);
79+
return hash;
80+
}
81+
82+
const signEIP712Permission = async (
83+
version,
84+
name,
85+
verifyingContract,
86+
chainId,
87+
signer,
88+
owner,
89+
spender,
90+
value,
91+
nonce,
92+
deadline,
93+
) => {
94+
const digest = getPermitDigest(
95+
version,
96+
name,
97+
verifyingContract,
98+
chainId,
99+
owner,
100+
spender,
101+
value,
102+
nonce,
103+
deadline,
104+
);
105+
return ecsign(
106+
Buffer.from(digest.slice(2), 'hex'),
107+
Buffer.from(signer.privateKey.slice(2), 'hex'),
108+
);
109+
};
110+
111+
module.exports = {
112+
EIP712_DOMAIN_TYPEHASH,
113+
EIP712_DOMAIN_TYPE,
114+
EIP2612_PERMIT_TYPEHASH,
115+
EIP2612_PERMIT_TYPE,
116+
getDomainSeparator,
117+
getPermitDigest,
118+
signEIP712Permission
119+
};

0 commit comments

Comments
 (0)