1+ const {
2+ setupSigners, deployErc20PointerForCw20, getAdmin, deployWasm, WASM , ABI
3+ } = require ( "./lib" ) ;
4+ const { expect } = require ( "chai" ) ;
5+
6+ describe ( "Wrapped CW20 Pointer" , function ( ) {
7+ let accounts ;
8+ let admin ;
9+ let cw20Address ;
10+ let cw20EvmPointerAddress ;
11+ let cw20EvmPointerContract ;
12+
13+ // We have a link ERC20 -> CW20, this contract wraps the ERC20 to add an extra log when doing
14+ // either a transfer or a transferFrom. An approval is required before transferFrom can be done
15+ // as ERC20 rules.
16+ //
17+ // This extra log tests the case where a DeliverTxHook adds logs to a transaction that already contains
18+ // log.
19+ let wrappedCw20EvmPointerAddress ;
20+ let wrappedCw20EvmPointerContract ;
21+ let deployment ;
22+
23+ const balances = {
24+ admin : 1000000 ,
25+ account0 : 2000000 ,
26+ account1 : 3000000
27+ }
28+
29+ before ( async function ( ) {
30+ deployment = { }
31+
32+ accounts = await setupSigners ( await hre . ethers . getSigners ( ) ) ;
33+
34+ if ( deployment . admin ) {
35+ admin = deployment . admin ;
36+ } else {
37+ admin = deployment . admin = await getAdmin ( ) ;
38+ }
39+
40+ if ( deployment . cw20Address ) {
41+ cw20Address = deployment . cw20Address ;
42+ } else {
43+ cw20Address = deployment . cw20Address = await deployWasm ( WASM . CW20 , accounts [ 0 ] . seiAddress , "cw20" , {
44+ name : "Test" ,
45+ symbol : "TEST" ,
46+ decimals : 6 ,
47+ initial_balances : [
48+ { address : admin . seiAddress , amount : "1000000" } ,
49+ { address : accounts [ 0 ] . seiAddress , amount : "2000000" } ,
50+ { address : accounts [ 1 ] . seiAddress , amount : "3000000" }
51+ ] ,
52+ mint : {
53+ "minter" : admin . seiAddress , "cap" : "99900000000"
54+ }
55+ } ) ;
56+ }
57+
58+ if ( deployment . cw20EvmPointerAddress ) {
59+ cw20EvmPointerAddress = deployment . cw20EvmPointerAddress ;
60+ } else {
61+ cw20EvmPointerAddress = deployment . cw20EvmPointerAddress = await deployErc20PointerForCw20 ( hre . ethers . provider , cw20Address , 5 ) ;
62+ }
63+
64+ contract = new hre . ethers . Contract ( cw20EvmPointerAddress , ABI . ERC20 , hre . ethers . provider ) ;
65+ cw20EvmPointerContract = await contract . connect ( accounts [ 0 ] . signer ) ;
66+
67+ if ( deployment . wrappedCw20EvmPointerAddress ) {
68+ wrappedCw20EvmPointerAddress = deployment . wrappedCw20EvmPointerAddress ;
69+ contract = new hre . ethers . Contract ( wrappedCw20EvmPointerAddress , ABI . ERC20 , hre . ethers . provider ) ;
70+ wrappedCw20EvmPointerContract = await contract . connect ( accounts [ 0 ] . signer ) ;
71+ } else {
72+ contract = await ethers . getContractFactory ( "ERC20PreTransferFromWrapper" )
73+ wrappedCw20EvmPointerContract = await contract . deploy ( await cw20EvmPointerAddress , { gasPrice : ethers . parseUnits ( '100' , 'gwei' ) } ) ;
74+ await wrappedCw20EvmPointerContract . waitForDeployment ( ) ;
75+ wrappedCw20EvmPointerAddress = deployment . wrappedCw20EvmPointerAddress = await wrappedCw20EvmPointerContract . getAddress ( ) ;
76+ }
77+
78+ } ) ;
79+
80+ it ( "CW20 transfer performed through ERC20 pointer contract, multiple trx bridged per block" , async function ( ) {
81+ let sender = accounts [ 0 ] ;
82+ let recipient = accounts [ 1 ] ;
83+
84+ expect ( await cw20EvmPointerContract . balanceOf ( sender . evmAddress ) ) . to . equal (
85+ balances . account0
86+ ) ;
87+ expect ( await cw20EvmPointerContract . balanceOf ( recipient . evmAddress ) ) . to . equal (
88+ balances . account1
89+ ) ;
90+
91+ const txCount = 15 ;
92+
93+ const tx = await cw20EvmPointerContract . approve ( wrappedCw20EvmPointerAddress , txCount ) ;
94+ await tx . wait ( ) ;
95+
96+ const nonce = await ethers . provider . getTransactionCount (
97+ sender . evmAddress
98+ ) ;
99+
100+ const transfer = async ( index ) => {
101+ let tx
102+ try {
103+ tx = await wrappedCw20EvmPointerContract . transferFrom ( sender . evmAddress , recipient . evmAddress , 1 , {
104+ nonce : nonce + ( index - 1 ) ,
105+ gasPrice : ethers . parseUnits ( '100' , 'gwei' )
106+ } ) ;
107+ } catch ( error ) {
108+ console . log ( `Transfer ${ index } send transaction failed` , error ) ;
109+ throw error ;
110+ }
111+
112+ let receipt
113+ try {
114+ receipt = await tx . wait ( ) ;
115+ } catch ( error ) {
116+ console . log ( `Transfer ${ index } send transaction failed` , error ) ;
117+ throw error ;
118+ }
119+ } ;
120+
121+ let promises = [ ] ;
122+ for ( let i = 1 ; i <= txCount ; i ++ ) {
123+ promises . push ( transfer ( i ) ) ;
124+ }
125+
126+ const blockNumber = await ethers . provider . getBlockNumber ( ) ;
127+
128+ await Promise . all ( promises ) ;
129+
130+ expect ( await cw20EvmPointerContract . balanceOf ( sender . evmAddress ) ) . to . equal (
131+ balances . account0 - txCount
132+ ) ;
133+ expect ( await cw20EvmPointerContract . balanceOf ( recipient . evmAddress ) ) . to . equal (
134+ balances . account1 + txCount
135+ ) ;
136+
137+ // check logs
138+ const filter = {
139+ fromBlock : blockNumber ,
140+ toBlock : "latest" ,
141+ address : await cw20EvmPointerContract . getAddress ( ) ,
142+ topics : [ ethers . id ( "Transfer(address,address,uint256)" ) ] ,
143+ } ;
144+
145+ const logs = await ethers . provider . getLogs ( filter ) ;
146+ expect ( logs . length ) . to . equal ( txCount ) ;
147+
148+ /** @type Record<number, Record<string, []unknown>> */
149+ const byBlockThenTx = { } ;
150+ logs . forEach ( ( log ) => {
151+ if ( ! byBlockThenTx [ log . blockNumber ] ) {
152+ byBlockThenTx [ log . blockNumber ] = { } ;
153+ }
154+
155+ if ( ! byBlockThenTx [ log . blockNumber ] [ log . transactionHash ] ) {
156+ byBlockThenTx [ log . blockNumber ] [ log . transactionHash ] = [ ] ;
157+ }
158+
159+ byBlockThenTx [ log . blockNumber ] [ log . transactionHash ] . push ( log ) ;
160+ } ) ;
161+
162+ // Sanity check to ensure we were able to generate a block with multiple logs
163+ expect (
164+ Object . entries ( byBlockThenTx ) . some (
165+ ( [ blockNumber , byTx ] ) => {
166+ const logCountInBlock = Object . values ( byTx ) . reduce ( ( logCount , logsInTx ) => logCount + logsInTx . length , 0 )
167+
168+ return logCountInBlock > 1
169+ }
170+ )
171+ ) . to . be . true ;
172+
173+ Object . entries ( byBlockThenTx ) . forEach (
174+ ( [ blockNumber , byTx ] ) => {
175+ Object . entries ( byTx ) . forEach (
176+ ( [ txHash , logs ] ) => {
177+ const logIndexes = { }
178+ logs . forEach ( ( log , index ) => {
179+ expect ( logIndexes [ log . index ] , `all log indexes in block tx ${ txHash } (at block #${ blockNumber } ) should be unique but log's Index value ${ log . index } for log at position ${ index } has already been seen` ) . to . be . undefined ;
180+ logIndexes [ log . index ] = index
181+ } )
182+ }
183+ )
184+ }
185+ )
186+
187+ const cleanupTx = await cw20EvmPointerContract
188+ . connect ( recipient . signer )
189+ . transfer ( sender . evmAddress , txCount ) ;
190+ await cleanupTx . wait ( ) ;
191+ } ) ;
192+ } ) ;
0 commit comments