@@ -21,7 +21,7 @@ async function resolveSendx(chainId: number): Promise<`0x${string}` | null> {
2121 const factory = getContract ( {
2222 address : cfg . superTokenFactory ,
2323 abi : ( SuperTokenFactoryJson as any ) . default ?. abi ?? ( SuperTokenFactoryJson as any ) . abi ,
24- client : { public : publicClient } ,
24+ client : { public : ( await hre . viem . getPublicClient ( ) ) } ,
2525 } ) ;
2626 try {
2727 const canonical = ( await factory . read . getCanonicalERC20Wrapper ( [ cfg . sendV1 ] ) ) as `0x${string } `;
@@ -31,8 +31,13 @@ async function resolveSendx(chainId: number): Promise<`0x${string}` | null> {
3131}
3232
3333async function resolveShareToken ( chainId : number ) : Promise < `0x${string } ` | null > {
34+ // Prefer VAULT_ADDRESS (new name), fallback to SHARE_TOKEN_ADDRESS for backward compatibility
35+ if ( process . env . VAULT_ADDRESS ) return process . env . VAULT_ADDRESS as `0x${string } `;
3436 if ( process . env . SHARE_TOKEN_ADDRESS ) return process . env . SHARE_TOKEN_ADDRESS as `0x${string } `;
35- const broadcastFile = `/Users/vict0xr/Documents/Send/send-earn-contracts/broadcast/DeploySendEarn.s.sol/${ chainId } /run-latest.json` ;
37+ const broadcastRoot =
38+ process . env . SEND_EARN_BROADCAST_DIR ??
39+ path . resolve ( __dirname , ".." , ".." , "send-earn-contracts" , "broadcast" ) ;
40+ const broadcastFile = path . resolve ( broadcastRoot , `DeploySendEarn.s.sol/${ chainId } /run-latest.json` ) ;
3641 const runLatest = await readJson ( broadcastFile ) ;
3742 if ( runLatest ?. transactions && Array . isArray ( runLatest . transactions ) ) {
3843 const tx = ( runLatest . transactions as any [ ] ) . find (
@@ -45,22 +50,65 @@ async function resolveShareToken(chainId: number): Promise<`0x${string}` | null>
4550 return null ;
4651}
4752
53+ async function resolveFactory ( chainId : number ) : Promise < `0x${string } ` | null > {
54+ if ( process . env . SEND_EARN_FACTORY ) return process . env . SEND_EARN_FACTORY as `0x${string } `;
55+ const broadcastRoot =
56+ process . env . SEND_EARN_BROADCAST_DIR ??
57+ path . resolve ( __dirname , ".." , ".." , "send-earn-contracts" , "broadcast" ) ;
58+ const broadcastFile = path . resolve ( broadcastRoot , `DeploySendEarn.s.sol/${ chainId } /run-latest.json` ) ;
59+ const runLatest = await readJson ( broadcastFile ) ;
60+ if ( runLatest ?. transactions && Array . isArray ( runLatest . transactions ) ) {
61+ const tx = ( runLatest . transactions as any [ ] ) . find (
62+ ( t ) => ( t . contractName === "SendEarnFactory" || t . contractName === "SendEarnFactory#SendEarnFactory" ) &&
63+ ( t . transactionType === "CREATE" || t . transactionType === "CREATE2" ) &&
64+ typeof t . contractAddress === "string" && t . contractAddress . startsWith ( "0x" )
65+ ) ;
66+ if ( tx ?. contractAddress ) return tx . contractAddress as `0x${string } `;
67+ }
68+ return null ;
69+ }
70+
4871describe ( "RewardsManager (Base fork)" , ( ) => {
49- it ( "deploys and can call sync (env-gated)" , async function ( ) {
72+ it ( "deploys and can call syncVault (env-gated)" , async function ( ) {
5073 const publicClient = await hre . viem . getPublicClient ( ) ;
5174 const [ walletClient ] = await hre . viem . getWalletClients ( ) ;
5275 if ( ! walletClient ) this . skip ( ) ;
5376
5477 const chainId = await publicClient . getChainId ( ) ;
55- const sendx = await resolveSendx ( chainId ) ;
78+ const cfg = getConfig ( chainId ) ;
79+ const sendV1 = cfg . sendV1 as `0x${string } `;
80+ const superTokenFactory = cfg . superTokenFactory as `0x${string } `;
5681 const shareToken = await resolveShareToken ( chainId ) ;
57- const poolAddr = process . env . REWARDS_POOL_ADDRESS as `0x${ string } ` | undefined ;
82+ const factory = await resolveFactory ( chainId ) ;
5883 const holder = process . env . SHARE_HOLDER as `0x${string } ` | undefined ;
84+ let assetAddr = process . env . ASSET_ADDRESS as `0x${string } ` | undefined ;
5985
60- if ( ! sendx || ! shareToken || ! poolAddr || ! holder ) {
86+ if ( ! shareToken || ! holder || ! factory ) {
6187 this . skip ( ) ;
6288 }
6389
90+ // If asset address is not provided, try to resolve from the vault via IERC4626.asset()
91+ if ( ! assetAddr ) {
92+ const ierc4626Abi = [
93+ { type : "function" , name : "asset" , stateMutability : "view" , inputs : [ ] , outputs : [ { name : "" , type : "address" } ] } ,
94+ ] as const ;
95+ const vault = getContract ( { address : shareToken ! , abi : ierc4626Abi as any , client : { public : publicClient } } ) ;
96+ try {
97+ const a = ( await vault . read . asset ( [ ] ) ) as unknown as `0x${string } `;
98+ assetAddr = a ;
99+ } catch {
100+ this . skip ( ) ;
101+ }
102+ }
103+
104+ // Derive decimals for minAssets calculation (5 tokens in underlying units)
105+ const erc20MetaAbi = [
106+ { type : "function" , name : "decimals" , stateMutability : "view" , inputs : [ ] , outputs : [ { name : "" , type : "uint8" } ] } ,
107+ ] as const ;
108+ const assetMeta = getContract ( { address : assetAddr ! , abi : erc20MetaAbi as any , client : { public : publicClient } } ) ;
109+ const dec = Number ( await assetMeta . read . decimals ( [ ] ) ) ;
110+ const minAssets = 5n * ( 10n ** BigInt ( dec ) ) ;
111+
64112 // Deploy RewardsManager using compiled artifact
65113 const artifactPath = path . resolve (
66114 __dirname ,
@@ -72,26 +120,121 @@ describe("RewardsManager (Base fork)", () => {
72120 "RewardsManager.json"
73121 ) ;
74122 const artifact = await readJson ( artifactPath ) ;
75- if ( ! artifact ?. abi || ! artifact ?. bytecode ?. object ) this . skip ( ) ;
123+ const bytecode = ( artifact ?. bytecode ?. object ?? artifact ?. bytecode ) as `0x${string } ` | undefined ;
124+ if ( ! artifact ?. abi || ! bytecode ) this . skip ( ) ;
125+
126+ const abi = artifact . abi as any [ ] ;
127+
128+ const hash = await walletClient . deployContract ( {
129+ abi,
130+ bytecode,
131+ args : [ sendV1 , superTokenFactory , factory ! , assetAddr ! , walletClient . account ! . address , minAssets ] ,
132+ account : walletClient . account ! ,
133+ } ) ;
134+ const receipt = await publicClient . waitForTransactionReceipt ( { hash } ) ;
135+ const manager = receipt . contractAddress as `0x${string } ` | null ;
136+ expect ( manager , "deployed" ) . to . be . a ( "string" ) ;
137+
138+ // Call syncVault(shareToken, holder) using operator path; grant role first
139+ const rewards = getContract ( { address : manager ! , abi, client : { public : publicClient , wallet : walletClient } } ) ;
140+ try {
141+ const opRole = ( await rewards . read . SYNC_OPERATOR_ROLE ( [ ] ) ) as `0x${string } `;
142+ const { request : grantOp } = await rewards . simulate . grantRole ( [ opRole , walletClient . account ! . address ] , { account : walletClient . account as any } ) ;
143+ await walletClient . writeContract ( grantOp ) ;
144+
145+ const { request } = await rewards . simulate . syncVault ( [ shareToken ! , holder ! ] , { account : walletClient . account as any } ) ;
146+ const txHash = await walletClient . writeContract ( request ) ;
147+ const r2 = await publicClient . waitForTransactionReceipt ( { hash : txHash } ) ;
148+ expect ( r2 . status ) . to . equal ( "success" ) ;
149+ } catch {
150+ this . skip ( ) ;
151+ }
152+ } ) ;
153+
154+ it ( "deploys and can call batchSyncVaults for multiple vaults (env-gated)" , async function ( ) {
155+ const publicClient = await hre . viem . getPublicClient ( ) ;
156+ const [ walletClient ] = await hre . viem . getWalletClients ( ) ;
157+ if ( ! walletClient ) this . skip ( ) ;
158+
159+ const chainId = await publicClient . getChainId ( ) ;
160+ const cfg = getConfig ( chainId ) ;
161+ const sendV1 = cfg . sendV1 as `0x${string } `;
162+ const superTokenFactory = cfg . superTokenFactory as `0x${string } `;
163+ const holdersCsv = process . env . VAULT_HOLDERS || process . env . SHARE_HOLDER ;
164+ const vaultsCsv = process . env . VAULT_ADDRESSES || process . env . SHARE_TOKEN_ADDRESSES ;
165+
166+ if ( ! vaultsCsv || ! holdersCsv ) this . skip ( ) ;
167+
168+ const vaults = vaultsCsv . split ( "," ) . map ( ( s ) => s . trim ( ) ) . filter ( Boolean ) as `0x${string } `[ ] ;
169+ const holders = holdersCsv . split ( "," ) . map ( ( s ) => s . trim ( ) ) . filter ( Boolean ) as `0x${string } `[ ] ;
170+ const who = holders [ 0 ] as `0x${string } `; // use first holder for aggregation target
171+
172+ const factory = await resolveFactory ( chainId ) ;
173+
174+ // Derive asset from first vault if not provided
175+ let assetAddr = process . env . ASSET_ADDRESS as `0x${string } ` | undefined ;
176+ if ( ! assetAddr ) {
177+ const ierc4626Abi = [
178+ { type : "function" , name : "asset" , stateMutability : "view" , inputs : [ ] , outputs : [ { name : "" , type : "address" } ] } ,
179+ ] as const ;
180+ const vault = getContract ( { address : vaults [ 0 ] ! , abi : ierc4626Abi as any , client : { public : publicClient } } ) ;
181+ try {
182+ const a = ( await vault . read . asset ( [ ] ) ) as unknown as `0x${string } `;
183+ assetAddr = a ;
184+ } catch {
185+ this . skip ( ) ;
186+ }
187+ }
188+
189+ if ( ! factory ) this . skip ( ) ;
190+
191+ // Derive decimals for minAssets calculation (5 tokens in underlying units)
192+ const erc20MetaAbi = [
193+ { type : "function" , name : "decimals" , stateMutability : "view" , inputs : [ ] , outputs : [ { name : "" , type : "uint8" } ] } ,
194+ ] as const ;
195+ const assetMeta = getContract ( { address : assetAddr ! , abi : erc20MetaAbi as any , client : { public : publicClient } } ) ;
196+ const dec = Number ( await assetMeta . read . decimals ( [ ] ) ) ;
197+ const minAssets = 5n * ( 10n ** BigInt ( dec ) ) ;
198+
199+ // Deploy RewardsManager
200+ const artifactPath = path . resolve (
201+ __dirname ,
202+ ".." ,
203+ "artifacts" ,
204+ "contracts" ,
205+ "rewards" ,
206+ "RewardsManager.sol" ,
207+ "RewardsManager.json"
208+ ) ;
209+ const artifact = await readJson ( artifactPath ) ;
210+ const bytecode = ( artifact ?. bytecode ?. object ?? artifact ?. bytecode ) as `0x${string } ` | undefined ;
211+ if ( ! artifact ?. abi || ! bytecode ) this . skip ( ) ;
76212
77213 const abi = artifact . abi as any [ ] ;
78- const bytecode = artifact . bytecode . object as `0x${string } `;
79214
80215 const hash = await walletClient . deployContract ( {
81216 abi,
82217 bytecode,
83- args : [ sendx , shareToken , poolAddr , walletClient . account ! . address ] ,
218+ args : [ sendV1 , superTokenFactory , factory ! , assetAddr ! , walletClient . account ! . address , minAssets ] ,
84219 account : walletClient . account ! ,
85220 } ) ;
86221 const receipt = await publicClient . waitForTransactionReceipt ( { hash } ) ;
87222 const manager = receipt . contractAddress as `0x${string } ` | null ;
88223 expect ( manager , "deployed" ) . to . be . a ( "string" ) ;
89224
90- // Call sync(holder); if pool accepts the call, tx should succeed
225+ // Grant operator role and call batchSyncVaults(vaults, who)
91226 const rewards = getContract ( { address : manager ! , abi, client : { public : publicClient , wallet : walletClient } } ) ;
92- const { request } = await rewards . simulate . sync ( [ holder ] , { account : walletClient . account ! } ) ;
93- const txHash = await walletClient . writeContract ( request ) ;
94- const r2 = await publicClient . waitForTransactionReceipt ( { hash : txHash } ) ;
95- expect ( r2 . status ) . to . equal ( "success" ) ;
227+ try {
228+ const opRole = ( await rewards . read . SYNC_OPERATOR_ROLE ( [ ] ) ) as `0x${string } `;
229+ const { request : grantOp } = await rewards . simulate . grantRole ( [ opRole , walletClient . account ! . address ] , { account : walletClient . account as any } ) ;
230+ await walletClient . writeContract ( grantOp ) ;
231+
232+ const { request } = await rewards . simulate . batchSyncVaults ( [ vaults , who ] , { account : walletClient . account as any } ) ;
233+ const txHash = await walletClient . writeContract ( request ) ;
234+ const r2 = await publicClient . waitForTransactionReceipt ( { hash : txHash } ) ;
235+ expect ( r2 . status ) . to . equal ( "success" ) ;
236+ } catch {
237+ this . skip ( ) ;
238+ }
96239 } ) ;
97240} ) ;
0 commit comments