1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
import { BigNumber, Contract, ContractFactory, utils, Wallet } from 'ethers'
import { ethers } from 'hardhat'
import { solidity } from 'ethereum-waffle'
import chai, { expect } from 'chai'
import { UniswapV3Deployer } from 'uniswap-v3-deploy-plugin/dist/deployer/UniswapV3Deployer'
import { OptimismEnv } from './shared/env'
import { FeeAmount, TICK_SPACINGS } from '@uniswap/v3-sdk'
import { abi as NFTABI } from '@uniswap/v3-periphery/artifacts/contracts/NonfungiblePositionManager.sol/NonfungiblePositionManager.json'
import { abi as RouterABI } from '@uniswap/v3-periphery/artifacts/contracts/SwapRouter.sol/SwapRouter.json'
chai.use(solidity)
// Below methods taken from the Uniswap test suite, see
// https://github.com/Uniswap/v3-periphery/blob/main/test/shared/ticks.ts
const getMinTick = (tickSpacing: number) =>
Math.ceil(-887272 / tickSpacing) * tickSpacing
const getMaxTick = (tickSpacing: number) =>
Math.floor(887272 / tickSpacing) * tickSpacing
describe('Contract interactions', () => {
let env: OptimismEnv
let Factory__ERC20: ContractFactory
let otherWallet: Wallet
before(async () => {
env = await OptimismEnv.new()
Factory__ERC20 = await ethers.getContractFactory('ERC20', env.l2Wallet)
otherWallet = Wallet.createRandom().connect(env.l2Wallet.provider)
await env.l2Wallet.sendTransaction({
to: otherWallet.address,
value: utils.parseEther('0.1'),
})
})
describe('ERC20s', () => {
let contract: Contract
before(async () => {
Factory__ERC20 = await ethers.getContractFactory('ERC20', env.l2Wallet)
})
it('should successfully deploy the contract', async () => {
contract = await Factory__ERC20.deploy(100000000, 'OVM Test', 8, 'OVM')
await contract.deployed()
})
it('should support approvals', async () => {
const spender = '0x' + '22'.repeat(20)
const tx = await contract.approve(spender, 1000)
await tx.wait()
let allowance = await contract.allowance(env.l2Wallet.address, spender)
expect(allowance).to.deep.equal(BigNumber.from(1000))
allowance = await contract.allowance(otherWallet.address, spender)
expect(allowance).to.deep.equal(BigNumber.from(0))
const logs = await contract.queryFilter(
contract.filters.Approval(env.l2Wallet.address),
1,
'latest'
)
expect(logs[0].args._owner).to.equal(env.l2Wallet.address)
expect(logs[0].args._spender).to.equal(spender)
expect(logs[0].args._value).to.deep.equal(BigNumber.from(1000))
})
it('should support transferring balances', async () => {
const tx = await contract.transfer(otherWallet.address, 1000)
await tx.wait()
const balFrom = await contract.balanceOf(env.l2Wallet.address)
const balTo = await contract.balanceOf(otherWallet.address)
expect(balFrom).to.deep.equal(BigNumber.from(100000000).sub(1000))
expect(balTo).to.deep.equal(BigNumber.from(1000))
const logs = await contract.queryFilter(
contract.filters.Transfer(env.l2Wallet.address),
1,
'latest'
)
expect(logs[0].args._from).to.equal(env.l2Wallet.address)
expect(logs[0].args._to).to.equal(otherWallet.address)
expect(logs[0].args._value).to.deep.equal(BigNumber.from(1000))
})
it('should support being self destructed', async () => {
const tx = await contract.destroy()
await tx.wait()
const code = await env.l2Wallet.provider.getCode(contract.address)
expect(code).to.equal('0x')
})
})
describe('uniswap', () => {
let contracts: { [name: string]: Contract }
let tokens: Contract[]
before(async () => {
if (
process.env.UNISWAP_POSITION_MANAGER_ADDRESS &&
process.env.UNISWAP_ROUTER_ADDRESS
) {
console.log('Using predeployed Uniswap. Addresses:')
console.log(
`Position manager: ${process.env.UNISWAP_POSITION_MANAGER_ADDRESS}`
)
console.log(`Router: ${process.env.UNISWAP_ROUTER_ADDRESS}`)
contracts = {
positionManager: new Contract(
process.env.UNISWAP_POSITION_MANAGER_ADDRESS,
NFTABI
).connect(env.l2Wallet),
router: new Contract(
process.env.UNISWAP_ROUTER_ADDRESS,
RouterABI
).connect(env.l2Wallet),
}
}
const tokenA = await Factory__ERC20.deploy(100000000, 'OVM1', 8, 'OVM1')
await tokenA.deployed()
const tokenB = await Factory__ERC20.deploy(100000000, 'OVM2', 8, 'OVM2')
await tokenB.deployed()
tokens = [tokenA, tokenB]
tokens.sort((a, b) => {
if (a.address > b.address) {
return 1
}
if (a.address < b.address) {
return -1
}
return 0
})
const tx = await tokens[0].transfer(otherWallet.address, 100000)
await tx.wait()
})
it('should deploy the Uniswap ecosystem', async function () {
if (contracts) {
console.log(
'Skipping Uniswap deployment since addresses are already defined.'
)
this.skip()
return
}
contracts = await UniswapV3Deployer.deploy(env.l2Wallet)
})
it('should deploy and initialize a liquidity pool', async () => {
const tx =
await contracts.positionManager.createAndInitializePoolIfNecessary(
tokens[0].address,
tokens[1].address,
FeeAmount.MEDIUM,
// initial ratio of 1/1
BigNumber.from('79228162514264337593543950336')
)
await tx.wait()
})
it('should approve the contracts', async () => {
for (const wallet of [env.l2Wallet, otherWallet]) {
for (const token of tokens) {
let tx = await token
.connect(wallet)
.approve(contracts.positionManager.address, 100000000)
await tx.wait()
tx = await token
.connect(wallet)
.approve(contracts.router.address, 100000000)
await tx.wait()
}
}
})
it('should mint new positions', async () => {
const tx = await contracts.positionManager.mint(
{
token0: tokens[0].address,
token1: tokens[1].address,
tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]),
tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]),
fee: FeeAmount.MEDIUM,
recipient: env.l2Wallet.address,
amount0Desired: 15,
amount1Desired: 15,
amount0Min: 0,
amount1Min: 0,
deadline: Date.now() * 2,
},
{
gasLimit: 10000000,
}
)
await tx.wait()
expect(
await contracts.positionManager.balanceOf(env.l2Wallet.address)
).to.eq(1)
expect(
await contracts.positionManager.tokenOfOwnerByIndex(
env.l2Wallet.address,
0
)
).to.eq(1)
})
it('should swap', async () => {
const tx = await contracts.router.connect(otherWallet).exactInputSingle(
{
tokenIn: tokens[0].address,
tokenOut: tokens[1].address,
fee: FeeAmount.MEDIUM,
recipient: otherWallet.address,
deadline: Date.now() * 2,
amountIn: 10,
amountOutMinimum: 0,
sqrtPriceLimitX96: 0,
},
{
gasLimit: 10000000,
}
)
await tx.wait()
expect(await tokens[1].balanceOf(otherWallet.address)).to.deep.equal(
BigNumber.from('5')
)
})
})
})