1
1
import { ethers , upgrades } from 'hardhat'
2
- import { Contract , Signer , BigNumber } from 'ethers'
2
+ import { Contract , Signer , BigNumber , BigNumberish } from 'ethers'
3
3
import { expect } from 'chai'
4
+ import { fromRpcSig } from 'ethereumjs-util'
5
+ import { TypedDataUtils , signTypedMessage , TypedMessage } from 'eth-sig-util'
6
+ import { default as Wallet } from 'ethereumjs-wallet'
4
7
5
8
const toUFrgDenomination = ( ample : string ) : BigNumber =>
6
9
ethers . utils . parseUnits ( ample , DECIMALS )
7
10
11
+ const ETHEREUM_CHAIN_ID = 1
12
+ const ETHEREUM_AMPL_ADDRESS = '0xD46bA6D942050d489DBd938a2C909A5d5039A161'
13
+ const EIP712_REVISION = '1'
14
+
15
+ const TOKEN_NAME = 'Ampleforth'
16
+ const TOKEN_SYMBOL = 'AMPL'
8
17
const DECIMALS = 9
9
18
const INITIAL_SUPPLY = ethers . utils . parseUnits ( '50' , 6 + DECIMALS )
10
19
const transferAmount = toUFrgDenomination ( '10' )
11
20
const unitTokenAmount = toUFrgDenomination ( '1' )
12
21
22
+ const EIP712Domain = [
23
+ { name : 'name' , type : 'string' } ,
24
+ { name : 'version' , type : 'string' } ,
25
+ { name : 'chainId' , type : 'uint256' } ,
26
+ { name : 'verifyingContract' , type : 'address' } ,
27
+ ]
28
+ const Permit = [
29
+ { name : 'owner' , type : 'address' } ,
30
+ { name : 'spender' , type : 'address' } ,
31
+ { name : 'value' , type : 'uint256' } ,
32
+ { name : 'nonce' , type : 'uint256' } ,
33
+ { name : 'deadline' , type : 'uint256' } ,
34
+ ]
35
+ function domainSeparator (
36
+ name : string ,
37
+ version : string ,
38
+ chainId : number ,
39
+ verifyingContract : string ,
40
+ ) : string {
41
+ return (
42
+ '0x' +
43
+ TypedDataUtils . hashStruct (
44
+ 'EIP712Domain' ,
45
+ { name, version, chainId, verifyingContract } ,
46
+ { EIP712Domain } ,
47
+ ) . toString ( 'hex' )
48
+ )
49
+ }
50
+
13
51
let accounts : Signer [ ] ,
14
52
deployer : Signer ,
15
53
uFragments : Contract ,
@@ -19,6 +57,7 @@ async function setupContracts() {
19
57
// prepare signers
20
58
accounts = await ethers . getSigners ( )
21
59
deployer = accounts [ 0 ]
60
+
22
61
// deploy upgradable token
23
62
const factory = await ethers . getContractFactory ( 'UFragments' )
24
63
uFragments = await upgrades . deployProxy (
@@ -60,10 +99,113 @@ describe('UFragments:Initialization', () => {
60
99
} )
61
100
62
101
it ( 'should set detailed ERC20 parameters' , async function ( ) {
63
- expect ( await uFragments . name ( ) ) . to . eq ( 'Ampleforth' )
64
- expect ( await uFragments . symbol ( ) ) . to . eq ( 'AMPL' )
102
+ expect ( await uFragments . name ( ) ) . to . eq ( TOKEN_NAME )
103
+ expect ( await uFragments . symbol ( ) ) . to . eq ( TOKEN_SYMBOL )
65
104
expect ( await uFragments . decimals ( ) ) . to . eq ( DECIMALS )
66
105
} )
106
+
107
+ it ( 'should set the EIP2612 parameters' , async function ( ) {
108
+ expect ( await uFragments . EIP712_REVISION ( ) ) . to . eq ( '0x31' )
109
+ expect ( await uFragments . PERMIT_TYPEHASH ( ) ) . to . eq (
110
+ '0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9' ,
111
+ )
112
+ // with hard-coded parameters
113
+ expect ( await uFragments . DOMAIN_SEPARATOR ( ) ) . to . eq (
114
+ domainSeparator (
115
+ TOKEN_NAME ,
116
+ '1' ,
117
+ ETHEREUM_CHAIN_ID ,
118
+ ETHEREUM_AMPL_ADDRESS ,
119
+ ) ,
120
+ )
121
+ } )
122
+
123
+ it ( 'initial nonce is 0' , async function ( ) {
124
+ expect ( await uFragments . nonces ( await deployer . getAddress ( ) ) ) . to . eq ( '0' )
125
+ } )
126
+ } )
127
+
128
+ // Using the cases specified by:
129
+ // https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/test/drafts/ERC20Permit.test.js
130
+ describe ( 'UFragments:EIP-2612 Permit' , ( ) => {
131
+ let wallet : Wallet , owner : string , spender : string , nonce : number
132
+
133
+ const value = 42
134
+ const maxDeadline = 99999999999
135
+
136
+ beforeEach ( 'setup UFragments contract' , async function ( ) {
137
+ await setupContracts ( )
138
+
139
+ wallet = Wallet . generate ( )
140
+ owner = wallet . getAddressString ( )
141
+ spender = Wallet . generate ( ) . getAddressString ( )
142
+ nonce = 0
143
+ } )
144
+
145
+ describe ( 'permit' , function ( ) {
146
+ const buildPermitData = (
147
+ deadline : number = maxDeadline ,
148
+ ) : TypedMessage < any > => {
149
+ return {
150
+ primaryType : 'Permit' ,
151
+ types : { EIP712Domain, Permit } ,
152
+ domain : {
153
+ name : TOKEN_NAME ,
154
+ version : EIP712_REVISION ,
155
+ chainId : ETHEREUM_CHAIN_ID ,
156
+ verifyingContract : ETHEREUM_AMPL_ADDRESS ,
157
+ } ,
158
+ message : { owner, spender, value, nonce, deadline } ,
159
+ }
160
+ }
161
+
162
+ it ( 'accepts owner signature' , async function ( ) {
163
+ const data = buildPermitData ( )
164
+ const signature = signTypedMessage ( wallet . getPrivateKey ( ) , { data } )
165
+ const { v, r, s } = fromRpcSig ( signature )
166
+ const receipt = await uFragments
167
+ . connect ( deployer )
168
+ . permit ( owner , spender , value , maxDeadline , v , r , s )
169
+ expect ( await uFragments . nonces ( owner ) ) . to . eq ( '1' )
170
+ expect ( await uFragments . allowance ( owner , spender ) ) . to . eq ( value )
171
+ } )
172
+
173
+ it ( 'rejects reused signature' , async function ( ) {
174
+ const data = buildPermitData ( )
175
+ const signature = signTypedMessage ( wallet . getPrivateKey ( ) , { data } )
176
+ const { v, r, s } = fromRpcSig ( signature )
177
+ await uFragments
178
+ . connect ( deployer )
179
+ . permit ( owner , spender , value , maxDeadline , v , r , s )
180
+ await expect (
181
+ uFragments
182
+ . connect ( deployer )
183
+ . permit ( owner , spender , value , maxDeadline , v , r , s ) ,
184
+ ) . to . be . reverted
185
+ } )
186
+
187
+ it ( 'rejects other signature' , async function ( ) {
188
+ const otherWallet = Wallet . generate ( )
189
+ const data = buildPermitData ( )
190
+ const signature = signTypedMessage ( otherWallet . getPrivateKey ( ) , { data } )
191
+ const { v, r, s } = fromRpcSig ( signature )
192
+ await expect (
193
+ uFragments
194
+ . connect ( deployer )
195
+ . permit ( owner , spender , value , maxDeadline , v , r , s ) ,
196
+ ) . to . be . reverted
197
+ } )
198
+
199
+ it ( 'rejects expired permit' , async function ( ) {
200
+ const deadline =
201
+ ( await ethers . provider . getBlock ( 'latest' ) ) . timestamp - 3600 * 24 * 7
202
+ const data = buildPermitData ( deadline )
203
+ const signature = signTypedMessage ( wallet . getPrivateKey ( ) , { data } )
204
+ const { v, r, s } = fromRpcSig ( signature )
205
+ await expect ( uFragments . permit ( owner , spender , value , deadline , v , r , s ) )
206
+ . to . be . reverted
207
+ } )
208
+ } )
67
209
} )
68
210
69
211
describe ( 'UFragments:setMonetaryPolicy' , async ( ) => {
0 commit comments