Transaction Hash:
Block:
23728630 at Nov-04-2025 09:27:11 PM +UTC
Transaction Fee:
0.000713448805811322 ETH
$1.41
Gas Used:
185,451 Gas / 3.847101422 Gwei
Emitted Events:
| 215 |
WstETH.Transfer( from=[Sender] 0xf7b4728d623ba580e64b7b69d61885bd20249da1, to=[Receiver] 0xb773bcc5b325ad9ac6b36e1a046ad4466833a16e, value=49457447620960000 )
|
| 216 |
WstETH.Approval( owner=[Sender] 0xf7b4728d623ba580e64b7b69d61885bd20249da1, spender=[Receiver] 0xb773bcc5b325ad9ac6b36e1a046ad4466833a16e, value=0 )
|
| 217 |
AxelarGasServiceProxy.0x999d431b58761213cf53af96262b67a069cbd963499fd8effd1e21556217b841( 0x999d431b58761213cf53af96262b67a069cbd963499fd8effd1e21556217b841, 0x000000000000000000000000b773bcc5b325ad9ac6b36e1a046ad4466833a16e, 0x90ec257f5e6083de074e84d318658800e7e70cf5753ed2587c8d2df05e0e172e, 00000000000000000000000000000000000000000000000000000000000000c0, 0000000000000000000000000000000000000000000000000000000000000100, 0000000000000000000000000000000000000000000000000000000000000180, 00000000000000000000000000000000000000000000000000afb5495cf1df00, 000000000000000000000000000000000000000000000000000027380b0bf096, 000000000000000000000000f7b4728d623ba580e64b7b69d61885bd20249da1, 0000000000000000000000000000000000000000000000000000000000000007, 6e657574726f6e00000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000042, 6e657574726f6e317a76657375647364667875737a30366a7a74707068346433, 683578367665676c71737370786e733276326a716d6c396e687977736b636339, 3233000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000006, 7773744554480000000000000000000000000000000000000000000000000000 )
|
| 218 |
WstETH.Transfer( from=[Receiver] 0xb773bcc5b325ad9ac6b36e1a046ad4466833a16e, to=0x4F4495243837681061C4743b74B3eEdf548D56A5, value=49457447620960000 )
|
| 219 |
WstETH.Approval( owner=[Receiver] 0xb773bcc5b325ad9ac6b36e1a046ad4466833a16e, spender=0x4F4495243837681061C4743b74B3eEdf548D56A5, value=115792089237316195423570985008687907853269984665640564039455661467485754130646 )
|
| 220 |
0x4f4495243837681061c4743b74b3eedf548d56a5.0x7e50569d26be643bda7757722291ec66b1be66d8283474ae3fab5a98f878a7a2( 0x7e50569d26be643bda7757722291ec66b1be66d8283474ae3fab5a98f878a7a2, 0x000000000000000000000000b773bcc5b325ad9ac6b36e1a046ad4466833a16e, 0x90ec257f5e6083de074e84d318658800e7e70cf5753ed2587c8d2df05e0e172e, 00000000000000000000000000000000000000000000000000000000000000a0, 00000000000000000000000000000000000000000000000000000000000000e0, 0000000000000000000000000000000000000000000000000000000000000160, 0000000000000000000000000000000000000000000000000000000000000a80, 00000000000000000000000000000000000000000000000000afb5495cf1df00, 0000000000000000000000000000000000000000000000000000000000000007, 6e657574726f6e00000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000042, 6e657574726f6e317a76657375647364667875737a30366a7a74707068346433, 683578367665676c71737370786e733276326a716d6c396e687977736b636339, 3233000000000000000000000000000000000000000000000000000000000000, 00000000000000000000000000000000000000000000000000000000000008f6, 000000027b22737761705f616e645f616374696f6e5f776974685f7265636f76, 6572223a7b22757365725f73776170223a7b22737761705f65786163745f6173, 7365745f696e223a7b22737761705f76656e75655f6e616d65223a226e657574, 726f6e2d6c69646f2d736174656c6c697465222c226f7065726174696f6e7322, 3a5b7b22706f6f6c223a226e657574726f6e31756737343071726b7175787a72, 6b326868323971726c7833736b746b666d6c336a65376a757573633274653778, 6d767373636e73306e32777279222c2264656e6f6d5f696e223a226962632f34, 4430343038353136373737373635394331313738344133353644364230443133, 4435433746304345373746374442313135324645303341324445324342463222, 2c2264656e6f6d5f6f7574223a22666163746f72792f6e657574726f6e317567, 37343071726b7175787a726b326868323971726c7833736b746b666d6c336a65, 376a7575736332746537786d767373636e73306e327772792f77737445544822, 7d5d7d7d2c226d696e5f6173736574223a7b226e6174697665223a7b2264656e, 6f6d223a22666163746f72792f6e657574726f6e31756737343071726b717578, 7a726b326868323971726c7833736b746b666d6c336a65376a75757363327465, 37786d767373636e73306e327772792f777374455448222c22616d6f756e7422, 3a223439343535353932383930323336333333227d7d2c2274696d656f75745f, 74696d657374616d70223a313736323239333939383034393031313531352c22, 706f73745f737761705f616374696f6e223a7b226962635f7472616e73666572, 223a7b226962635f696e666f223a7b22736f757263655f6368616e6e656c223a, 226368616e6e656c2d3130222c227265636569766572223a226f736d6f31366d, 78766378727138746b756376616674636d34673767757166646b78396a653637, 77686537222c22666565223a7b22726563765f666565223a5b5d2c2261636b5f, 666565223a5b7b2264656e6f6d223a22756e74726e222c22616d6f756e74223a, 22313030303030227d5d2c2274696d656f75745f666565223a5b7b2264656e6f, 6d223a22756e74726e222c22616d6f756e74223a22313030303030227d5d7d2c, 226d656d6f223a22222c227265636f7665725f61646472657373223a226e6575, 74726f6e31366d78766378727138746b756376616674636d3467376775716664, 6b78396a656b3635393474227d2c226665655f73776170223a7b22737761705f, 76656e75655f6e616d65223a226e657574726f6e2d617374726f706f7274222c, 226f7065726174696f6e73223a5b7b22706f6f6c223a226e657574726f6e3167, 68686c7a367a6663333372343975307937377533736866356138386863773277, 72707776756b793775637332347965397970716a3361797075222c2264656e6f, 6d5f696e223a226962632f344430343038353136373737373635394331313738, 3441333536443642304431334435433746304345373746374442313135324645, 3033413244453243424632222c2264656e6f6d5f6f7574223a22666163746f72, 792f6e657574726f6e31756737343071726b7175787a726b326868323971726c, 7833736b746b666d6c336a65376a7575736332746537786d767373636e73306e, 327772792f777374455448227d2c7b22706f6f6c223a226e657574726f6e3174, 73656b38393670707768397a6d7678326b63636b36306577726e67346172786e, 63326532796b6636707232756e306333633973386d65686d38222c2264656e6f, 6d5f696e223a22666163746f72792f6e657574726f6e31756737343071726b71, 75787a726b326868323971726c7833736b746b666d6c336a65376a7575736332, 746537786d767373636e73306e327772792f777374455448222c2264656e6f6d, 5f6f7574223a226962632f423535394138304436323234394338414130374133, 3830453241324245413645354341394136463037394339313243334139453942, 3439343130354534463831227d2c7b22706f6f6c223a226e657574726f6e316c, 343874737132373238747a30756d68376c343035743630683077746874773930, 3874653970666d636667766b7538636d32657374396871336a222c2264656e6f, 6d5f696e223a226962632f423535394138304436323234394338414130374133, 3830453241324245413645354341394136463037394339313243334139453942, 3439343130354534463831222c2264656e6f6d5f6f7574223a226962632f4334, 4346463436464436444533354341344346344345303331453634334338464443, 394241344239394145353938453942304544393846453341323331394639227d, 2c7b22706f6f6c223a226e657574726f6e31346d73736334796375646b6d7a6e, 73747774636d336173737234677434306e713338783538617035373474686772, 78677a72307170786875396d222c2264656e6f6d5f696e223a226962632f4334, 4346463436464436444533354341344346344345303331453634334338464443, 394241344239394145353938453942304544393846453341323331394639222c, 2264656e6f6d5f6f7574223a22666163746f72792f6e657574726f6e31743571, 726a747972796838677a7438303071723576796c6868326638636d7834776d7a, 396d632f75676f6464617264227d2c7b22706f6f6c223a226e657574726f6e31, 3037797974663634756c396c36616a7865766679647433706b78787761303932, 34737267743975357165617a6e71337863613271777037326c73222c2264656e, 6f6d5f696e223a22666163746f72792f6e657574726f6e31743571726a747972, 796838677a7438303071723576796c6868326638636d7834776d7a396d632f75, 676f6464617264222c2264656e6f6d5f6f7574223a22756e74726e227d5d2c22, 726566756e645f61646472657373223a226e657574726f6e31366d7876637872, 7138746b756376616674636d34673767757166646b78396a656b363539347422, 7d7d7d2c22616666696c6961746573223a5b5d2c227265636f766572795f6164, 6472223a226e657574726f6e31366d78766378727138746b756376616674636d, 34673767757166646b78396a656b3635393474227d7d00000000000000000000, 0000000000000000000000000000000000000000000000000000000000000006, 7773744554480000000000000000000000000000000000000000000000000000 )
|
Account State Difference:
| Address | Before | After | State Difference | ||
|---|---|---|---|---|---|
| 0x2d5d7d31...09a082712 | (Axelar: Gas Service) | 0.099209113062099538 Eth | 0.09925223471908324 Eth | 0.000043121656983702 | |
|
0x4838B106...B0BAD5f97
Miner
| (Titan Builder) | 2.384234444079157519 Eth | 2.384316207212256187 Eth | 0.000081763133098668 | |
| 0x7f39C581...c935E2Ca0 | |||||
| 0xf7b4728D...d20249DA1 |
0.001438343410494207 Eth
Nonce: 14
|
0.000681772947699183 Eth
Nonce: 15
| 0.000756570462795024 |
Execution Trace
ETH 0.000043121656983702
0xb773bcc5b325ad9ac6b36e1a046ad4466833a16e.d421c105( )
ETH 0.000043121656983702
0xbeab88e91c27619fb617f7359dabc10d2f8e6d0d.d421c105( )Axelar: Gateway.935b13f6( )-
AxelarGateway.tokenAddresses( symbol=wstETH ) => ( 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0 )
-
-
WstETH.transferFrom( sender=0xf7b4728D623ba580e64B7B69d61885Bd20249DA1, recipient=0xB773bCc5B325ad9AC6B36e1A046AD4466833A16E, amount=49457447620960000 ) => ( True )
ETH 0.000043121656983702
AxelarGasServiceProxy.c62c2002( )- ETH 0.000043121656983702
AxelarGasService.payNativeGasForContractCallWithToken( sender=0xB773bCc5B325ad9AC6B36e1A046AD4466833A16E, destinationChain=neutron, destinationAddress=neutron1zvesudsdfxusz06jztpph4d3h5x6veglqsspxns2v2jqml9nhywskcc923, payload=0x000000027B22737761705F616E645F616374696F6E5F776974685F7265636F766572223A7B22757365725F73776170223A7B22737761705F65786163745F61737365745F696E223A7B22737761705F76656E75655F6E616D65223A226E657574726F6E2D6C69646F2D736174656C6C697465222C226F7065726174696F6E73223A5B7B22706F6F6C223A226E657574726F6E31756737343071726B7175787A726B326868323971726C7833736B746B666D6C336A65376A7575736332746537786D767373636E73306E32777279222C2264656E6F6D5F696E223A226962632F34443034303835313637373737363539433131373834413335364436423044313344354337463043453737463744423131353246453033413244453243424632222C2264656E6F6D5F6F7574223A22666163746F72792F6E657574726F6E31756737343071726B7175787A726B326868323971726C7833736B746B666D6C336A65376A7575736332746537786D767373636E73306E327772792F777374455448227D5D7D7D2C226D696E5F6173736574223A7B226E6174697665223A7B2264656E6F6D223A22666163746F72792F6E657574726F6E31756737343071726B7175787A726B326868323971726C7833736B746B666D6C336A65376A7575736332746537786D767373636E73306E327772792F777374455448222C22616D6F756E74223A223439343535353932383930323336333333227D7D2C2274696D656F75745F74696D657374616D70223A313736323239333939383034393031313531352C22706F73745F737761705F616374696F6E223A7B226962635F7472616E73666572223A7B226962635F696E666F223A7B22736F757263655F6368616E6E656C223A226368616E6E656C2D3130222C227265636569766572223A226F736D6F31366D78766378727138746B756376616674636D34673767757166646B78396A65363777686537222C22666565223A7B22726563765F666565223A5B5D2C2261636B5F666565223A5B7B2264656E6F6D223A22756E74726E222C22616D6F756E74223A22313030303030227D5D2C2274696D656F75745F666565223A5B7B2264656E6F6D223A22756E74726E222C22616D6F756E74223A22313030303030227D5D7D2C226D656D6F223A22222C227265636F7665725F61646472657373223A226E657574726F6E31366D78766378727138746B756376616674636D34673767757166646B78396A656B3635393474227D2C226665655F73776170223A7B22737761705F76656E75655F6E616D65223A226E657574726F6E2D617374726F706F7274222C226F7065726174696F6E73223A5B7B22706F6F6C223A226E657574726F6E316768686C7A367A666333337234397530793737753373686635613838686377327772707776756B793775637332347965397970716A3361797075222C2264656E6F6D5F696E223A226962632F34443034303835313637373737363539433131373834413335364436423044313344354337463043453737463744423131353246453033413244453243424632222C2264656E6F6D5F6F7574223A22666163746F72792F6E657574726F6E31756737343071726B7175787A726B326868323971726C7833736B746B666D6C336A65376A7575736332746537786D767373636E73306E327772792F777374455448227D2C7B22706F6F6C223A226E657574726F6E317473656B38393670707768397A6D7678326B63636B36306577726E67346172786E63326532796B6636707232756E306333633973386D65686D38222C2264656E6F6D5F696E223A22666163746F72792F6E657574726F6E31756737343071726B7175787A726B326868323971726C7833736B746B666D6C336A65376A7575736332746537786D767373636E73306E327772792F777374455448222C2264656E6F6D5F6F7574223A226962632F42353539413830443632323439433841413037413338304532413242454136453543413941364630373943393132433341394539423439343130354534463831227D2C7B22706F6F6C223A226E657574726F6E316C343874737132373238747A30756D68376C3430357436306830777468747739303874653970666D636667766B7538636D32657374396871336A222C2264656E6F6D5F696E223A226962632F42353539413830443632323439433841413037413338304532413242454136453543413941364630373943393132433341394539423439343130354534463831222C2264656E6F6D5F6F7574223A226962632F43344346463436464436444533354341344346344345303331453634334338464443394241344239394145353938453942304544393846453341323331394639227D2C7B22706F6F6C223A226E657574726F6E31346D73736334796375646B6D7A6E73747774636D336173737234677434306E71333878353861703537347468677278677A72307170786875396D222C2264656E6F6D5F696E223A226962632F43344346463436464436444533354341344346344345303331453634334338464443394241344239394145353938453942304544393846453341323331394639222C2264656E6F6D5F6F7574223A22666163746F72792F6E657574726F6E31743571726A747972796838677A7438303071723576796C6868326638636D7834776D7A396D632F75676F6464617264227D2C7B22706F6F6C223A226E657574726F6E313037797974663634756C396C36616A7865766679647433706B7878776130393234737267743975357165617A6E71337863613271777037326C73222C2264656E6F6D5F696E223A22666163746F72792F6E657574726F6E31743571726A747972796838677A7438303071723576796C6868326638636D7834776D7A396D632F75676F6464617264222C2264656E6F6D5F6F7574223A22756E74726E227D5D2C22726566756E645F61646472657373223A226E657574726F6E31366D78766378727138746B756376616674636D34673767757166646B78396A656B3635393474227D7D7D2C22616666696C6961746573223A5B5D2C227265636F766572795F61646472223A226E657574726F6E31366D78766378727138746B756376616674636D34673767757166646B78396A656B3635393474227D7D, symbol=wstETH, amount=49457447620960000, refundAddress=0xf7b4728D623ba580e64B7B69d61885Bd20249DA1 )
- ETH 0.000043121656983702
-
WstETH.balanceOf( account=0xB773bCc5B325ad9AC6B36e1A046AD4466833A16E ) => ( 49457447620960000 )
Axelar: Gateway.b5417084( )AxelarGateway.callContractWithToken( destinationChain=neutron, destinationContractAddress=neutron1zvesudsdfxusz06jztpph4d3h5x6veglqsspxns2v2jqml9nhywskcc923, payload=0x000000027B22737761705F616E645F616374696F6E5F776974685F7265636F766572223A7B22757365725F73776170223A7B22737761705F65786163745F61737365745F696E223A7B22737761705F76656E75655F6E616D65223A226E657574726F6E2D6C69646F2D736174656C6C697465222C226F7065726174696F6E73223A5B7B22706F6F6C223A226E657574726F6E31756737343071726B7175787A726B326868323971726C7833736B746B666D6C336A65376A7575736332746537786D767373636E73306E32777279222C2264656E6F6D5F696E223A226962632F34443034303835313637373737363539433131373834413335364436423044313344354337463043453737463744423131353246453033413244453243424632222C2264656E6F6D5F6F7574223A22666163746F72792F6E657574726F6E31756737343071726B7175787A726B326868323971726C7833736B746B666D6C336A65376A7575736332746537786D767373636E73306E327772792F777374455448227D5D7D7D2C226D696E5F6173736574223A7B226E6174697665223A7B2264656E6F6D223A22666163746F72792F6E657574726F6E31756737343071726B7175787A726B326868323971726C7833736B746B666D6C336A65376A7575736332746537786D767373636E73306E327772792F777374455448222C22616D6F756E74223A223439343535353932383930323336333333227D7D2C2274696D656F75745F74696D657374616D70223A313736323239333939383034393031313531352C22706F73745F737761705F616374696F6E223A7B226962635F7472616E73666572223A7B226962635F696E666F223A7B22736F757263655F6368616E6E656C223A226368616E6E656C2D3130222C227265636569766572223A226F736D6F31366D78766378727138746B756376616674636D34673767757166646B78396A65363777686537222C22666565223A7B22726563765F666565223A5B5D2C2261636B5F666565223A5B7B2264656E6F6D223A22756E74726E222C22616D6F756E74223A22313030303030227D5D2C2274696D656F75745F666565223A5B7B2264656E6F6D223A22756E74726E222C22616D6F756E74223A22313030303030227D5D7D2C226D656D6F223A22222C227265636F7665725F61646472657373223A226E657574726F6E31366D78766378727138746B756376616674636D34673767757166646B78396A656B3635393474227D2C226665655F73776170223A7B22737761705F76656E75655F6E616D65223A226E657574726F6E2D617374726F706F7274222C226F7065726174696F6E73223A5B7B22706F6F6C223A226E657574726F6E316768686C7A367A666333337234397530793737753373686635613838686377327772707776756B793775637332347965397970716A3361797075222C2264656E6F6D5F696E223A226962632F34443034303835313637373737363539433131373834413335364436423044313344354337463043453737463744423131353246453033413244453243424632222C2264656E6F6D5F6F7574223A22666163746F72792F6E657574726F6E31756737343071726B7175787A726B326868323971726C7833736B746B666D6C336A65376A7575736332746537786D767373636E73306E327772792F777374455448227D2C7B22706F6F6C223A226E657574726F6E317473656B38393670707768397A6D7678326B63636B36306577726E67346172786E63326532796B6636707232756E306333633973386D65686D38222C2264656E6F6D5F696E223A22666163746F72792F6E657574726F6E31756737343071726B7175787A726B326868323971726C7833736B746B666D6C336A65376A7575736332746537786D767373636E73306E327772792F777374455448222C2264656E6F6D5F6F7574223A226962632F42353539413830443632323439433841413037413338304532413242454136453543413941364630373943393132433341394539423439343130354534463831227D2C7B22706F6F6C223A226E657574726F6E316C343874737132373238747A30756D68376C3430357436306830777468747739303874653970666D636667766B7538636D32657374396871336A222C2264656E6F6D5F696E223A226962632F42353539413830443632323439433841413037413338304532413242454136453543413941364630373943393132433341394539423439343130354534463831222C2264656E6F6D5F6F7574223A226962632F43344346463436464436444533354341344346344345303331453634334338464443394241344239394145353938453942304544393846453341323331394639227D2C7B22706F6F6C223A226E657574726F6E31346D73736334796375646B6D7A6E73747774636D336173737234677434306E71333878353861703537347468677278677A72307170786875396D222C2264656E6F6D5F696E223A226962632F43344346463436464436444533354341344346344345303331453634334338464443394241344239394145353938453942304544393846453341323331394639222C2264656E6F6D5F6F7574223A22666163746F72792F6E657574726F6E31743571726A747972796838677A7438303071723576796C6868326638636D7834776D7A396D632F75676F6464617264227D2C7B22706F6F6C223A226E657574726F6E313037797974663634756C396C36616A7865766679647433706B7878776130393234737267743975357165617A6E71337863613271777037326C73222C2264656E6F6D5F696E223A22666163746F72792F6E657574726F6E31743571726A747972796838677A7438303071723576796C6868326638636D7834776D7A396D632F75676F6464617264222C2264656E6F6D5F6F7574223A22756E74726E227D5D2C22726566756E645F61646472657373223A226E657574726F6E31366D78766378727138746B756376616674636D34673767757166646B78396A656B3635393474227D7D7D2C22616666696C6961746573223A5B5D2C227265636F766572795F61646472223A226E657574726F6E31366D78766378727138746B756376616674636D34673767757166646B78396A656B3635393474227D7D, symbol=wstETH, amount=49457447620960000 )-
WstETH.transferFrom( sender=0xB773bCc5B325ad9AC6B36e1A046AD4466833A16E, recipient=0x4F4495243837681061C4743b74B3eEdf548D56A5, amount=49457447620960000 ) => ( True )
-
File 1 of 4: WstETH
File 2 of 4: AxelarGasServiceProxy
File 3 of 4: AxelarGateway
File 4 of 4: AxelarGasService
// SPDX-License-Identifier: MIT AND GPL-3.0
// File: @openzeppelin/contracts/utils/Context.sol
pragma solidity >=0.6.0 <0.8.0;
/*
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with GSN meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address payable) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes memory) {
this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
return msg.data;
}
}
// File: @openzeppelin/contracts/token/ERC20/IERC20.sol
pragma solidity >=0.6.0 <0.8.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `recipient`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address recipient, uint256 amount) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `sender` to `recipient` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
}
// File: @openzeppelin/contracts/math/SafeMath.sol
pragma solidity >=0.6.0 <0.8.0;
/**
* @dev Wrappers over Solidity's arithmetic operations with added overflow
* checks.
*
* Arithmetic operations in Solidity wrap on overflow. This can easily result
* in bugs, because programmers usually assume that an overflow raises an
* error, which is the standard behavior in high level programming languages.
* `SafeMath` restores this intuition by reverting the transaction when an
* operation overflows.
*
* Using this library instead of the unchecked operations eliminates an entire
* class of bugs, so it's recommended to use it always.
*/
library SafeMath {
/**
* @dev Returns the addition of two unsigned integers, with an overflow flag.
*
* _Available since v3.4._
*/
function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
uint256 c = a + b;
if (c < a) return (false, 0);
return (true, c);
}
/**
* @dev Returns the substraction of two unsigned integers, with an overflow flag.
*
* _Available since v3.4._
*/
function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
if (b > a) return (false, 0);
return (true, a - b);
}
/**
* @dev Returns the multiplication of two unsigned integers, with an overflow flag.
*
* _Available since v3.4._
*/
function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
if (a == 0) return (true, 0);
uint256 c = a * b;
if (c / a != b) return (false, 0);
return (true, c);
}
/**
* @dev Returns the division of two unsigned integers, with a division by zero flag.
*
* _Available since v3.4._
*/
function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
if (b == 0) return (false, 0);
return (true, a / b);
}
/**
* @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
*
* _Available since v3.4._
*/
function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
if (b == 0) return (false, 0);
return (true, a % b);
}
/**
* @dev Returns the addition of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `+` operator.
*
* Requirements:
*
* - Addition cannot overflow.
*/
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a, "SafeMath: addition overflow");
return c;
}
/**
* @dev Returns the subtraction of two unsigned integers, reverting on
* overflow (when the result is negative).
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
*
* - Subtraction cannot overflow.
*/
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
require(b <= a, "SafeMath: subtraction overflow");
return a - b;
}
/**
* @dev Returns the multiplication of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `*` operator.
*
* Requirements:
*
* - Multiplication cannot overflow.
*/
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
if (a == 0) return 0;
uint256 c = a * b;
require(c / a == b, "SafeMath: multiplication overflow");
return c;
}
/**
* @dev Returns the integer division of two unsigned integers, reverting on
* division by zero. The result is rounded towards zero.
*
* Counterpart to Solidity's `/` operator. Note: this function uses a
* `revert` opcode (which leaves remaining gas untouched) while Solidity
* uses an invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function div(uint256 a, uint256 b) internal pure returns (uint256) {
require(b > 0, "SafeMath: division by zero");
return a / b;
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* reverting when dividing by zero.
*
* Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function mod(uint256 a, uint256 b) internal pure returns (uint256) {
require(b > 0, "SafeMath: modulo by zero");
return a % b;
}
/**
* @dev Returns the subtraction of two unsigned integers, reverting with custom message on
* overflow (when the result is negative).
*
* CAUTION: This function is deprecated because it requires allocating memory for the error
* message unnecessarily. For custom revert reasons use {trySub}.
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
*
* - Subtraction cannot overflow.
*/
function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
require(b <= a, errorMessage);
return a - b;
}
/**
* @dev Returns the integer division of two unsigned integers, reverting with custom message on
* division by zero. The result is rounded towards zero.
*
* CAUTION: This function is deprecated because it requires allocating memory for the error
* message unnecessarily. For custom revert reasons use {tryDiv}.
*
* Counterpart to Solidity's `/` operator. Note: this function uses a
* `revert` opcode (which leaves remaining gas untouched) while Solidity
* uses an invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
require(b > 0, errorMessage);
return a / b;
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* reverting with custom message when dividing by zero.
*
* CAUTION: This function is deprecated because it requires allocating memory for the error
* message unnecessarily. For custom revert reasons use {tryMod}.
*
* Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
require(b > 0, errorMessage);
return a % b;
}
}
// File: @openzeppelin/contracts/token/ERC20/ERC20.sol
pragma solidity >=0.6.0 <0.8.0;
/**
* @dev Implementation of the {IERC20} interface.
*
* This implementation is agnostic to the way tokens are created. This means
* that a supply mechanism has to be added in a derived contract using {_mint}.
* For a generic mechanism see {ERC20PresetMinterPauser}.
*
* TIP: For a detailed writeup see our guide
* https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How
* to implement supply mechanisms].
*
* We have followed general OpenZeppelin guidelines: functions revert instead
* of returning `false` on failure. This behavior is nonetheless conventional
* and does not conflict with the expectations of ERC20 applications.
*
* Additionally, an {Approval} event is emitted on calls to {transferFrom}.
* This allows applications to reconstruct the allowance for all accounts just
* by listening to said events. Other implementations of the EIP may not emit
* these events, as it isn't required by the specification.
*
* Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
* functions have been added to mitigate the well-known issues around setting
* allowances. See {IERC20-approve}.
*/
contract ERC20 is Context, IERC20 {
using SafeMath for uint256;
mapping (address => uint256) private _balances;
mapping (address => mapping (address => uint256)) private _allowances;
uint256 private _totalSupply;
string private _name;
string private _symbol;
uint8 private _decimals;
/**
* @dev Sets the values for {name} and {symbol}, initializes {decimals} with
* a default value of 18.
*
* To select a different value for {decimals}, use {_setupDecimals}.
*
* All three of these values are immutable: they can only be set once during
* construction.
*/
constructor (string memory name_, string memory symbol_) public {
_name = name_;
_symbol = symbol_;
_decimals = 18;
}
/**
* @dev Returns the name of the token.
*/
function name() public view virtual returns (string memory) {
return _name;
}
/**
* @dev Returns the symbol of the token, usually a shorter version of the
* name.
*/
function symbol() public view virtual returns (string memory) {
return _symbol;
}
/**
* @dev Returns the number of decimals used to get its user representation.
* For example, if `decimals` equals `2`, a balance of `505` tokens should
* be displayed to a user as `5,05` (`505 / 10 ** 2`).
*
* Tokens usually opt for a value of 18, imitating the relationship between
* Ether and Wei. This is the value {ERC20} uses, unless {_setupDecimals} is
* called.
*
* NOTE: This information is only used for _display_ purposes: it in
* no way affects any of the arithmetic of the contract, including
* {IERC20-balanceOf} and {IERC20-transfer}.
*/
function decimals() public view virtual returns (uint8) {
return _decimals;
}
/**
* @dev See {IERC20-totalSupply}.
*/
function totalSupply() public view virtual override returns (uint256) {
return _totalSupply;
}
/**
* @dev See {IERC20-balanceOf}.
*/
function balanceOf(address account) public view virtual override returns (uint256) {
return _balances[account];
}
/**
* @dev See {IERC20-transfer}.
*
* Requirements:
*
* - `recipient` cannot be the zero address.
* - the caller must have a balance of at least `amount`.
*/
function transfer(address recipient, uint256 amount) public virtual override returns (bool) {
_transfer(_msgSender(), recipient, amount);
return true;
}
/**
* @dev See {IERC20-allowance}.
*/
function allowance(address owner, address spender) public view virtual override returns (uint256) {
return _allowances[owner][spender];
}
/**
* @dev See {IERC20-approve}.
*
* Requirements:
*
* - `spender` cannot be the zero address.
*/
function approve(address spender, uint256 amount) public virtual override returns (bool) {
_approve(_msgSender(), spender, amount);
return true;
}
/**
* @dev See {IERC20-transferFrom}.
*
* Emits an {Approval} event indicating the updated allowance. This is not
* required by the EIP. See the note at the beginning of {ERC20}.
*
* Requirements:
*
* - `sender` and `recipient` cannot be the zero address.
* - `sender` must have a balance of at least `amount`.
* - the caller must have allowance for ``sender``'s tokens of at least
* `amount`.
*/
function transferFrom(address sender, address recipient, uint256 amount) public virtual override returns (bool) {
_transfer(sender, recipient, amount);
_approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, "ERC20: transfer amount exceeds allowance"));
return true;
}
/**
* @dev Atomically increases the allowance granted to `spender` by the caller.
*
* This is an alternative to {approve} that can be used as a mitigation for
* problems described in {IERC20-approve}.
*
* Emits an {Approval} event indicating the updated allowance.
*
* Requirements:
*
* - `spender` cannot be the zero address.
*/
function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
_approve(_msgSender(), spender, _allowances[_msgSender()][spender].add(addedValue));
return true;
}
/**
* @dev Atomically decreases the allowance granted to `spender` by the caller.
*
* This is an alternative to {approve} that can be used as a mitigation for
* problems described in {IERC20-approve}.
*
* Emits an {Approval} event indicating the updated allowance.
*
* Requirements:
*
* - `spender` cannot be the zero address.
* - `spender` must have allowance for the caller of at least
* `subtractedValue`.
*/
function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
_approve(_msgSender(), spender, _allowances[_msgSender()][spender].sub(subtractedValue, "ERC20: decreased allowance below zero"));
return true;
}
/**
* @dev Moves tokens `amount` from `sender` to `recipient`.
*
* This is internal function is equivalent to {transfer}, and can be used to
* e.g. implement automatic token fees, slashing mechanisms, etc.
*
* Emits a {Transfer} event.
*
* Requirements:
*
* - `sender` cannot be the zero address.
* - `recipient` cannot be the zero address.
* - `sender` must have a balance of at least `amount`.
*/
function _transfer(address sender, address recipient, uint256 amount) internal virtual {
require(sender != address(0), "ERC20: transfer from the zero address");
require(recipient != address(0), "ERC20: transfer to the zero address");
_beforeTokenTransfer(sender, recipient, amount);
_balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance");
_balances[recipient] = _balances[recipient].add(amount);
emit Transfer(sender, recipient, amount);
}
/** @dev Creates `amount` tokens and assigns them to `account`, increasing
* the total supply.
*
* Emits a {Transfer} event with `from` set to the zero address.
*
* Requirements:
*
* - `to` cannot be the zero address.
*/
function _mint(address account, uint256 amount) internal virtual {
require(account != address(0), "ERC20: mint to the zero address");
_beforeTokenTransfer(address(0), account, amount);
_totalSupply = _totalSupply.add(amount);
_balances[account] = _balances[account].add(amount);
emit Transfer(address(0), account, amount);
}
/**
* @dev Destroys `amount` tokens from `account`, reducing the
* total supply.
*
* Emits a {Transfer} event with `to` set to the zero address.
*
* Requirements:
*
* - `account` cannot be the zero address.
* - `account` must have at least `amount` tokens.
*/
function _burn(address account, uint256 amount) internal virtual {
require(account != address(0), "ERC20: burn from the zero address");
_beforeTokenTransfer(account, address(0), amount);
_balances[account] = _balances[account].sub(amount, "ERC20: burn amount exceeds balance");
_totalSupply = _totalSupply.sub(amount);
emit Transfer(account, address(0), amount);
}
/**
* @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.
*
* This internal function is equivalent to `approve`, and can be used to
* e.g. set automatic allowances for certain subsystems, etc.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `owner` cannot be the zero address.
* - `spender` cannot be the zero address.
*/
function _approve(address owner, address spender, uint256 amount) internal virtual {
require(owner != address(0), "ERC20: approve from the zero address");
require(spender != address(0), "ERC20: approve to the zero address");
_allowances[owner][spender] = amount;
emit Approval(owner, spender, amount);
}
/**
* @dev Sets {decimals} to a value other than the default one of 18.
*
* WARNING: This function should only be called from the constructor. Most
* applications that interact with token contracts will not expect
* {decimals} to ever change, and may work incorrectly if it does.
*/
function _setupDecimals(uint8 decimals_) internal virtual {
_decimals = decimals_;
}
/**
* @dev Hook that is called before any transfer of tokens. This includes
* minting and burning.
*
* Calling conditions:
*
* - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
* will be to transferred to `to`.
* - when `from` is zero, `amount` tokens will be minted for `to`.
* - when `to` is zero, `amount` of ``from``'s tokens will be burned.
* - `from` and `to` are never both zero.
*
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
*/
function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual { }
}
// File: @openzeppelin/contracts/drafts/IERC20Permit.sol
pragma solidity >=0.6.0 <0.8.0;
/**
* @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
* https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
*
* Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
* presenting a message signed by the account. By not relying on `{IERC20-approve}`, the token holder account doesn't
* need to send a transaction, and thus is not required to hold Ether at all.
*/
interface IERC20Permit {
/**
* @dev Sets `value` as the allowance of `spender` over `owner`'s tokens,
* given `owner`'s signed approval.
*
* IMPORTANT: The same issues {IERC20-approve} has related to transaction
* ordering also apply here.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `spender` cannot be the zero address.
* - `deadline` must be a timestamp in the future.
* - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
* over the EIP712-formatted function arguments.
* - the signature must use ``owner``'s current nonce (see {nonces}).
*
* For more information on the signature format, see the
* https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
* section].
*/
function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) external;
/**
* @dev Returns the current nonce for `owner`. This value must be
* included whenever a signature is generated for {permit}.
*
* Every successful call to {permit} increases ``owner``'s nonce by one. This
* prevents a signature from being used multiple times.
*/
function nonces(address owner) external view returns (uint256);
/**
* @dev Returns the domain separator used in the encoding of the signature for `permit`, as defined by {EIP712}.
*/
// solhint-disable-next-line func-name-mixedcase
function DOMAIN_SEPARATOR() external view returns (bytes32);
}
// File: @openzeppelin/contracts/cryptography/ECDSA.sol
pragma solidity >=0.6.0 <0.8.0;
/**
* @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
*
* These functions can be used to verify that a message was signed by the holder
* of the private keys of a given address.
*/
library ECDSA {
/**
* @dev Returns the address that signed a hashed message (`hash`) with
* `signature`. This address can then be used for verification purposes.
*
* The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
* this function rejects them by requiring the `s` value to be in the lower
* half order, and the `v` value to be either 27 or 28.
*
* IMPORTANT: `hash` _must_ be the result of a hash operation for the
* verification to be secure: it is possible to craft signatures that
* recover to arbitrary addresses for non-hashed data. A safe way to ensure
* this is by receiving a hash of the original message (which may otherwise
* be too long), and then calling {toEthSignedMessageHash} on it.
*/
function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
// Check the signature length
if (signature.length != 65) {
revert("ECDSA: invalid signature length");
}
// Divide the signature in r, s and v variables
bytes32 r;
bytes32 s;
uint8 v;
// ecrecover takes the signature parameters, and the only way to get them
// currently is to use assembly.
// solhint-disable-next-line no-inline-assembly
assembly {
r := mload(add(signature, 0x20))
s := mload(add(signature, 0x40))
v := byte(0, mload(add(signature, 0x60)))
}
return recover(hash, v, r, s);
}
/**
* @dev Overload of {ECDSA-recover-bytes32-bytes-} that receives the `v`,
* `r` and `s` signature fields separately.
*/
function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) {
// EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
// unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
// the valid range for s in (281): 0 < s < secp256k1n ÷ 2 + 1, and for v in (282): v ∈ {27, 28}. Most
// signatures from current libraries generate a unique signature with an s-value in the lower half order.
//
// If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
// with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
// vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
// these malleable signatures as well.
require(uint256(s) <= 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0, "ECDSA: invalid signature 's' value");
require(v == 27 || v == 28, "ECDSA: invalid signature 'v' value");
// If the signature is valid (and not malleable), return the signer address
address signer = ecrecover(hash, v, r, s);
require(signer != address(0), "ECDSA: invalid signature");
return signer;
}
/**
* @dev Returns an Ethereum Signed Message, created from a `hash`. This
* replicates the behavior of the
* https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sign[`eth_sign`]
* JSON-RPC method.
*
* See {recover}.
*/
function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) {
// 32 is the length in bytes of hash,
// enforced by the type signature above
return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash));
}
}
// File: @openzeppelin/contracts/utils/Counters.sol
pragma solidity >=0.6.0 <0.8.0;
/**
* @title Counters
* @author Matt Condon (@shrugs)
* @dev Provides counters that can only be incremented or decremented by one. This can be used e.g. to track the number
* of elements in a mapping, issuing ERC721 ids, or counting request ids.
*
* Include with `using Counters for Counters.Counter;`
* Since it is not possible to overflow a 256 bit integer with increments of one, `increment` can skip the {SafeMath}
* overflow check, thereby saving gas. This does assume however correct usage, in that the underlying `_value` is never
* directly accessed.
*/
library Counters {
using SafeMath for uint256;
struct Counter {
// This variable should never be directly accessed by users of the library: interactions must be restricted to
// the library's function. As of Solidity v0.5.2, this cannot be enforced, though there is a proposal to add
// this feature: see https://github.com/ethereum/solidity/issues/4637
uint256 _value; // default: 0
}
function current(Counter storage counter) internal view returns (uint256) {
return counter._value;
}
function increment(Counter storage counter) internal {
// The {SafeMath} overflow check can be skipped here, see the comment at the top
counter._value += 1;
}
function decrement(Counter storage counter) internal {
counter._value = counter._value.sub(1);
}
}
// File: @openzeppelin/contracts/drafts/EIP712.sol
pragma solidity >=0.6.0 <0.8.0;
/**
* @dev https://eips.ethereum.org/EIPS/eip-712[EIP 712] is a standard for hashing and signing of typed structured data.
*
* The encoding specified in the EIP is very generic, and such a generic implementation in Solidity is not feasible,
* thus this contract does not implement the encoding itself. Protocols need to implement the type-specific encoding
* they need in their contracts using a combination of `abi.encode` and `keccak256`.
*
* This contract implements the EIP 712 domain separator ({_domainSeparatorV4}) that is used as part of the encoding
* scheme, and the final step of the encoding to obtain the message digest that is then signed via ECDSA
* ({_hashTypedDataV4}).
*
* The implementation of the domain separator was designed to be as efficient as possible while still properly updating
* the chain id to protect against replay attacks on an eventual fork of the chain.
*
* NOTE: This contract implements the version of the encoding known as "v4", as implemented by the JSON RPC method
* https://docs.metamask.io/guide/signing-data.html[`eth_signTypedDataV4` in MetaMask].
*
* _Available since v3.4._
*/
abstract contract EIP712 {
/* solhint-disable var-name-mixedcase */
// Cache the domain separator as an immutable value, but also store the chain id that it corresponds to, in order to
// invalidate the cached domain separator if the chain id changes.
bytes32 private immutable _CACHED_DOMAIN_SEPARATOR;
uint256 private immutable _CACHED_CHAIN_ID;
bytes32 private immutable _HASHED_NAME;
bytes32 private immutable _HASHED_VERSION;
bytes32 private immutable _TYPE_HASH;
/* solhint-enable var-name-mixedcase */
/**
* @dev Initializes the domain separator and parameter caches.
*
* The meaning of `name` and `version` is specified in
* https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator[EIP 712]:
*
* - `name`: the user readable name of the signing domain, i.e. the name of the DApp or the protocol.
* - `version`: the current major version of the signing domain.
*
* NOTE: These parameters cannot be changed except through a xref:learn::upgrading-smart-contracts.adoc[smart
* contract upgrade].
*/
constructor(string memory name, string memory version) internal {
bytes32 hashedName = keccak256(bytes(name));
bytes32 hashedVersion = keccak256(bytes(version));
bytes32 typeHash = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");
_HASHED_NAME = hashedName;
_HASHED_VERSION = hashedVersion;
_CACHED_CHAIN_ID = _getChainId();
_CACHED_DOMAIN_SEPARATOR = _buildDomainSeparator(typeHash, hashedName, hashedVersion);
_TYPE_HASH = typeHash;
}
/**
* @dev Returns the domain separator for the current chain.
*/
function _domainSeparatorV4() internal view virtual returns (bytes32) {
if (_getChainId() == _CACHED_CHAIN_ID) {
return _CACHED_DOMAIN_SEPARATOR;
} else {
return _buildDomainSeparator(_TYPE_HASH, _HASHED_NAME, _HASHED_VERSION);
}
}
function _buildDomainSeparator(bytes32 typeHash, bytes32 name, bytes32 version) private view returns (bytes32) {
return keccak256(
abi.encode(
typeHash,
name,
version,
_getChainId(),
address(this)
)
);
}
/**
* @dev Given an already https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct[hashed struct], this
* function returns the hash of the fully encoded EIP712 message for this domain.
*
* This hash can be used together with {ECDSA-recover} to obtain the signer of a message. For example:
*
* ```solidity
* bytes32 digest = _hashTypedDataV4(keccak256(abi.encode(
* keccak256("Mail(address to,string contents)"),
* mailTo,
* keccak256(bytes(mailContents))
* )));
* address signer = ECDSA.recover(digest, signature);
* ```
*/
function _hashTypedDataV4(bytes32 structHash) internal view virtual returns (bytes32) {
return keccak256(abi.encodePacked("\x19\x01", _domainSeparatorV4(), structHash));
}
function _getChainId() private view returns (uint256 chainId) {
this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
// solhint-disable-next-line no-inline-assembly
assembly {
chainId := chainid()
}
}
}
// File: @openzeppelin/contracts/drafts/ERC20Permit.sol
pragma solidity >=0.6.5 <0.8.0;
/**
* @dev Implementation of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
* https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
*
* Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
* presenting a message signed by the account. By not relying on `{IERC20-approve}`, the token holder account doesn't
* need to send a transaction, and thus is not required to hold Ether at all.
*
* _Available since v3.4._
*/
abstract contract ERC20Permit is ERC20, IERC20Permit, EIP712 {
using Counters for Counters.Counter;
mapping (address => Counters.Counter) private _nonces;
// solhint-disable-next-line var-name-mixedcase
bytes32 private immutable _PERMIT_TYPEHASH = keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
/**
* @dev Initializes the {EIP712} domain separator using the `name` parameter, and setting `version` to `"1"`.
*
* It's a good idea to use the same `name` that is defined as the ERC20 token name.
*/
constructor(string memory name) internal EIP712(name, "1") {
}
/**
* @dev See {IERC20Permit-permit}.
*/
function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public virtual override {
// solhint-disable-next-line not-rely-on-time
require(block.timestamp <= deadline, "ERC20Permit: expired deadline");
bytes32 structHash = keccak256(
abi.encode(
_PERMIT_TYPEHASH,
owner,
spender,
value,
_nonces[owner].current(),
deadline
)
);
bytes32 hash = _hashTypedDataV4(structHash);
address signer = ECDSA.recover(hash, v, r, s);
require(signer == owner, "ERC20Permit: invalid signature");
_nonces[owner].increment();
_approve(owner, spender, value);
}
/**
* @dev See {IERC20Permit-nonces}.
*/
function nonces(address owner) public view override returns (uint256) {
return _nonces[owner].current();
}
/**
* @dev See {IERC20Permit-DOMAIN_SEPARATOR}.
*/
// solhint-disable-next-line func-name-mixedcase
function DOMAIN_SEPARATOR() external view override returns (bytes32) {
return _domainSeparatorV4();
}
}
// File: contracts/0.6.12/interfaces/IStETH.sol
// SPDX-FileCopyrightText: 2021 Lido <info@lido.fi>
pragma solidity 0.6.12; // latest available for using OZ
interface IStETH is IERC20 {
function getPooledEthByShares(uint256 _sharesAmount) external view returns (uint256);
function getSharesByPooledEth(uint256 _pooledEthAmount) external view returns (uint256);
function submit(address _referral) external payable returns (uint256);
}
// File: contracts/0.6.12/WstETH.sol
// SPDX-FileCopyrightText: 2021 Lido <info@lido.fi>
/* See contracts/COMPILERS.md */
pragma solidity 0.6.12;
/**
* @title StETH token wrapper with static balances.
* @dev It's an ERC20 token that represents the account's share of the total
* supply of stETH tokens. WstETH token's balance only changes on transfers,
* unlike StETH that is also changed when oracles report staking rewards and
* penalties. It's a "power user" token for DeFi protocols which don't
* support rebasable tokens.
*
* The contract is also a trustless wrapper that accepts stETH tokens and mints
* wstETH in return. Then the user unwraps, the contract burns user's wstETH
* and sends user locked stETH in return.
*
* The contract provides the staking shortcut: user can send ETH with regular
* transfer and get wstETH in return. The contract will send ETH to Lido submit
* method, staking it and wrapping the received stETH.
*
*/
contract WstETH is ERC20Permit {
IStETH public stETH;
/**
* @param _stETH address of the StETH token to wrap
*/
constructor(IStETH _stETH)
public
ERC20Permit("Wrapped liquid staked Ether 2.0")
ERC20("Wrapped liquid staked Ether 2.0", "wstETH")
{
stETH = _stETH;
}
/**
* @notice Exchanges stETH to wstETH
* @param _stETHAmount amount of stETH to wrap in exchange for wstETH
* @dev Requirements:
* - `_stETHAmount` must be non-zero
* - msg.sender must approve at least `_stETHAmount` stETH to this
* contract.
* - msg.sender must have at least `_stETHAmount` of stETH.
* User should first approve _stETHAmount to the WstETH contract
* @return Amount of wstETH user receives after wrap
*/
function wrap(uint256 _stETHAmount) external returns (uint256) {
require(_stETHAmount > 0, "wstETH: can't wrap zero stETH");
uint256 wstETHAmount = stETH.getSharesByPooledEth(_stETHAmount);
_mint(msg.sender, wstETHAmount);
stETH.transferFrom(msg.sender, address(this), _stETHAmount);
return wstETHAmount;
}
/**
* @notice Exchanges wstETH to stETH
* @param _wstETHAmount amount of wstETH to uwrap in exchange for stETH
* @dev Requirements:
* - `_wstETHAmount` must be non-zero
* - msg.sender must have at least `_wstETHAmount` wstETH.
* @return Amount of stETH user receives after unwrap
*/
function unwrap(uint256 _wstETHAmount) external returns (uint256) {
require(_wstETHAmount > 0, "wstETH: zero amount unwrap not allowed");
uint256 stETHAmount = stETH.getPooledEthByShares(_wstETHAmount);
_burn(msg.sender, _wstETHAmount);
stETH.transfer(msg.sender, stETHAmount);
return stETHAmount;
}
/**
* @notice Shortcut to stake ETH and auto-wrap returned stETH
*/
receive() external payable {
uint256 shares = stETH.submit{value: msg.value}(address(0));
_mint(msg.sender, shares);
}
/**
* @notice Get amount of wstETH for a given amount of stETH
* @param _stETHAmount amount of stETH
* @return Amount of wstETH for a given stETH amount
*/
function getWstETHByStETH(uint256 _stETHAmount) external view returns (uint256) {
return stETH.getSharesByPooledEth(_stETHAmount);
}
/**
* @notice Get amount of stETH for a given amount of wstETH
* @param _wstETHAmount amount of wstETH
* @return Amount of stETH for a given wstETH amount
*/
function getStETHByWstETH(uint256 _wstETHAmount) external view returns (uint256) {
return stETH.getPooledEthByShares(_wstETHAmount);
}
/**
* @notice Get amount of stETH for a one wstETH
* @return Amount of stETH for 1 wstETH
*/
function stEthPerToken() external view returns (uint256) {
return stETH.getPooledEthByShares(1 ether);
}
/**
* @notice Get amount of wstETH for a one stETH
* @return Amount of wstETH for a 1 stETH
*/
function tokensPerStEth() external view returns (uint256) {
return stETH.getSharesByPooledEth(1 ether);
}
}File 2 of 4: AxelarGasServiceProxy
// Sources flattened with hardhat v2.9.9 https://hardhat.org
// File contracts/interfaces/IUpgradable.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.9;
// General interface for upgradable contracts
interface IUpgradable {
error NotOwner();
error InvalidOwner();
error InvalidCodeHash();
error InvalidImplementation();
error SetupFailed();
error NotProxy();
event Upgraded(address indexed newImplementation);
event OwnershipTransferred(address indexed newOwner);
// Get current owner
function owner() external view returns (address);
function contractId() external pure returns (bytes32);
function implementation() external view returns (address);
function upgrade(
address newImplementation,
bytes32 newImplementationCodeHash,
bytes calldata params
) external;
function setup(bytes calldata data) external;
}
// File contracts/util/Proxy.sol
contract Proxy {
error InvalidImplementation();
error SetupFailed();
error EtherNotAccepted();
error NotOwner();
error AlreadyInitialized();
// bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1)
bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
// keccak256('owner')
bytes32 internal constant _OWNER_SLOT = 0x02016836a56b71f0d02689e69e326f4f4c1b9057164ef592671cf0d37c8040c0;
constructor() {
// solhint-disable-next-line no-inline-assembly
assembly {
sstore(_OWNER_SLOT, caller())
}
}
function init(
address implementationAddress,
address newOwner,
bytes memory params
) external {
address owner;
// solhint-disable-next-line no-inline-assembly
assembly {
owner := sload(_OWNER_SLOT)
}
if (msg.sender != owner) revert NotOwner();
if (implementation() != address(0)) revert AlreadyInitialized();
if (IUpgradable(implementationAddress).contractId() != contractId()) revert InvalidImplementation();
// solhint-disable-next-line no-inline-assembly
assembly {
sstore(_IMPLEMENTATION_SLOT, implementationAddress)
sstore(_OWNER_SLOT, newOwner)
}
// solhint-disable-next-line avoid-low-level-calls
(bool success, ) = implementationAddress.delegatecall(
// keccak('setup(bytes)') selector
abi.encodeWithSelector(0x9ded06df, params)
);
if (!success) revert SetupFailed();
}
// solhint-disable-next-line no-empty-blocks
function contractId() internal pure virtual returns (bytes32) {}
function implementation() public view returns (address implementation_) {
// solhint-disable-next-line no-inline-assembly
assembly {
implementation_ := sload(_IMPLEMENTATION_SLOT)
}
}
// solhint-disable-next-line no-empty-blocks
function setup(bytes calldata data) public {}
// solhint-disable-next-line no-complex-fallback
fallback() external payable {
address implementaion_ = implementation();
// solhint-disable-next-line no-inline-assembly
assembly {
calldatacopy(0, 0, calldatasize())
let result := delegatecall(gas(), implementaion_, 0, calldatasize(), 0, 0)
returndatacopy(0, 0, returndatasize())
switch result
case 0 {
revert(0, returndatasize())
}
default {
return(0, returndatasize())
}
}
}
receive() external payable virtual {
revert EtherNotAccepted();
}
}
// File contracts/gas-service/AxelarGasServiceProxy.sol
contract AxelarGasServiceProxy is Proxy {
function contractId() internal pure override returns (bytes32) {
return keccak256('axelar-gas-service');
}
}File 3 of 4: AxelarGateway
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import { IGovernable } from './IGovernable.sol';
import { IImplementation } from './IImplementation.sol';
interface IAxelarGateway is IImplementation, IGovernable {
/**********\\
|* Errors *|
\\**********/
error NotSelf();
error InvalidCodeHash();
error SetupFailed();
error InvalidAuthModule();
error InvalidTokenDeployer();
error InvalidAmount();
error InvalidChainId();
error InvalidCommands();
error TokenDoesNotExist(string symbol);
error TokenAlreadyExists(string symbol);
error TokenDeployFailed(string symbol);
error TokenContractDoesNotExist(address token);
error BurnFailed(string symbol);
error MintFailed(string symbol);
error InvalidSetMintLimitsParams();
error ExceedMintLimit(string symbol);
/**********\\
|* Events *|
\\**********/
event TokenSent(
address indexed sender,
string destinationChain,
string destinationAddress,
string symbol,
uint256 amount
);
event ContractCall(
address indexed sender,
string destinationChain,
string destinationContractAddress,
bytes32 indexed payloadHash,
bytes payload
);
event ContractCallWithToken(
address indexed sender,
string destinationChain,
string destinationContractAddress,
bytes32 indexed payloadHash,
bytes payload,
string symbol,
uint256 amount
);
event Executed(bytes32 indexed commandId);
event TokenDeployed(string symbol, address tokenAddresses);
event ContractCallApproved(
bytes32 indexed commandId,
string sourceChain,
string sourceAddress,
address indexed contractAddress,
bytes32 indexed payloadHash,
bytes32 sourceTxHash,
uint256 sourceEventIndex
);
event ContractCallApprovedWithMint(
bytes32 indexed commandId,
string sourceChain,
string sourceAddress,
address indexed contractAddress,
bytes32 indexed payloadHash,
string symbol,
uint256 amount,
bytes32 sourceTxHash,
uint256 sourceEventIndex
);
event ContractCallExecuted(bytes32 indexed commandId);
event TokenMintLimitUpdated(string symbol, uint256 limit);
event OperatorshipTransferred(bytes newOperatorsData);
event Upgraded(address indexed implementation);
/********************\\
|* Public Functions *|
\\********************/
function sendToken(
string calldata destinationChain,
string calldata destinationAddress,
string calldata symbol,
uint256 amount
) external;
function callContract(
string calldata destinationChain,
string calldata contractAddress,
bytes calldata payload
) external;
function callContractWithToken(
string calldata destinationChain,
string calldata contractAddress,
bytes calldata payload,
string calldata symbol,
uint256 amount
) external;
function isContractCallApproved(
bytes32 commandId,
string calldata sourceChain,
string calldata sourceAddress,
address contractAddress,
bytes32 payloadHash
) external view returns (bool);
function isContractCallAndMintApproved(
bytes32 commandId,
string calldata sourceChain,
string calldata sourceAddress,
address contractAddress,
bytes32 payloadHash,
string calldata symbol,
uint256 amount
) external view returns (bool);
function validateContractCall(
bytes32 commandId,
string calldata sourceChain,
string calldata sourceAddress,
bytes32 payloadHash
) external returns (bool);
function validateContractCallAndMint(
bytes32 commandId,
string calldata sourceChain,
string calldata sourceAddress,
bytes32 payloadHash,
string calldata symbol,
uint256 amount
) external returns (bool);
/***********\\
|* Getters *|
\\***********/
function authModule() external view returns (address);
function tokenDeployer() external view returns (address);
function tokenMintLimit(string memory symbol) external view returns (uint256);
function tokenMintAmount(string memory symbol) external view returns (uint256);
function allTokensFrozen() external view returns (bool);
function implementation() external view returns (address);
function tokenAddresses(string memory symbol) external view returns (address);
function tokenFrozen(string memory symbol) external view returns (bool);
function isCommandExecuted(bytes32 commandId) external view returns (bool);
/************************\\
|* Governance Functions *|
\\************************/
function setTokenMintLimits(string[] calldata symbols, uint256[] calldata limits) external;
function upgrade(
address newImplementation,
bytes32 newImplementationCodeHash,
bytes calldata setupParams
) external;
/**********************\\
|* External Functions *|
\\**********************/
function execute(bytes calldata input) external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
// General interface for upgradable contracts
interface IContractIdentifier {
/**
* @notice Returns the contract ID. It can be used as a check during upgrades.
* @dev Meant to be overridden in derived contracts.
* @return bytes32 The contract ID
*/
function contractId() external pure returns (bytes32);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
error InvalidAccount();
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `recipient`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address recipient, uint256 amount) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `sender` to `recipient` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(
address sender,
address recipient,
uint256 amount
) external returns (bool);
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @title IGovernable Interface
* @notice This is an interface used by the AxelarGateway contract to manage governance and mint limiter roles.
*/
interface IGovernable {
error NotGovernance();
error NotMintLimiter();
error InvalidGovernance();
error InvalidMintLimiter();
event GovernanceTransferred(address indexed previousGovernance, address indexed newGovernance);
event MintLimiterTransferred(address indexed previousGovernance, address indexed newGovernance);
/**
* @notice Returns the governance address.
* @return address of the governance
*/
function governance() external view returns (address);
/**
* @notice Returns the mint limiter address.
* @return address of the mint limiter
*/
function mintLimiter() external view returns (address);
/**
* @notice Transfer the governance role to another address.
* @param newGovernance The new governance address
*/
function transferGovernance(address newGovernance) external;
/**
* @notice Transfer the mint limiter role to another address.
* @param newGovernance The new mint limiter address
*/
function transferMintLimiter(address newGovernance) external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import { IContractIdentifier } from './IContractIdentifier.sol';
interface IImplementation is IContractIdentifier {
error NotProxy();
function setup(bytes calldata data) external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @title IOwnable Interface
* @notice IOwnable is an interface that abstracts the implementation of a
* contract with ownership control features. It's commonly used in upgradable
* contracts and includes the functionality to get current owner, transfer
* ownership, and propose and accept ownership.
*/
interface IOwnable {
error NotOwner();
error InvalidOwner();
error InvalidOwnerAddress();
event OwnershipTransferStarted(address indexed newOwner);
event OwnershipTransferred(address indexed newOwner);
/**
* @notice Returns the current owner of the contract.
* @return address The address of the current owner
*/
function owner() external view returns (address);
/**
* @notice Returns the address of the pending owner of the contract.
* @return address The address of the pending owner
*/
function pendingOwner() external view returns (address);
/**
* @notice Transfers ownership of the contract to a new address
* @param newOwner The address to transfer ownership to
*/
function transferOwnership(address newOwner) external;
/**
* @notice Proposes to transfer the contract's ownership to a new address.
* The new owner needs to accept the ownership explicitly.
* @param newOwner The address to transfer ownership to
*/
function proposeOwnership(address newOwner) external;
/**
* @notice Transfers ownership to the pending owner.
* @dev Can only be called by the pending owner
*/
function acceptOwnership() external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
library ContractAddress {
function isContract(address contractAddress) internal view returns (bool) {
bytes32 existingCodeHash = contractAddress.codehash;
// https://eips.ethereum.org/EIPS/eip-1052
// keccak256('') == 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470
return
existingCodeHash != bytes32(0) &&
existingCodeHash != 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import { IERC20 } from '../interfaces/IERC20.sol';
error TokenTransferFailed();
/*
* @title SafeTokenCall
* @dev This library is used for performing safe token transfers.
*/
library SafeTokenCall {
/*
* @notice Make a safe call to a token contract.
* @param token The token contract to interact with.
* @param callData The function call data.
* @throws TokenTransferFailed error if transfer of token is not successful.
*/
function safeCall(IERC20 token, bytes memory callData) internal {
(bool success, bytes memory returnData) = address(token).call(callData);
bool transferred = success && (returnData.length == uint256(0) || abi.decode(returnData, (bool)));
if (!transferred || address(token).code.length == 0) revert TokenTransferFailed();
}
}
/*
* @title SafeTokenTransfer
* @dev This library safely transfers tokens from the contract to a recipient.
*/
library SafeTokenTransfer {
/*
* @notice Transfer tokens to a recipient.
* @param token The token contract.
* @param receiver The recipient of the tokens.
* @param amount The amount of tokens to transfer.
*/
function safeTransfer(
IERC20 token,
address receiver,
uint256 amount
) internal {
SafeTokenCall.safeCall(token, abi.encodeWithSelector(IERC20.transfer.selector, receiver, amount));
}
}
/*
* @title SafeTokenTransferFrom
* @dev This library helps to safely transfer tokens on behalf of a token holder.
*/
library SafeTokenTransferFrom {
/*
* @notice Transfer tokens on behalf of a token holder.
* @param token The token contract.
* @param from The address of the token holder.
* @param to The address the tokens are to be sent to.
* @param amount The amount of tokens to be transferred.
*/
function safeTransferFrom(
IERC20 token,
address from,
address to,
uint256 amount
) internal {
SafeTokenCall.safeCall(token, abi.encodeWithSelector(IERC20.transferFrom.selector, from, to, amount));
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import { IImplementation } from '../interfaces/IImplementation.sol';
/**
* @title Implementation
* @notice This contract serves as a base for other contracts and enforces a proxy-first access restriction.
* @dev Derived contracts must implement the setup function.
*/
abstract contract Implementation is IImplementation {
address private immutable implementationAddress;
/**
* @dev Contract constructor that sets the implementation address to the address of this contract.
*/
constructor() {
implementationAddress = address(this);
}
/**
* @dev Modifier to require the caller to be the proxy contract.
* Reverts if the caller is the current contract (i.e., the implementation contract itself).
*/
modifier onlyProxy() {
if (implementationAddress == address(this)) revert NotProxy();
_;
}
/**
* @notice Initializes contract parameters.
* This function is intended to be overridden by derived contracts.
* The overriding function must have the onlyProxy modifier.
* @param params The parameters to be used for initialization
*/
function setup(bytes calldata params) external virtual;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import { IERC20 } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IERC20.sol';
import { IImplementation } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IImplementation.sol';
import { IContractIdentifier } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IContractIdentifier.sol';
import { IAxelarGateway } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IAxelarGateway.sol';
import { SafeTokenCall, SafeTokenTransfer, SafeTokenTransferFrom } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/libs/SafeTransfer.sol';
import { ContractAddress } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/libs/ContractAddress.sol';
import { Implementation } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/upgradable/Implementation.sol';
import { IAxelarAuth } from './interfaces/IAxelarAuth.sol';
import { IBurnableMintableCappedERC20 } from './interfaces/IBurnableMintableCappedERC20.sol';
import { ITokenDeployer } from './interfaces/ITokenDeployer.sol';
import { ECDSA } from './ECDSA.sol';
import { DepositHandler } from './DepositHandler.sol';
import { EternalStorage } from './EternalStorage.sol';
/**
* @title AxelarGateway Contract
* @notice This contract serves as the gateway for cross-chain contract calls,
* and token transfers within the Axelar network.
* It includes functions for sending tokens, calling contracts, and validating contract calls.
* The contract is managed via the decentralized governance mechanism on the Axelar network.
* @dev EternalStorage is used to simplify storage for upgradability, and InterchainGovernance module is used for governance.
*/
contract AxelarGateway is IAxelarGateway, Implementation, EternalStorage {
using SafeTokenCall for IERC20;
using SafeTokenTransfer for IERC20;
using SafeTokenTransferFrom for IERC20;
using ContractAddress for address;
error InvalidImplementation();
enum TokenType {
InternalBurnable,
InternalBurnableFrom,
External
}
/**
* @dev Deprecated slots. Should not be reused.
*/
// bytes32 internal constant KEY_ALL_TOKENS_FROZEN = keccak256('all-tokens-frozen');
// bytes32 internal constant PREFIX_TOKEN_FROZEN = keccak256('token-frozen');
/**
* @dev Storage slot with the address of the current implementation. `keccak256('eip1967.proxy.implementation') - 1`.
*/
bytes32 internal constant KEY_IMPLEMENTATION = bytes32(0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc);
/**
* @dev Storage slot with the address of the current governance. keccak256('governance')) - 1
*/
bytes32 internal constant KEY_GOVERNANCE = bytes32(0xabea6fd3db56a6e6d0242111b43ebb13d1c42709651c032c7894962023a1f909);
/**
* @dev Storage slot with the address of the current mint limiter. keccak256('mint-limiter')) - 1
*/
bytes32 internal constant KEY_MINT_LIMITER = bytes32(0x627f0c11732837b3240a2de89c0b6343512886dd50978b99c76a68c6416a4d92);
bytes32 internal constant PREFIX_COMMAND_EXECUTED = keccak256('command-executed');
bytes32 internal constant PREFIX_TOKEN_ADDRESS = keccak256('token-address');
bytes32 internal constant PREFIX_TOKEN_TYPE = keccak256('token-type');
bytes32 internal constant PREFIX_CONTRACT_CALL_APPROVED = keccak256('contract-call-approved');
bytes32 internal constant PREFIX_CONTRACT_CALL_APPROVED_WITH_MINT = keccak256('contract-call-approved-with-mint');
bytes32 internal constant PREFIX_TOKEN_MINT_LIMIT = keccak256('token-mint-limit');
bytes32 internal constant PREFIX_TOKEN_MINT_AMOUNT = keccak256('token-mint-amount');
bytes32 internal constant SELECTOR_BURN_TOKEN = keccak256('burnToken');
bytes32 internal constant SELECTOR_DEPLOY_TOKEN = keccak256('deployToken');
bytes32 internal constant SELECTOR_MINT_TOKEN = keccak256('mintToken');
bytes32 internal constant SELECTOR_APPROVE_CONTRACT_CALL = keccak256('approveContractCall');
bytes32 internal constant SELECTOR_APPROVE_CONTRACT_CALL_WITH_MINT = keccak256('approveContractCallWithMint');
bytes32 internal constant SELECTOR_TRANSFER_OPERATORSHIP = keccak256('transferOperatorship');
address public immutable authModule;
address public immutable tokenDeployer;
/**
* @notice Constructs the AxelarGateway contract.
* @param authModule_ The address of the authentication module
* @param tokenDeployer_ The address of the token deployer
*/
constructor(address authModule_, address tokenDeployer_) {
if (authModule_.code.length == 0) revert InvalidAuthModule();
if (tokenDeployer_.code.length == 0) revert InvalidTokenDeployer();
authModule = authModule_;
tokenDeployer = tokenDeployer_;
}
/**
* @notice Ensures that the caller of the function is the gateway contract itself.
*/
modifier onlySelf() {
if (msg.sender != address(this)) revert NotSelf();
_;
}
/**
* @notice Ensures that the caller of the function is the governance address.
*/
modifier onlyGovernance() {
if (msg.sender != getAddress(KEY_GOVERNANCE)) revert NotGovernance();
_;
}
/**
* @notice Ensures that the caller of the function is either the mint limiter or governance.
*/
modifier onlyMintLimiter() {
if (msg.sender != getAddress(KEY_MINT_LIMITER) && msg.sender != getAddress(KEY_GOVERNANCE)) revert NotMintLimiter();
_;
}
/******************\\
|* Public Methods *|
\\******************/
/**
* @notice Send the specified token to the destination chain and address.
* @param destinationChain The chain to send tokens to. A registered chain name on Axelar must be used here
* @param destinationAddress The address on the destination chain to send tokens to
* @param symbol The symbol of the token to send
* @param amount The amount of tokens to send
*/
function sendToken(
string calldata destinationChain,
string calldata destinationAddress,
string calldata symbol,
uint256 amount
) external {
_burnTokenFrom(msg.sender, symbol, amount);
emit TokenSent(msg.sender, destinationChain, destinationAddress, symbol, amount);
}
/**
* @notice Calls a contract on the specified destination chain with a given payload.
* This function is the entry point for general message passing between chains.
* @param destinationChain The chain where the destination contract exists. A registered chain name on Axelar must be used here
* @param destinationContractAddress The address of the contract to call on the destination chain
* @param payload The payload to be sent to the destination contract, usually representing an encoded function call with arguments
*/
function callContract(
string calldata destinationChain,
string calldata destinationContractAddress,
bytes calldata payload
) external {
emit ContractCall(msg.sender, destinationChain, destinationContractAddress, keccak256(payload), payload);
}
/**
* @notice Calls a contract on the specified destination chain with a given payload and token amount.
* This function is the entry point for general message passing with token transfer between chains.
* @param destinationChain The chain where the destination contract exists. A registered chain name on Axelar must be used here
* @param destinationContractAddress The address of the contract to call with tokens on the destination chain
* @param payload The payload to be sent to the destination contract, usually representing an encoded function call with arguments
* @param symbol The symbol of the token to be sent with the call
* @param amount The amount of tokens to be sent with the call
*/
function callContractWithToken(
string calldata destinationChain,
string calldata destinationContractAddress,
bytes calldata payload,
string calldata symbol,
uint256 amount
) external {
_burnTokenFrom(msg.sender, symbol, amount);
emit ContractCallWithToken(msg.sender, destinationChain, destinationContractAddress, keccak256(payload), payload, symbol, amount);
}
/**
* @notice Checks whether a contract call has been approved by the gateway.
* @param commandId The gateway command ID
* @param sourceChain The source chain of the contract call
* @param sourceAddress The source address of the contract call
* @param contractAddress The contract address that will be called
* @param payloadHash The hash of the payload for that will be sent with the call
* @return bool A boolean value indicating whether the contract call has been approved by the gateway.
*/
function isContractCallApproved(
bytes32 commandId,
string calldata sourceChain,
string calldata sourceAddress,
address contractAddress,
bytes32 payloadHash
) external view override returns (bool) {
return getBool(_getIsContractCallApprovedKey(commandId, sourceChain, sourceAddress, contractAddress, payloadHash));
}
/**
* @notice Checks whether a contract call with token has been approved by the gateway.
* @param commandId The gateway command ID
* @param sourceChain The source chain of the contract call
* @param sourceAddress The source address of the contract call
* @param contractAddress The contract address that will be called, and where tokens will be sent
* @param payloadHash The hash of the payload for that will be sent with the call
* @param symbol The symbol of the token to be sent with the call
* @param amount The amount of tokens to be sent with the call
* @return bool A boolean value indicating whether the contract call with token has been approved by the gateway.
*/
function isContractCallAndMintApproved(
bytes32 commandId,
string calldata sourceChain,
string calldata sourceAddress,
address contractAddress,
bytes32 payloadHash,
string calldata symbol,
uint256 amount
) external view override returns (bool) {
return
getBool(
_getIsContractCallApprovedWithMintKey(commandId, sourceChain, sourceAddress, contractAddress, payloadHash, symbol, amount)
);
}
/**
* @notice Called on the destination chain gateway by the recipient of the cross-chain contract call to validate it and only allow execution
* if this function returns true.
* @dev Once validated, the gateway marks the message as executed so the contract call is not executed twice.
* @param commandId The gateway command ID
* @param sourceChain The source chain of the contract call
* @param sourceAddress The source address of the contract call
* @param payloadHash The hash of the payload for that will be sent with the call
* @return valid True if the contract call is approved, false otherwise
*/
function validateContractCall(
bytes32 commandId,
string calldata sourceChain,
string calldata sourceAddress,
bytes32 payloadHash
) external override returns (bool valid) {
bytes32 key = _getIsContractCallApprovedKey(commandId, sourceChain, sourceAddress, msg.sender, payloadHash);
valid = getBool(key);
if (valid) {
_setBool(key, false);
emit ContractCallExecuted(commandId);
}
}
/**
* @notice Called on the destination chain gateway to validate the approval of a contract call with token transfer and only
* allow execution if this function returns true.
* @dev Once validated, the gateway marks the message as executed so the contract call with token is not executed twice.
* @param commandId The gateway command ID
* @param sourceChain The source chain of the contract call
* @param sourceAddress The source address of the contract call
* @param payloadHash The hash of the payload for that will be sent with the call
* @param symbol The symbol of the token to be sent with the call
* @param amount The amount of tokens to be sent with the call
* @return valid True if the contract call with token is approved, false otherwise
*/
function validateContractCallAndMint(
bytes32 commandId,
string calldata sourceChain,
string calldata sourceAddress,
bytes32 payloadHash,
string calldata symbol,
uint256 amount
) external override returns (bool valid) {
bytes32 key = _getIsContractCallApprovedWithMintKey(commandId, sourceChain, sourceAddress, msg.sender, payloadHash, symbol, amount);
valid = getBool(key);
if (valid) {
// Prevent re-entrancy
_setBool(key, false);
emit ContractCallExecuted(commandId);
_mintToken(symbol, msg.sender, amount);
}
}
/***********\\
|* Getters *|
\\***********/
/**
* @notice Gets the address of governance, should be the address of InterchainGovernance.
* @return address The address of governance.
*/
function governance() public view override returns (address) {
return getAddress(KEY_GOVERNANCE);
}
/**
* @notice Gets the address of the mint limiter, should be the address of Multisig.
* @return address The address of the mint limiter.
*/
function mintLimiter() public view override returns (address) {
return getAddress(KEY_MINT_LIMITER);
}
/**
* @notice Gets the transfer limit for a specific token symbol within the configured epoch.
* @param symbol The symbol of the token
* @return uint The transfer limit for the given token.
*/
function tokenMintLimit(string memory symbol) public view override returns (uint256) {
return getUint(_getTokenMintLimitKey(symbol));
}
/**
* @notice Gets the transfer amount for a specific token symbol within the configured epoch.
* @param symbol The symbol of the token
* @return uint The transfer amount for the given token.
*/
function tokenMintAmount(string memory symbol) public view override returns (uint256) {
return getUint(_getTokenMintAmountKey(symbol, block.timestamp / 6 hours));
}
/**
* @dev This function is kept around to keep things working for internal
* tokens that were deployed before the token freeze functionality was removed
*/
function allTokensFrozen() external pure override returns (bool) {
return false;
}
/**
* @notice Gets the address of the gateway implementation contract.
* @return address The address of the gateway implementation.
*/
function implementation() public view override returns (address) {
return getAddress(KEY_IMPLEMENTATION);
}
/**
* @notice Gets the address of a specific token using its symbol.
* @param symbol The symbol of the token
* @return address The address of the token associated with the given symbol.
*/
function tokenAddresses(string memory symbol) public view override returns (address) {
return getAddress(_getTokenAddressKey(symbol));
}
/**
* @dev Deprecated. This function is kept around to keep things working for internal tokens that were deployed before the token freeze functionality was removed
*/
function tokenFrozen(string memory) external pure override returns (bool) {
return false;
}
/**
* @notice Checks whether a command with a given command ID has been executed.
* @param commandId The command ID to check
* @return bool True if the command has been executed, false otherwise
*/
function isCommandExecuted(bytes32 commandId) public view override returns (bool) {
return getBool(_getIsCommandExecutedKey(commandId));
}
/**
* @notice Gets the contract ID of the Axelar Gateway.
* @return bytes32 The keccak256 hash of the string 'axelar-gateway'
*/
function contractId() public pure returns (bytes32) {
return keccak256('axelar-gateway');
}
/************************\\
|* Governance Functions *|
\\************************/
/**
* @notice Transfers the governance role to a new address.
* @param newGovernance The address to transfer the governance role to.
* @dev Only the current governance entity can call this function.
*/
function transferGovernance(address newGovernance) external override onlyGovernance {
if (newGovernance == address(0)) revert InvalidGovernance();
_transferGovernance(newGovernance);
}
/**
* @notice Transfers the mint limiter role to a new address.
* @param newMintLimiter The address to transfer the mint limiter role to.
* @dev Only the current mint limiter or the governance address can call this function.
*/
function transferMintLimiter(address newMintLimiter) external override onlyMintLimiter {
if (newMintLimiter == address(0)) revert InvalidMintLimiter();
_transferMintLimiter(newMintLimiter);
}
/**
* @notice Sets the transfer limits for an array of tokens.
* @param symbols The array of token symbols to set the transfer limits for
* @param limits The array of transfer limits corresponding to the symbols
* @dev Only the mint limiter or the governance address can call this function.
*/
function setTokenMintLimits(string[] calldata symbols, uint256[] calldata limits) external override onlyMintLimiter {
uint256 length = symbols.length;
if (length != limits.length) revert InvalidSetMintLimitsParams();
for (uint256 i; i < length; ++i) {
string memory symbol = symbols[i];
uint256 limit = limits[i];
if (tokenAddresses(symbol) == address(0)) revert TokenDoesNotExist(symbol);
_setTokenMintLimit(symbol, limit);
}
}
/**
* @notice Upgrades the contract to a new implementation.
* @param newImplementation The address of the new implementation
* @param newImplementationCodeHash The code hash of the new implementation
* @param setupParams Optional setup params for the new implementation
* @dev Only the governance address can call this function.
*/
function upgrade(
address newImplementation,
bytes32 newImplementationCodeHash,
bytes calldata setupParams
) external override onlyGovernance {
if (newImplementationCodeHash != newImplementation.codehash) revert InvalidCodeHash();
if (contractId() != IContractIdentifier(newImplementation).contractId()) revert InvalidImplementation();
emit Upgraded(newImplementation);
_setImplementation(newImplementation);
if (setupParams.length != 0) {
// slither-disable-next-line controlled-delegatecall
(bool success, ) = newImplementation.delegatecall(abi.encodeWithSelector(IImplementation.setup.selector, setupParams));
if (!success) revert SetupFailed();
}
}
/**********************\\
|* External Functions *|
\\**********************/
/**
* @notice Sets up the governance and mint limiter roles, and transfers operatorship if necessary.
* This function is called by the proxy during initial deployment, and optionally called during gateway upgrades.
* @param params The encoded parameters containing the governance and mint limiter addresses, as well as the new operator data.
* @dev Not publicly accessible as it's overshadowed in the proxy.
*/
function setup(bytes calldata params) external override(IImplementation, Implementation) onlyProxy {
(address governance_, address mintLimiter_, bytes memory newOperatorsData) = abi.decode(params, (address, address, bytes));
if (governance_ != address(0)) _transferGovernance(governance_);
if (mintLimiter_ != address(0)) _transferMintLimiter(mintLimiter_);
if (newOperatorsData.length != 0) {
emit OperatorshipTransferred(newOperatorsData);
IAxelarAuth(authModule).transferOperatorship(newOperatorsData);
}
}
/**
* @notice Executes a batch of commands signed by the Axelar network. There are a finite set of command types that can be executed.
* @param input The encoded input containing the data for the batch of commands, as well as the proof that verifies the integrity of the data.
* @dev Each command has a corresponding commandID that is guaranteed to be unique from the Axelar network.
* @dev This function allows retrying a commandID if the command initially failed to be processed.
* @dev Ignores unknown commands or duplicate commandIDs.
* @dev Emits an Executed event for successfully executed commands.
*/
// slither-disable-next-line cyclomatic-complexity
function execute(bytes calldata input) external override {
(bytes memory data, bytes memory proof) = abi.decode(input, (bytes, bytes));
bytes32 messageHash = ECDSA.toEthSignedMessageHash(keccak256(data));
// returns true for current operators
// slither-disable-next-line reentrancy-no-eth
bool allowOperatorshipTransfer = IAxelarAuth(authModule).validateProof(messageHash, proof);
uint256 chainId;
bytes32[] memory commandIds;
string[] memory commands;
bytes[] memory params;
(chainId, commandIds, commands, params) = abi.decode(data, (uint256, bytes32[], string[], bytes[]));
if (chainId != block.chainid) revert InvalidChainId();
uint256 commandsLength = commandIds.length;
if (commandsLength != commands.length || commandsLength != params.length) revert InvalidCommands();
for (uint256 i; i < commandsLength; ++i) {
bytes32 commandId = commandIds[i];
// Ignore if duplicate commandId received
if (isCommandExecuted(commandId)) continue;
bytes4 commandSelector;
bytes32 commandHash = keccak256(abi.encodePacked(commands[i]));
if (commandHash == SELECTOR_DEPLOY_TOKEN) {
commandSelector = AxelarGateway.deployToken.selector;
} else if (commandHash == SELECTOR_MINT_TOKEN) {
commandSelector = AxelarGateway.mintToken.selector;
} else if (commandHash == SELECTOR_APPROVE_CONTRACT_CALL) {
commandSelector = AxelarGateway.approveContractCall.selector;
} else if (commandHash == SELECTOR_APPROVE_CONTRACT_CALL_WITH_MINT) {
commandSelector = AxelarGateway.approveContractCallWithMint.selector;
} else if (commandHash == SELECTOR_BURN_TOKEN) {
commandSelector = AxelarGateway.burnToken.selector;
} else if (commandHash == SELECTOR_TRANSFER_OPERATORSHIP) {
if (!allowOperatorshipTransfer) continue;
allowOperatorshipTransfer = false;
commandSelector = AxelarGateway.transferOperatorship.selector;
} else {
// Ignore unknown commands
continue;
}
// Prevent a re-entrancy from executing this command before it can be marked as successful.
_setCommandExecuted(commandId, true);
// slither-disable-next-line calls-loop,reentrancy-no-eth
(bool success, ) = address(this).call(abi.encodeWithSelector(commandSelector, params[i], commandId));
// slither-disable-next-line reentrancy-events
if (success) emit Executed(commandId);
else _setCommandExecuted(commandId, false);
}
}
/******************\\
|* Self Functions *|
\\******************/
/**
* @notice Deploys a new token or registers an existing token in the gateway contract itself.
* @param params Encoded parameters including the token name, symbol, decimals, cap, token address, and mint limit
* @dev If the token address is not specified, a new token is deployed and registed as InternalBurnableFrom
* @dev If the token address is specified, the token is marked as External.
* @dev Emits a TokenDeployed event with the symbol and token address.
*/
function deployToken(bytes calldata params, bytes32) external onlySelf {
(string memory name, string memory symbol, uint8 decimals, uint256 cap, address tokenAddress, uint256 mintLimit) = abi.decode(
params,
(string, string, uint8, uint256, address, uint256)
);
// Ensure that this symbol has not been taken.
if (tokenAddresses(symbol) != address(0)) revert TokenAlreadyExists(symbol);
_setTokenMintLimit(symbol, mintLimit);
if (tokenAddress == address(0)) {
// If token address is not specified, it indicates a request to deploy one.
bytes32 salt = keccak256(abi.encodePacked(symbol));
_setTokenType(symbol, TokenType.InternalBurnableFrom);
// slither-disable-next-line reentrancy-no-eth,controlled-delegatecall
(bool success, bytes memory data) = tokenDeployer.delegatecall(
abi.encodeWithSelector(ITokenDeployer.deployToken.selector, name, symbol, decimals, cap, salt)
);
if (!success) revert TokenDeployFailed(symbol);
tokenAddress = abi.decode(data, (address));
} else {
// If token address is specified, ensure that there is a contact at the specified address.
if (tokenAddress.code.length == uint256(0)) revert TokenContractDoesNotExist(tokenAddress);
// Mark that this symbol is an external token, which is needed to differentiate between operations on mint and burn.
_setTokenType(symbol, TokenType.External);
}
// slither-disable-next-line reentrancy-events
emit TokenDeployed(symbol, tokenAddress);
_setTokenAddress(symbol, tokenAddress);
}
/**
* @notice Transfers a specific amount of tokens to an account, based on the provided symbol.
* @param params Encoded parameters including the token symbol, recipient address, and amount to mint.
* @dev This function will revert if the token is not registered with the gatewaty.
* @dev If the token type is External, a safe transfer is performed to the recipient account.
* @dev If the token type is Internal (InternalBurnable or InternalBurnableFrom), the mint function is called on the token address.
*/
function mintToken(bytes calldata params, bytes32) external onlySelf {
(string memory symbol, address account, uint256 amount) = abi.decode(params, (string, address, uint256));
_mintToken(symbol, account, amount);
}
/**
* @notice Burns tokens of a given symbol, either through an external deposit handler or a token defined burn method.
* @param params Encoded parameters including the token symbol and a salt value for the deposit handler
*/
function burnToken(bytes calldata params, bytes32) external onlySelf {
(string memory symbol, bytes32 salt) = abi.decode(params, (string, bytes32));
address tokenAddress = tokenAddresses(symbol);
if (tokenAddress == address(0)) revert TokenDoesNotExist(symbol);
if (_getTokenType(symbol) == TokenType.External) {
address depositHandlerAddress = _getCreate2Address(salt, keccak256(abi.encodePacked(type(DepositHandler).creationCode)));
if (depositHandlerAddress.isContract()) return;
DepositHandler depositHandler = new DepositHandler{ salt: salt }();
(bool success, bytes memory returnData) = depositHandler.execute(
tokenAddress,
abi.encodeWithSelector(IERC20.transfer.selector, address(this), IERC20(tokenAddress).balanceOf(address(depositHandler)))
);
if (!success || (returnData.length != uint256(0) && !abi.decode(returnData, (bool)))) revert BurnFailed(symbol);
// NOTE: `depositHandler` must always be destroyed in the same runtime context that it is deployed.
depositHandler.destroy(address(this));
} else {
IBurnableMintableCappedERC20(tokenAddress).burn(salt);
}
}
/**
* @notice Approves a contract call.
* @param params Encoded parameters including the source chain, source address, contract address, payload hash, transaction hash, and event index
* @param commandId to associate with the approval
*/
function approveContractCall(bytes calldata params, bytes32 commandId) external onlySelf {
(
string memory sourceChain,
string memory sourceAddress,
address contractAddress,
bytes32 payloadHash,
bytes32 sourceTxHash,
uint256 sourceEventIndex
) = abi.decode(params, (string, string, address, bytes32, bytes32, uint256));
_setContractCallApproved(commandId, sourceChain, sourceAddress, contractAddress, payloadHash);
emit ContractCallApproved(commandId, sourceChain, sourceAddress, contractAddress, payloadHash, sourceTxHash, sourceEventIndex);
}
/**
* @notice Approves a contract call with token transfer.
* @param params Encoded parameters including the source chain, source address, contract address, payload hash, token symbol,
* token amount, transaction hash, and event index.
* @param commandId to associate with the approval
*/
function approveContractCallWithMint(bytes calldata params, bytes32 commandId) external onlySelf {
(
string memory sourceChain,
string memory sourceAddress,
address contractAddress,
bytes32 payloadHash,
string memory symbol,
uint256 amount,
bytes32 sourceTxHash,
uint256 sourceEventIndex
) = abi.decode(params, (string, string, address, bytes32, string, uint256, bytes32, uint256));
_setContractCallApprovedWithMint(commandId, sourceChain, sourceAddress, contractAddress, payloadHash, symbol, amount);
emit ContractCallApprovedWithMint(
commandId,
sourceChain,
sourceAddress,
contractAddress,
payloadHash,
symbol,
amount,
sourceTxHash,
sourceEventIndex
);
}
/**
* @notice Transfers operatorship with the provided data by calling the transferOperatorship function on the auth module.
* @param newOperatorsData Encoded data for the new operators
*/
function transferOperatorship(bytes calldata newOperatorsData, bytes32) external onlySelf {
emit OperatorshipTransferred(newOperatorsData);
IAxelarAuth(authModule).transferOperatorship(newOperatorsData);
}
/********************\\
|* Internal Methods *|
\\********************/
function _mintToken(
string memory symbol,
address account,
uint256 amount
) internal {
address tokenAddress = tokenAddresses(symbol);
if (tokenAddress == address(0)) revert TokenDoesNotExist(symbol);
_setTokenMintAmount(symbol, tokenMintAmount(symbol) + amount);
if (_getTokenType(symbol) == TokenType.External) {
IERC20(tokenAddress).safeTransfer(account, amount);
} else {
IBurnableMintableCappedERC20(tokenAddress).mint(account, amount);
}
}
/**
* @notice Burns or locks a specific amount of tokens from a sender's account based on the provided symbol.
* @param sender Address of the account from which to burn the tokens
* @param symbol Symbol of the token to burn
* @param amount Amount of tokens to burn
* @dev Depending on the token type (External, InternalBurnableFrom, or InternalBurnable), the function either
* transfers the tokens to gateway contract itself or calls a burn function on the token contract.
*/
function _burnTokenFrom(
address sender,
string memory symbol,
uint256 amount
) internal {
address tokenAddress = tokenAddresses(symbol);
if (tokenAddress == address(0)) revert TokenDoesNotExist(symbol);
if (amount == 0) revert InvalidAmount();
TokenType tokenType = _getTokenType(symbol);
if (tokenType == TokenType.External) {
IERC20(tokenAddress).safeTransferFrom(sender, address(this), amount);
} else if (tokenType == TokenType.InternalBurnableFrom) {
IERC20(tokenAddress).safeCall(abi.encodeWithSelector(IBurnableMintableCappedERC20.burnFrom.selector, sender, amount));
} else {
IERC20(tokenAddress).safeTransferFrom(sender, IBurnableMintableCappedERC20(tokenAddress).depositAddress(bytes32(0)), amount);
IBurnableMintableCappedERC20(tokenAddress).burn(bytes32(0));
}
}
/********************\\
|* Pure Key Getters *|
\\********************/
function _getTokenMintLimitKey(string memory symbol) internal pure returns (bytes32) {
return keccak256(abi.encodePacked(PREFIX_TOKEN_MINT_LIMIT, symbol));
}
function _getTokenMintAmountKey(string memory symbol, uint256 day) internal pure returns (bytes32) {
return keccak256(abi.encode(PREFIX_TOKEN_MINT_AMOUNT, symbol, day));
}
function _getTokenTypeKey(string memory symbol) internal pure returns (bytes32) {
return keccak256(abi.encodePacked(PREFIX_TOKEN_TYPE, symbol));
}
function _getTokenAddressKey(string memory symbol) internal pure returns (bytes32) {
return keccak256(abi.encodePacked(PREFIX_TOKEN_ADDRESS, symbol));
}
function _getIsCommandExecutedKey(bytes32 commandId) internal pure returns (bytes32) {
return keccak256(abi.encodePacked(PREFIX_COMMAND_EXECUTED, commandId));
}
function _getIsContractCallApprovedKey(
bytes32 commandId,
string memory sourceChain,
string memory sourceAddress,
address contractAddress,
bytes32 payloadHash
) internal pure returns (bytes32) {
return keccak256(abi.encode(PREFIX_CONTRACT_CALL_APPROVED, commandId, sourceChain, sourceAddress, contractAddress, payloadHash));
}
function _getIsContractCallApprovedWithMintKey(
bytes32 commandId,
string memory sourceChain,
string memory sourceAddress,
address contractAddress,
bytes32 payloadHash,
string memory symbol,
uint256 amount
) internal pure returns (bytes32) {
return
keccak256(
abi.encode(
PREFIX_CONTRACT_CALL_APPROVED_WITH_MINT,
commandId,
sourceChain,
sourceAddress,
contractAddress,
payloadHash,
symbol,
amount
)
);
}
/********************\\
|* Internal Getters *|
\\********************/
function _getCreate2Address(bytes32 salt, bytes32 codeHash) internal view returns (address) {
return address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), salt, codeHash)))));
}
function _getTokenType(string memory symbol) internal view returns (TokenType) {
return TokenType(getUint(_getTokenTypeKey(symbol)));
}
/********************\\
|* Internal Setters *|
\\********************/
function _setTokenMintLimit(string memory symbol, uint256 limit) internal {
emit TokenMintLimitUpdated(symbol, limit);
_setUint(_getTokenMintLimitKey(symbol), limit);
}
function _setTokenMintAmount(string memory symbol, uint256 amount) internal {
uint256 limit = tokenMintLimit(symbol);
if (limit > 0 && amount > limit) revert ExceedMintLimit(symbol);
_setUint(_getTokenMintAmountKey(symbol, block.timestamp / 6 hours), amount);
}
function _setTokenType(string memory symbol, TokenType tokenType) internal {
_setUint(_getTokenTypeKey(symbol), uint256(tokenType));
}
function _setTokenAddress(string memory symbol, address tokenAddress) internal {
_setAddress(_getTokenAddressKey(symbol), tokenAddress);
}
function _setCommandExecuted(bytes32 commandId, bool executed) internal {
_setBool(_getIsCommandExecutedKey(commandId), executed);
}
function _setContractCallApproved(
bytes32 commandId,
string memory sourceChain,
string memory sourceAddress,
address contractAddress,
bytes32 payloadHash
) internal {
_setBool(_getIsContractCallApprovedKey(commandId, sourceChain, sourceAddress, contractAddress, payloadHash), true);
}
function _setContractCallApprovedWithMint(
bytes32 commandId,
string memory sourceChain,
string memory sourceAddress,
address contractAddress,
bytes32 payloadHash,
string memory symbol,
uint256 amount
) internal {
_setBool(
_getIsContractCallApprovedWithMintKey(commandId, sourceChain, sourceAddress, contractAddress, payloadHash, symbol, amount),
true
);
}
function _setImplementation(address newImplementation) internal {
_setAddress(KEY_IMPLEMENTATION, newImplementation);
}
function _transferGovernance(address newGovernance) internal {
emit GovernanceTransferred(getAddress(KEY_GOVERNANCE), newGovernance);
_setAddress(KEY_GOVERNANCE, newGovernance);
}
function _transferMintLimiter(address newMintLimiter) internal {
emit MintLimiterTransferred(getAddress(KEY_MINT_LIMITER), newMintLimiter);
_setAddress(KEY_MINT_LIMITER, newMintLimiter);
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.9;
contract DepositHandler {
error IsLocked();
error NotContract();
uint256 internal constant IS_NOT_LOCKED = uint256(1);
uint256 internal constant IS_LOCKED = uint256(2);
uint256 internal _lockedStatus = IS_NOT_LOCKED;
modifier noReenter() {
if (_lockedStatus == IS_LOCKED) revert IsLocked();
_lockedStatus = IS_LOCKED;
_;
_lockedStatus = IS_NOT_LOCKED;
}
function execute(address callee, bytes calldata data) external noReenter returns (bool success, bytes memory returnData) {
if (callee.code.length == 0) revert NotContract();
(success, returnData) = callee.call(data);
}
// NOTE: The gateway should always destroy the `DepositHandler` in the same runtime context that deploys it.
function destroy(address etherDestination) external noReenter {
selfdestruct(payable(etherDestination));
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.9;
/**
* @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
*
* These functions can be used to verify that a message was signed by the holder
* of the private keys of a given address.
*/
library ECDSA {
error InvalidSignatureLength();
error InvalidS();
error InvalidV();
error InvalidSignature();
/**
* @dev Returns the address that signed a hashed message (`hash`) with
* `signature`. This address can then be used for verification purposes.
*
* The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
* this function rejects them by requiring the `s` value to be in the lower
* half order, and the `v` value to be either 27 or 28.
*
* IMPORTANT: `hash` _must_ be the result of a hash operation for the
* verification to be secure: it is possible to craft signatures that
* recover to arbitrary addresses for non-hashed data. A safe way to ensure
* this is by receiving a hash of the original message (which may otherwise
* be too long), and then calling {toEthSignedMessageHash} on it.
*/
function recover(bytes32 hash, bytes memory signature) internal pure returns (address signer) {
// Check the signature length
if (signature.length != 65) revert InvalidSignatureLength();
// Divide the signature in r, s and v variables
bytes32 r;
bytes32 s;
uint8 v;
// ecrecover takes the signature parameters, and the only way to get them
// currently is to use assembly.
// solhint-disable-next-line no-inline-assembly
assembly {
r := mload(add(signature, 0x20))
s := mload(add(signature, 0x40))
v := byte(0, mload(add(signature, 0x60)))
}
// EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
// unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
// the valid range for s in (281): 0 < s < secp256k1n ÷ 2 + 1, and for v in (282): v ∈ {27, 28}. Most
// signatures from current libraries generate a unique signature with an s-value in the lower half order.
//
// If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
// with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
// vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
// these malleable signatures as well.
if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) revert InvalidS();
if (v != 27 && v != 28) revert InvalidV();
// If the signature is valid (and not malleable), return the signer address
if ((signer = ecrecover(hash, v, r, s)) == address(0)) revert InvalidSignature();
}
/**
* @dev Returns an Ethereum Signed Message, created from a `hash`. This
* replicates the behavior of the
* https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sign[`eth_sign`]
* JSON-RPC method.
*
* See {recover}.
*/
function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) {
// 32 is the length in bytes of hash,
// enforced by the type signature above
return keccak256(abi.encodePacked('\\x19Ethereum Signed Message:\
32', hash));
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.9;
/**
* @title EternalStorage
* @dev This contract holds all the necessary state variables to carry out the storage of any contract.
*/
contract EternalStorage {
mapping(bytes32 => uint256) private _uintStorage;
mapping(bytes32 => string) private _stringStorage;
mapping(bytes32 => address) private _addressStorage;
mapping(bytes32 => bytes) private _bytesStorage;
mapping(bytes32 => bool) private _boolStorage;
mapping(bytes32 => int256) private _intStorage;
// *** Getter Methods ***
function getUint(bytes32 key) public view returns (uint256) {
return _uintStorage[key];
}
function getString(bytes32 key) public view returns (string memory) {
return _stringStorage[key];
}
function getAddress(bytes32 key) public view returns (address) {
return _addressStorage[key];
}
function getBytes(bytes32 key) public view returns (bytes memory) {
return _bytesStorage[key];
}
function getBool(bytes32 key) public view returns (bool) {
return _boolStorage[key];
}
function getInt(bytes32 key) public view returns (int256) {
return _intStorage[key];
}
// *** Setter Methods ***
function _setUint(bytes32 key, uint256 value) internal {
_uintStorage[key] = value;
}
function _setString(bytes32 key, string memory value) internal {
_stringStorage[key] = value;
}
function _setAddress(bytes32 key, address value) internal {
_addressStorage[key] = value;
}
function _setBytes(bytes32 key, bytes memory value) internal {
_bytesStorage[key] = value;
}
function _setBool(bytes32 key, bool value) internal {
_boolStorage[key] = value;
}
function _setInt(bytes32 key, int256 value) internal {
_intStorage[key] = value;
}
// *** Delete Methods ***
function _deleteUint(bytes32 key) internal {
delete _uintStorage[key];
}
function _deleteString(bytes32 key) internal {
delete _stringStorage[key];
}
function _deleteAddress(bytes32 key) internal {
delete _addressStorage[key];
}
function _deleteBytes(bytes32 key) internal {
delete _bytesStorage[key];
}
function _deleteBool(bytes32 key) internal {
delete _boolStorage[key];
}
function _deleteInt(bytes32 key) internal {
delete _intStorage[key];
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import { IOwnable } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IOwnable.sol';
interface IAxelarAuth is IOwnable {
function validateProof(bytes32 messageHash, bytes calldata proof) external returns (bool currentOperators);
function transferOperatorship(bytes calldata params) external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import { IERC20Burn } from './IERC20Burn.sol';
import { IERC20BurnFrom } from './IERC20BurnFrom.sol';
import { IMintableCappedERC20 } from './IMintableCappedERC20.sol';
interface IBurnableMintableCappedERC20 is IERC20Burn, IERC20BurnFrom, IMintableCappedERC20 {
function depositAddress(bytes32 salt) external view returns (address);
function burn(bytes32 salt) external;
function burnFrom(address account, uint256 amount) external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
error InvalidAccount();
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `recipient`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address recipient, uint256 amount) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `sender` to `recipient` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(
address sender,
address recipient,
uint256 amount
) external returns (bool);
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
interface IERC20Burn {
function burn(bytes32 salt) external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
interface IERC20BurnFrom {
function burnFrom(address account, uint256 amount) external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
interface IERC20Permit {
function DOMAIN_SEPARATOR() external view returns (bytes32);
function nonces(address account) external view returns (uint256);
function permit(
address issuer,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import { IERC20 } from './IERC20.sol';
import { IERC20Permit } from './IERC20Permit.sol';
import { IOwnable } from './IOwnable.sol';
interface IMintableCappedERC20 is IERC20, IERC20Permit, IOwnable {
error CapExceeded();
function cap() external view returns (uint256);
function mint(address account, uint256 amount) external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
interface IOwnable {
error NotOwner();
error InvalidOwner();
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
function owner() external view returns (address);
function transferOwnership(address newOwner) external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface ITokenDeployer {
function deployToken(
string calldata name,
string calldata symbol,
uint8 decimals,
uint256 cap,
bytes32 salt
) external returns (address tokenAddress);
}
File 4 of 4: AxelarGasService
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import { GasEstimationType, GasInfo } from '../types/GasEstimationTypes.sol';
import { IInterchainGasEstimation } from '../interfaces/IInterchainGasEstimation.sol';
/**
* @title InterchainGasEstimation
* @notice This is an abstract contract that allows for estimating gas fees for cross-chain communication on the Axelar network.
*/
abstract contract InterchainGasEstimation is IInterchainGasEstimation {
// keccak256('GasEstimate.Slot') - 1
bytes32 internal constant GAS_SERVICE_SLOT = 0x2fa150da4c9f4c3a28593398c65313dd42f63d0530ec6db4a2b46e6d837a3902;
// 68 bytes for the TX RLP encoding overhead
uint256 internal constant TX_ENCODING_OVERHEAD = 68;
// GMP executeWithToken call parameters
// 4 bytes for method selector, 32 bytes for the commandId, 96 bytes for the sourceChain, 128 bytes for the sourceAddress, 96 bytes for token symbol, 32 bytes for amount
// Expecting most of the calldata bytes to be zeroes. So multiplying by 8 as a weighted average of 4 and 16
uint256 internal constant GMP_CALLDATA_SIZE = 4 + 32 + 96 + 128 + 96 + 32; // 388 bytes
struct GasServiceStorage {
mapping(string => GasInfo) gasPrices;
}
/**
* @notice Returns the gas price for a specific chain.
* @param chain The name of the chain
* @return gasInfo The gas info for the chain
*/
function getGasInfo(string calldata chain) external view returns (GasInfo memory) {
return _storage().gasPrices[chain];
}
/**
* @notice Sets the gas price for a specific chain.
* @dev This function is called by the gas oracle to update the gas price for a specific chain.
* @param chain The name of the chain
* @param gasInfo The gas info for the chain
*/
function _setGasInfo(string calldata chain, GasInfo calldata gasInfo) internal {
emit GasInfoUpdated(chain, gasInfo);
_storage().gasPrices[chain] = gasInfo;
}
/**
* @notice Estimates the gas fee for a contract call on a destination chain.
* @param destinationChain Axelar registered name of the destination chain
* param destinationAddress Destination contract address being called
* @param executionGasLimit The gas limit to be used for the destination contract execution,
* e.g. pass in 200k if your app consumes needs upto 200k for this contract call
* param params Additional parameters for the gas estimation
* @return gasEstimate The cross-chain gas estimate, in terms of source chain's native gas token that should be forwarded to the gas service.
*/
function estimateGasFee(
string calldata destinationChain,
string calldata, /* destinationAddress */
bytes calldata payload,
uint256 executionGasLimit,
bytes calldata /* params */
) public view returns (uint256 gasEstimate) {
GasInfo storage gasInfo = _storage().gasPrices[destinationChain];
GasEstimationType gasEstimationType = GasEstimationType(gasInfo.gasEstimationType);
gasEstimate = gasInfo.axelarBaseFee + (executionGasLimit * gasInfo.relativeGasPrice);
// if chain is L2, compute L1 data fee using L1 gas price info
if (gasEstimationType != GasEstimationType.Default) {
GasInfo storage l1GasInfo = _storage().gasPrices['ethereum'];
gasEstimate += computeL1DataFee(gasEstimationType, payload, gasInfo, l1GasInfo);
}
}
/**
* @notice Computes the additional L1 data fee for an L2 destination chain.
* @param gasEstimationType The gas estimation type
* @param payload The payload of the contract call
* @param l1GasInfo The L1 gas info
* @return l1DataFee The L1 to L2 data fee
*/
function computeL1DataFee(
GasEstimationType gasEstimationType,
bytes calldata payload,
GasInfo storage gasInfo,
GasInfo storage l1GasInfo
) internal view returns (uint256) {
if (gasEstimationType == GasEstimationType.OptimismEcotone) {
return optimismEcotoneL1Fee(payload, gasInfo, l1GasInfo);
}
if (gasEstimationType == GasEstimationType.OptimismBedrock) {
return optimismBedrockL1Fee(payload, gasInfo, l1GasInfo);
}
if (gasEstimationType == GasEstimationType.Arbitrum) {
return arbitrumL1Fee(payload, gasInfo, l1GasInfo);
}
if (gasEstimationType == GasEstimationType.Scroll) {
return scrollL1Fee(payload, gasInfo, l1GasInfo);
}
revert UnsupportedEstimationType(gasEstimationType);
}
/**
* @notice Computes the L1 to L2 fee for an OP chain with Ecotone gas model.
* @param payload The payload of the contract call
* @param gasInfo Destination chain gas info
* @param l1GasInfo The L1 gas info
* @return l1DataFee The L1 to L2 data fee
*/
function optimismEcotoneL1Fee(
bytes calldata payload,
GasInfo storage gasInfo,
GasInfo storage l1GasInfo
) internal view returns (uint256 l1DataFee) {
/* Optimism Ecotone gas model https://docs.optimism.io/stack/transactions/fees#ecotone
tx_compressed_size = ((count_zero_bytes(tx_data) * 4 + count_non_zero_bytes(tx_data) * 16)) / 16
weighted_gas_price = 16 * base_fee_scalar*base_fee + blob_base_fee_scalar * blob_base_fee
l1_data_fee = tx_compressed_size * weighted_gas_price
Reference implementation:
https://github.com/ethereum-optimism/optimism/blob/876e16ad04968f0bb641eb76f98eb77e7e1a3e16/packages/contracts-bedrock/src/L2/GasPriceOracle.sol#L138
*/
// The new base_fee_scalar is currently set to 0.001368
// We are setting it to un upper bound of 0.0015 to account for possible fluctuations
uint256 scalarPrecision = 10**6;
// The blob_base_fee_scalar is currently set to 0.810949. Setting it to 0.9 as an upper bound
// https://eips.ethereum.org/EIPS/eip-4844
uint256 blobBaseFeeScalar = 9 * 10**5; // 0.9 multiplied by scalarPrecision
// Calculating transaction size in bytes that will later be divided by 16 to compress the size
uint256 txSize = _l1TxSize(payload);
uint256 weightedGasPrice = 16 *
gasInfo.l1FeeScalar *
l1GasInfo.relativeGasPrice +
blobBaseFeeScalar *
l1GasInfo.relativeBlobBaseFee;
l1DataFee = (weightedGasPrice * txSize) / (16 * scalarPrecision); // 16 for txSize compression and scalar precision conversion
}
/**
* @notice Computes the L1 to L2 fee for an OP chain with Bedrock gas model.
* @param payload The payload of the contract call
* @param gasInfo Destination chain gas info
* @param l1GasInfo The L1 gas info
* @return l1DataFee The L1 to L2 data fee
*/
function optimismBedrockL1Fee(
bytes calldata payload,
GasInfo storage gasInfo,
GasInfo storage l1GasInfo
) internal view returns (uint256 l1DataFee) {
// Resembling OP Bedrock gas price model
// https://docs.optimism.io/stack/transactions/fees#bedrock
// https://docs-v2.mantle.xyz/devs/concepts/tx-fee/ef
// Reference https://github.com/mantlenetworkio/mantle-v2/blob/a29f01045191344b0ba89542215e6a02bd5e7fcc/packages/contracts-bedrock/contracts/L2/GasPriceOracle.sol#L98-L105
uint256 overhead = 188;
uint256 precision = 1e6;
uint256 txSize = _l1TxSize(payload) + overhead;
return (l1GasInfo.relativeGasPrice * txSize * gasInfo.l1FeeScalar) / precision;
}
/**
* @notice Computes the L1 to L2 fee for a contract call on the Arbitrum chain.
* @param payload The payload of the contract call
* param gasInfo Destination chain gas info
* @param l1GasInfo The L1 gas info
* @return l1DataFee The L1 to L2 data fee
*/
function arbitrumL1Fee(
bytes calldata payload,
GasInfo storage, /* gasInfo */
GasInfo storage l1GasInfo
) internal view returns (uint256 l1DataFee) {
// https://docs.arbitrum.io/build-decentralized-apps/how-to-estimate-gas
// https://docs.arbitrum.io/arbos/l1-pricing
// Reference https://github.com/OffchainLabs/nitro/blob/master/arbos/l1pricing/l1pricing.go#L565-L578
uint256 oneInBips = 10000;
uint256 txDataNonZeroGasEIP2028 = 16;
uint256 estimationPaddingUnits = 16 * txDataNonZeroGasEIP2028;
uint256 estimationPaddingBasisPoints = 100;
uint256 l1Bytes = TX_ENCODING_OVERHEAD + GMP_CALLDATA_SIZE + payload.length;
// Brotli baseline compression rate as 2x
uint256 units = (txDataNonZeroGasEIP2028 * l1Bytes) / 2;
return
(l1GasInfo.relativeGasPrice *
(units + estimationPaddingUnits) *
(oneInBips + estimationPaddingBasisPoints)) / oneInBips;
}
/**
* @notice Computes the L1 to L2 fee for a contract call on the Scroll chain.
* @param payload The payload of the contract call
* @param gasInfo Destination chain gas info
* @param l1GasInfo The L1 gas info
* @return l1DataFee The L1 to L2 data fee
*/
function scrollL1Fee(
bytes calldata payload,
GasInfo storage gasInfo,
GasInfo storage l1GasInfo
) internal view returns (uint256 l1DataFee) {
// https://docs.scroll.io/en/developers/guides/estimating-gas-and-tx-fees/
// Reference https://github.com/scroll-tech/scroll/blob/af2913903b181f3492af1c62b4da4c1c99cc552d/contracts/src/L2/predeploys/L1GasPriceOracle.sol#L63-L86
uint256 overhead = 2500;
uint256 precision = 1e9;
uint256 txSize = _l1TxSize(payload) + overhead + (4 * 16);
return (l1GasInfo.relativeGasPrice * txSize * gasInfo.l1FeeScalar) / precision;
}
/**
* @notice Computes the transaction size for an L1 transaction
* @param payload The payload of the contract call
* @return txSize The transaction size
*/
function _l1TxSize(bytes calldata payload) private pure returns (uint256 txSize) {
txSize = TX_ENCODING_OVERHEAD * 16;
// GMP executeWithToken call parameters
// Expecting most of the calldata bytes to be zeroes. So multiplying by 8 as a weighted average of 4 and 16
txSize += GMP_CALLDATA_SIZE * 8;
uint256 length = payload.length;
for (uint256 i; i < length; ++i) {
if (payload[i] == 0) {
txSize += 4; // 4 for each zero byte
} else {
txSize += 16; // 16 for each non-zero byte
}
}
}
/**
* @notice Get the storage slot for the GasServiceStorage struct
*/
function _storage() private pure returns (GasServiceStorage storage slot) {
assembly {
slot.slot := GAS_SERVICE_SLOT
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import { GasInfo } from '../types/GasEstimationTypes.sol';
import { IInterchainGasEstimation } from './IInterchainGasEstimation.sol';
import { IUpgradable } from './IUpgradable.sol';
/**
* @title IAxelarGasService Interface
* @notice This is an interface for the AxelarGasService contract which manages gas payments
* and refunds for cross-chain communication on the Axelar network.
* @dev This interface inherits IUpgradable
*/
interface IAxelarGasService is IInterchainGasEstimation, IUpgradable {
error InvalidAddress();
error NotCollector();
error InvalidAmounts();
error InvalidGasUpdates();
error InvalidParams();
error InsufficientGasPayment(uint256 required, uint256 provided);
event GasPaidForContractCall(
address indexed sourceAddress,
string destinationChain,
string destinationAddress,
bytes32 indexed payloadHash,
address gasToken,
uint256 gasFeeAmount,
address refundAddress
);
event GasPaidForContractCallWithToken(
address indexed sourceAddress,
string destinationChain,
string destinationAddress,
bytes32 indexed payloadHash,
string symbol,
uint256 amount,
address gasToken,
uint256 gasFeeAmount,
address refundAddress
);
event NativeGasPaidForContractCall(
address indexed sourceAddress,
string destinationChain,
string destinationAddress,
bytes32 indexed payloadHash,
uint256 gasFeeAmount,
address refundAddress
);
event NativeGasPaidForContractCallWithToken(
address indexed sourceAddress,
string destinationChain,
string destinationAddress,
bytes32 indexed payloadHash,
string symbol,
uint256 amount,
uint256 gasFeeAmount,
address refundAddress
);
event GasPaidForExpressCall(
address indexed sourceAddress,
string destinationChain,
string destinationAddress,
bytes32 indexed payloadHash,
address gasToken,
uint256 gasFeeAmount,
address refundAddress
);
event GasPaidForExpressCallWithToken(
address indexed sourceAddress,
string destinationChain,
string destinationAddress,
bytes32 indexed payloadHash,
string symbol,
uint256 amount,
address gasToken,
uint256 gasFeeAmount,
address refundAddress
);
event NativeGasPaidForExpressCall(
address indexed sourceAddress,
string destinationChain,
string destinationAddress,
bytes32 indexed payloadHash,
uint256 gasFeeAmount,
address refundAddress
);
event NativeGasPaidForExpressCallWithToken(
address indexed sourceAddress,
string destinationChain,
string destinationAddress,
bytes32 indexed payloadHash,
string symbol,
uint256 amount,
uint256 gasFeeAmount,
address refundAddress
);
event GasAdded(
bytes32 indexed txHash,
uint256 indexed logIndex,
address gasToken,
uint256 gasFeeAmount,
address refundAddress
);
event NativeGasAdded(bytes32 indexed txHash, uint256 indexed logIndex, uint256 gasFeeAmount, address refundAddress);
event ExpressGasAdded(
bytes32 indexed txHash,
uint256 indexed logIndex,
address gasToken,
uint256 gasFeeAmount,
address refundAddress
);
event NativeExpressGasAdded(
bytes32 indexed txHash,
uint256 indexed logIndex,
uint256 gasFeeAmount,
address refundAddress
);
event Refunded(
bytes32 indexed txHash,
uint256 indexed logIndex,
address payable receiver,
address token,
uint256 amount
);
/**
* @notice Pay for gas for any type of contract execution on a destination chain.
* @dev This function is called on the source chain before calling the gateway to execute a remote contract.
* @dev If estimateOnChain is true, the function will estimate the gas cost and revert if the payment is insufficient.
* @param sender The address making the payment
* @param destinationChain The target chain where the contract call will be made
* @param destinationAddress The target address on the destination chain
* @param payload Data payload for the contract call
* @param executionGasLimit The gas limit for the contract call
* @param estimateOnChain Flag to enable on-chain gas estimation
* @param refundAddress The address where refunds, if any, should be sent
* @param params Additional parameters for gas payment. This can be left empty for normal contract call payments.
*/
function payGas(
address sender,
string calldata destinationChain,
string calldata destinationAddress,
bytes calldata payload,
uint256 executionGasLimit,
bool estimateOnChain,
address refundAddress,
bytes calldata params
) external payable;
/**
* @notice Pay for gas using ERC20 tokens for a contract call on a destination chain.
* @dev This function is called on the source chain before calling the gateway to execute a remote contract.
* @param sender The address making the payment
* @param destinationChain The target chain where the contract call will be made
* @param destinationAddress The target address on the destination chain
* @param payload Data payload for the contract call
* @param gasToken The address of the ERC20 token used to pay for gas
* @param gasFeeAmount The amount of tokens to pay for gas
* @param refundAddress The address where refunds, if any, should be sent
*/
function payGasForContractCall(
address sender,
string calldata destinationChain,
string calldata destinationAddress,
bytes calldata payload,
address gasToken,
uint256 gasFeeAmount,
address refundAddress
) external;
/**
* @notice Pay for gas using ERC20 tokens for a contract call with tokens on a destination chain.
* @dev This function is called on the source chain before calling the gateway to execute a remote contract.
* @param sender The address making the payment
* @param destinationChain The target chain where the contract call with tokens will be made
* @param destinationAddress The target address on the destination chain
* @param payload Data payload for the contract call with tokens
* @param symbol The symbol of the token to be sent with the call
* @param amount The amount of tokens to be sent with the call
* @param gasToken The address of the ERC20 token used to pay for gas
* @param gasFeeAmount The amount of tokens to pay for gas
* @param refundAddress The address where refunds, if any, should be sent
*/
function payGasForContractCallWithToken(
address sender,
string calldata destinationChain,
string calldata destinationAddress,
bytes calldata payload,
string calldata symbol,
uint256 amount,
address gasToken,
uint256 gasFeeAmount,
address refundAddress
) external;
/**
* @notice Pay for gas using native currency for a contract call on a destination chain.
* @dev This function is called on the source chain before calling the gateway to execute a remote contract.
* @param sender The address making the payment
* @param destinationChain The target chain where the contract call will be made
* @param destinationAddress The target address on the destination chain
* @param payload Data payload for the contract call
* @param refundAddress The address where refunds, if any, should be sent
*/
function payNativeGasForContractCall(
address sender,
string calldata destinationChain,
string calldata destinationAddress,
bytes calldata payload,
address refundAddress
) external payable;
/**
* @notice Pay for gas using native currency for a contract call with tokens on a destination chain.
* @dev This function is called on the source chain before calling the gateway to execute a remote contract.
* @param sender The address making the payment
* @param destinationChain The target chain where the contract call with tokens will be made
* @param destinationAddress The target address on the destination chain
* @param payload Data payload for the contract call with tokens
* @param symbol The symbol of the token to be sent with the call
* @param amount The amount of tokens to be sent with the call
* @param refundAddress The address where refunds, if any, should be sent
*/
function payNativeGasForContractCallWithToken(
address sender,
string calldata destinationChain,
string calldata destinationAddress,
bytes calldata payload,
string calldata symbol,
uint256 amount,
address refundAddress
) external payable;
/**
* @notice Pay for gas using ERC20 tokens for an express contract call on a destination chain.
* @dev This function is called on the source chain before calling the gateway to express execute a remote contract.
* @param sender The address making the payment
* @param destinationChain The target chain where the contract call will be made
* @param destinationAddress The target address on the destination chain
* @param payload Data payload for the contract call
* @param gasToken The address of the ERC20 token used to pay for gas
* @param gasFeeAmount The amount of tokens to pay for gas
* @param refundAddress The address where refunds, if any, should be sent
*/
function payGasForExpressCall(
address sender,
string calldata destinationChain,
string calldata destinationAddress,
bytes calldata payload,
address gasToken,
uint256 gasFeeAmount,
address refundAddress
) external;
/**
* @notice Pay for gas using ERC20 tokens for an express contract call with tokens on a destination chain.
* @dev This function is called on the source chain before calling the gateway to express execute a remote contract.
* @param sender The address making the payment
* @param destinationChain The target chain where the contract call with tokens will be made
* @param destinationAddress The target address on the destination chain
* @param payload Data payload for the contract call with tokens
* @param symbol The symbol of the token to be sent with the call
* @param amount The amount of tokens to be sent with the call
* @param gasToken The address of the ERC20 token used to pay for gas
* @param gasFeeAmount The amount of tokens to pay for gas
* @param refundAddress The address where refunds, if any, should be sent
*/
function payGasForExpressCallWithToken(
address sender,
string calldata destinationChain,
string calldata destinationAddress,
bytes calldata payload,
string calldata symbol,
uint256 amount,
address gasToken,
uint256 gasFeeAmount,
address refundAddress
) external;
/**
* @notice Pay for gas using native currency for an express contract call on a destination chain.
* @dev This function is called on the source chain before calling the gateway to execute a remote contract.
* @param sender The address making the payment
* @param destinationChain The target chain where the contract call will be made
* @param destinationAddress The target address on the destination chain
* @param payload Data payload for the contract call
* @param refundAddress The address where refunds, if any, should be sent
*/
function payNativeGasForExpressCall(
address sender,
string calldata destinationChain,
string calldata destinationAddress,
bytes calldata payload,
address refundAddress
) external payable;
/**
* @notice Pay for gas using native currency for an express contract call with tokens on a destination chain.
* @dev This function is called on the source chain before calling the gateway to execute a remote contract.
* @param sender The address making the payment
* @param destinationChain The target chain where the contract call with tokens will be made
* @param destinationAddress The target address on the destination chain
* @param payload Data payload for the contract call with tokens
* @param symbol The symbol of the token to be sent with the call
* @param amount The amount of tokens to be sent with the call
* @param refundAddress The address where refunds, if any, should be sent
*/
function payNativeGasForExpressCallWithToken(
address sender,
string calldata destinationChain,
string calldata destinationAddress,
bytes calldata payload,
string calldata symbol,
uint256 amount,
address refundAddress
) external payable;
/**
* @notice Add additional gas payment using ERC20 tokens after initiating a cross-chain call.
* @dev This function can be called on the source chain after calling the gateway to execute a remote contract.
* @param txHash The transaction hash of the cross-chain call
* @param logIndex The log index for the cross-chain call
* @param gasToken The ERC20 token address used to add gas
* @param gasFeeAmount The amount of tokens to add as gas
* @param refundAddress The address where refunds, if any, should be sent
*/
function addGas(
bytes32 txHash,
uint256 logIndex,
address gasToken,
uint256 gasFeeAmount,
address refundAddress
) external;
/**
* @notice Add additional gas payment using native currency after initiating a cross-chain call.
* @dev This function can be called on the source chain after calling the gateway to execute a remote contract.
* @param txHash The transaction hash of the cross-chain call
* @param logIndex The log index for the cross-chain call
* @param refundAddress The address where refunds, if any, should be sent
*/
function addNativeGas(
bytes32 txHash,
uint256 logIndex,
address refundAddress
) external payable;
/**
* @notice Add additional gas payment using ERC20 tokens after initiating an express cross-chain call.
* @dev This function can be called on the source chain after calling the gateway to express execute a remote contract.
* @param txHash The transaction hash of the cross-chain call
* @param logIndex The log index for the cross-chain call
* @param gasToken The ERC20 token address used to add gas
* @param gasFeeAmount The amount of tokens to add as gas
* @param refundAddress The address where refunds, if any, should be sent
*/
function addExpressGas(
bytes32 txHash,
uint256 logIndex,
address gasToken,
uint256 gasFeeAmount,
address refundAddress
) external;
/**
* @notice Add additional gas payment using native currency after initiating an express cross-chain call.
* @dev This function can be called on the source chain after calling the gateway to express execute a remote contract.
* @param txHash The transaction hash of the cross-chain call
* @param logIndex The log index for the cross-chain call
* @param refundAddress The address where refunds, if any, should be sent
*/
function addNativeExpressGas(
bytes32 txHash,
uint256 logIndex,
address refundAddress
) external payable;
/**
* @notice Updates the gas price for a specific chain.
* @dev This function is called by the gas oracle to update the gas prices for a specific chains.
* @param chains Array of chain names
* @param gasUpdates Array of gas updates
*/
function updateGasInfo(string[] calldata chains, GasInfo[] calldata gasUpdates) external;
/**
* @notice Allows the gasCollector to collect accumulated fees from the contract.
* @dev Use address(0) as the token address for native currency.
* @param receiver The address to receive the collected fees
* @param tokens Array of token addresses to be collected
* @param amounts Array of amounts to be collected for each respective token address
*/
function collectFees(
address payable receiver,
address[] calldata tokens,
uint256[] calldata amounts
) external;
/**
* @notice Refunds gas payment to the receiver in relation to a specific cross-chain transaction.
* @dev Only callable by the gasCollector.
* @dev Use address(0) as the token address to refund native currency.
* @param txHash The transaction hash of the cross-chain call
* @param logIndex The log index for the cross-chain call
* @param receiver The address to receive the refund
* @param token The token address to be refunded
* @param amount The amount to refund
*/
function refund(
bytes32 txHash,
uint256 logIndex,
address payable receiver,
address token,
uint256 amount
) external;
/**
* @notice Returns the address of the designated gas collector.
* @return address of the gas collector
*/
function gasCollector() external returns (address);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
// General interface for upgradable contracts
interface IContractIdentifier {
/**
* @notice Returns the contract ID. It can be used as a check during upgrades.
* @dev Meant to be overridden in derived contracts.
* @return bytes32 The contract ID
*/
function contractId() external pure returns (bytes32);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
error InvalidAccount();
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `recipient`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address recipient, uint256 amount) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `sender` to `recipient` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(
address sender,
address recipient,
uint256 amount
) external returns (bool);
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import { IContractIdentifier } from './IContractIdentifier.sol';
interface IImplementation is IContractIdentifier {
error NotProxy();
function setup(bytes calldata data) external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import { GasEstimationType, GasInfo } from '../types/GasEstimationTypes.sol';
/**
* @title IInterchainGasEstimation Interface
* @notice This is an interface for the InterchainGasEstimation contract
* which allows for estimating gas fees for cross-chain communication on the Axelar network.
*/
interface IInterchainGasEstimation {
error UnsupportedEstimationType(GasEstimationType gasEstimationType);
/**
* @notice Event emitted when the gas price for a specific chain is updated.
* @param chain The name of the chain
* @param info The gas info for the chain
*/
event GasInfoUpdated(string chain, GasInfo info);
/**
* @notice Returns the gas price for a specific chain.
* @param chain The name of the chain
* @return gasInfo The gas info for the chain
*/
function getGasInfo(string calldata chain) external view returns (GasInfo memory);
/**
* @notice Estimates the gas fee for a cross-chain contract call.
* @param destinationChain Axelar registered name of the destination chain
* @param destinationAddress Destination contract address being called
* @param executionGasLimit The gas limit to be used for the destination contract execution,
* e.g. pass in 200k if your app consumes needs upto 200k for this contract call
* @param params Additional parameters for the gas estimation
* @return gasEstimate The cross-chain gas estimate, in terms of source chain's native gas token that should be forwarded to the gas service.
*/
function estimateGasFee(
string calldata destinationChain,
string calldata destinationAddress,
bytes calldata payload,
uint256 executionGasLimit,
bytes calldata params
) external view returns (uint256 gasEstimate);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @title IOwnable Interface
* @notice IOwnable is an interface that abstracts the implementation of a
* contract with ownership control features. It's commonly used in upgradable
* contracts and includes the functionality to get current owner, transfer
* ownership, and propose and accept ownership.
*/
interface IOwnable {
error NotOwner();
error InvalidOwner();
error InvalidOwnerAddress();
event OwnershipTransferStarted(address indexed newOwner);
event OwnershipTransferred(address indexed newOwner);
/**
* @notice Returns the current owner of the contract.
* @return address The address of the current owner
*/
function owner() external view returns (address);
/**
* @notice Returns the address of the pending owner of the contract.
* @return address The address of the pending owner
*/
function pendingOwner() external view returns (address);
/**
* @notice Transfers ownership of the contract to a new address
* @param newOwner The address to transfer ownership to
*/
function transferOwnership(address newOwner) external;
/**
* @notice Proposes to transfer the contract's ownership to a new address.
* The new owner needs to accept the ownership explicitly.
* @param newOwner The address to transfer ownership to
*/
function proposeOwnership(address newOwner) external;
/**
* @notice Transfers ownership to the pending owner.
* @dev Can only be called by the pending owner
*/
function acceptOwnership() external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import { IOwnable } from './IOwnable.sol';
import { IImplementation } from './IImplementation.sol';
// General interface for upgradable contracts
interface IUpgradable is IOwnable, IImplementation {
error InvalidCodeHash();
error InvalidImplementation();
error SetupFailed();
event Upgraded(address indexed newImplementation);
function implementation() external view returns (address);
function upgrade(
address newImplementation,
bytes32 newImplementationCodeHash,
bytes calldata params
) external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
error NativeTransferFailed();
/*
* @title SafeNativeTransfer
* @dev This library is used for performing safe native value transfers in Solidity by utilizing inline assembly.
*/
library SafeNativeTransfer {
/*
* @notice Perform a native transfer to a given address.
* @param receiver The recipient address to which the amount will be sent.
* @param amount The amount of native value to send.
* @throws NativeTransferFailed error if transfer is not successful.
*/
function safeNativeTransfer(address receiver, uint256 amount) internal {
bool success;
assembly {
success := call(gas(), receiver, amount, 0, 0, 0, 0)
}
if (!success) revert NativeTransferFailed();
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import { IERC20 } from '../interfaces/IERC20.sol';
error TokenTransferFailed();
/*
* @title SafeTokenCall
* @dev This library is used for performing safe token transfers.
*/
library SafeTokenCall {
/*
* @notice Make a safe call to a token contract.
* @param token The token contract to interact with.
* @param callData The function call data.
* @throws TokenTransferFailed error if transfer of token is not successful.
*/
function safeCall(IERC20 token, bytes memory callData) internal {
(bool success, bytes memory returnData) = address(token).call(callData);
bool transferred = success && (returnData.length == uint256(0) || abi.decode(returnData, (bool)));
if (!transferred || address(token).code.length == 0) revert TokenTransferFailed();
}
}
/*
* @title SafeTokenTransfer
* @dev This library safely transfers tokens from the contract to a recipient.
*/
library SafeTokenTransfer {
/*
* @notice Transfer tokens to a recipient.
* @param token The token contract.
* @param receiver The recipient of the tokens.
* @param amount The amount of tokens to transfer.
*/
function safeTransfer(
IERC20 token,
address receiver,
uint256 amount
) internal {
SafeTokenCall.safeCall(token, abi.encodeWithSelector(IERC20.transfer.selector, receiver, amount));
}
}
/*
* @title SafeTokenTransferFrom
* @dev This library helps to safely transfer tokens on behalf of a token holder.
*/
library SafeTokenTransferFrom {
/*
* @notice Transfer tokens on behalf of a token holder.
* @param token The token contract.
* @param from The address of the token holder.
* @param to The address the tokens are to be sent to.
* @param amount The amount of tokens to be transferred.
*/
function safeTransferFrom(
IERC20 token,
address from,
address to,
uint256 amount
) internal {
SafeTokenCall.safeCall(token, abi.encodeWithSelector(IERC20.transferFrom.selector, from, to, amount));
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @title GasEstimationType
* @notice This enum represents the gas estimation types for different chains.
*/
enum GasEstimationType {
Default,
OptimismEcotone,
OptimismBedrock,
Arbitrum,
Scroll
}
/**
* @title GasInfo
* @notice This struct represents the gas pricing information for a specific chain.
* @dev Smaller uint types are used for efficient struct packing to save storage costs.
*/
struct GasInfo {
/// @dev Custom gas pricing rule, such as L1 data fee on L2s
uint64 gasEstimationType;
/// @dev Scalar value needed for specific gas estimation types, expected to be less than 1e10
uint64 l1FeeScalar;
/// @dev Axelar base fee for cross-chain message approval on destination, in terms of source native gas token
uint128 axelarBaseFee;
/// @dev Gas price of destination chain, in terms of the source chain token, i.e dest_gas_price * dest_token_market_price / src_token_market_price
uint128 relativeGasPrice;
/// @dev Needed for specific gas estimation types. Blob base fee of destination chain, in terms of the source chain token, i.e dest_blob_base_fee * dest_token_market_price / src_token_market_price
uint128 relativeBlobBaseFee;
/// @dev Axelar express fee for express execution, in terms of source chain token
uint128 expressFee;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import { IImplementation } from '../interfaces/IImplementation.sol';
/**
* @title Implementation
* @notice This contract serves as a base for other contracts and enforces a proxy-first access restriction.
* @dev Derived contracts must implement the setup function.
*/
abstract contract Implementation is IImplementation {
address private immutable implementationAddress;
/**
* @dev Contract constructor that sets the implementation address to the address of this contract.
*/
constructor() {
implementationAddress = address(this);
}
/**
* @dev Modifier to require the caller to be the proxy contract.
* Reverts if the caller is the current contract (i.e., the implementation contract itself).
*/
modifier onlyProxy() {
if (implementationAddress == address(this)) revert NotProxy();
_;
}
/**
* @notice Initializes contract parameters.
* This function is intended to be overridden by derived contracts.
* The overriding function must have the onlyProxy modifier.
* @param params The parameters to be used for initialization
*/
function setup(bytes calldata params) external virtual;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import { IImplementation } from '../interfaces/IImplementation.sol';
import { IUpgradable } from '../interfaces/IUpgradable.sol';
import { Ownable } from '../utils/Ownable.sol';
import { Implementation } from './Implementation.sol';
/**
* @title Upgradable Contract
* @notice This contract provides an interface for upgradable smart contracts and includes the functionality to perform upgrades.
*/
abstract contract Upgradable is Ownable, Implementation, IUpgradable {
// bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1)
bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
/**
* @notice Constructor sets the implementation address to the address of the contract itself
* @dev This is used in the onlyProxy modifier to prevent certain functions from being called directly
* on the implementation contract itself.
* @dev The owner is initially set as address(1) because the actual owner is set within the proxy. It is not
* set as the zero address because Ownable is designed to throw an error for ownership transfers to the zero address.
*/
constructor() Ownable(address(1)) {}
/**
* @notice Returns the address of the current implementation
* @return implementation_ Address of the current implementation
*/
function implementation() public view returns (address implementation_) {
assembly {
implementation_ := sload(_IMPLEMENTATION_SLOT)
}
}
/**
* @notice Upgrades the contract to a new implementation
* @param newImplementation The address of the new implementation contract
* @param newImplementationCodeHash The codehash of the new implementation contract
* @param params Optional setup parameters for the new implementation contract
* @dev This function is only callable by the owner.
*/
function upgrade(
address newImplementation,
bytes32 newImplementationCodeHash,
bytes calldata params
) external override onlyOwner {
if (IUpgradable(newImplementation).contractId() != IUpgradable(implementation()).contractId())
revert InvalidImplementation();
if (newImplementationCodeHash != newImplementation.codehash) revert InvalidCodeHash();
assembly {
sstore(_IMPLEMENTATION_SLOT, newImplementation)
}
emit Upgraded(newImplementation);
if (params.length > 0) {
// slither-disable-next-line controlled-delegatecall
(bool success, ) = newImplementation.delegatecall(abi.encodeWithSelector(this.setup.selector, params));
if (!success) revert SetupFailed();
}
}
/**
* @notice Sets up the contract with initial data
* @param data Initialization data for the contract
* @dev This function is only callable by the proxy contract.
*/
function setup(bytes calldata data) external override(IImplementation, Implementation) onlyProxy {
_setup(data);
}
/**
* @notice Internal function to set up the contract with initial data
* @param data Initialization data for the contract
* @dev This function should be implemented in derived contracts.
*/
function _setup(bytes calldata data) internal virtual {}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import { IOwnable } from '../interfaces/IOwnable.sol';
/**
* @title Ownable
* @notice A contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* The owner account is set through ownership transfer. This module makes
* it possible to transfer the ownership of the contract to a new account in one
* step, as well as to an interim pending owner. In the second flow the ownership does not
* change until the pending owner accepts the ownership transfer.
*/
abstract contract Ownable is IOwnable {
// keccak256('owner')
bytes32 internal constant _OWNER_SLOT = 0x02016836a56b71f0d02689e69e326f4f4c1b9057164ef592671cf0d37c8040c0;
// keccak256('ownership-transfer')
bytes32 internal constant _OWNERSHIP_TRANSFER_SLOT =
0x9855384122b55936fbfb8ca5120e63c6537a1ac40caf6ae33502b3c5da8c87d1;
/**
* @notice Initializes the contract by transferring ownership to the owner parameter.
* @param _owner Address to set as the initial owner of the contract
*/
constructor(address _owner) {
_transferOwnership(_owner);
}
/**
* @notice Modifier that throws an error if called by any account other than the owner.
*/
modifier onlyOwner() {
if (owner() != msg.sender) revert NotOwner();
_;
}
/**
* @notice Returns the current owner of the contract.
* @return owner_ The current owner of the contract
*/
function owner() public view returns (address owner_) {
assembly {
owner_ := sload(_OWNER_SLOT)
}
}
/**
* @notice Returns the pending owner of the contract.
* @return owner_ The pending owner of the contract
*/
function pendingOwner() public view returns (address owner_) {
assembly {
owner_ := sload(_OWNERSHIP_TRANSFER_SLOT)
}
}
/**
* @notice Transfers ownership of the contract to a new account `newOwner`.
* @dev Can only be called by the current owner.
* @param newOwner The address to transfer ownership to
*/
function transferOwnership(address newOwner) external virtual onlyOwner {
_transferOwnership(newOwner);
}
/**
* @notice Propose to transfer ownership of the contract to a new account `newOwner`.
* @dev Can only be called by the current owner. The ownership does not change
* until the new owner accepts the ownership transfer.
* @param newOwner The address to transfer ownership to
*/
function proposeOwnership(address newOwner) external virtual onlyOwner {
if (newOwner == address(0)) revert InvalidOwnerAddress();
emit OwnershipTransferStarted(newOwner);
assembly {
sstore(_OWNERSHIP_TRANSFER_SLOT, newOwner)
}
}
/**
* @notice Accepts ownership of the contract.
* @dev Can only be called by the pending owner
*/
function acceptOwnership() external virtual {
address newOwner = pendingOwner();
if (newOwner != msg.sender) revert InvalidOwner();
_transferOwnership(newOwner);
}
/**
* @notice Internal function to transfer ownership of the contract to a new account `newOwner`.
* @dev Called in the constructor to set the initial owner.
* @param newOwner The address to transfer ownership to
*/
function _transferOwnership(address newOwner) internal virtual {
if (newOwner == address(0)) revert InvalidOwnerAddress();
emit OwnershipTransferred(newOwner);
assembly {
sstore(_OWNER_SLOT, newOwner)
sstore(_OWNERSHIP_TRANSFER_SLOT, 0)
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import { IERC20 } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IERC20.sol';
import { IAxelarGasService } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IAxelarGasService.sol';
import { InterchainGasEstimation, GasInfo } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/gas-estimation/InterchainGasEstimation.sol';
import { Upgradable } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/upgradable/Upgradable.sol';
import { SafeTokenTransfer, SafeTokenTransferFrom } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/libs/SafeTransfer.sol';
import { SafeNativeTransfer } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/libs/SafeNativeTransfer.sol';
/**
* @title AxelarGasService
* @notice This contract manages gas payments and refunds for cross-chain communication on the Axelar network.
* @dev The owner address of this contract should be the microservice that pays for gas.
* @dev Users pay gas for cross-chain calls, and the gasCollector can collect accumulated fees and/or refund users if needed.
*/
contract AxelarGasService is InterchainGasEstimation, Upgradable, IAxelarGasService {
using SafeTokenTransfer for IERC20;
using SafeTokenTransferFrom for IERC20;
using SafeNativeTransfer for address payable;
address public immutable gasCollector;
/**
* @notice Constructs the AxelarGasService contract.
* @param gasCollector_ The address of the gas collector
*/
constructor(address gasCollector_) {
gasCollector = gasCollector_;
}
/**
* @notice Modifier that ensures the caller is the designated gas collector.
*/
modifier onlyCollector() {
if (msg.sender != gasCollector) revert NotCollector();
_;
}
/**
* @notice Pay for gas for any type of contract execution on a destination chain.
* @dev This function is called on the source chain before calling the gateway to execute a remote contract.
* @dev If estimateOnChain is true, the function will estimate the gas cost and revert if the payment is insufficient.
* @param sender The address making the payment
* @param destinationChain The target chain where the contract call will be made
* @param destinationAddress The target address on the destination chain
* @param payload Data payload for the contract call
* @param executionGasLimit The gas limit for the contract call
* @param estimateOnChain Flag to enable on-chain gas estimation
* @param refundAddress The address where refunds, if any, should be sent
* @param params Additional parameters for gas payment
*/
function payGas(
address sender,
string calldata destinationChain,
string calldata destinationAddress,
bytes calldata payload,
uint256 executionGasLimit,
bool estimateOnChain,
address refundAddress,
bytes calldata params
) external payable override {
if (params.length > 0) {
revert InvalidParams();
}
if (estimateOnChain) {
uint256 gasEstimate = estimateGasFee(destinationChain, destinationAddress, payload, executionGasLimit, params);
if (gasEstimate > msg.value) {
revert InsufficientGasPayment(gasEstimate, msg.value);
}
}
emit NativeGasPaidForContractCall(sender, destinationChain, destinationAddress, keccak256(payload), msg.value, refundAddress);
}
/**
* @notice Pay for gas using ERC20 tokens for a contract call on a destination chain.
* @dev This function is called on the source chain before calling the gateway to execute a remote contract.
* @param sender The address making the payment
* @param destinationChain The target chain where the contract call will be made
* @param destinationAddress The target address on the destination chain
* @param payload Data payload for the contract call
* @param gasToken The address of the ERC20 token used to pay for gas
* @param gasFeeAmount The amount of tokens to pay for gas
* @param refundAddress The address where refunds, if any, should be sent
*/
function payGasForContractCall(
address sender,
string calldata destinationChain,
string calldata destinationAddress,
bytes calldata payload,
address gasToken,
uint256 gasFeeAmount,
address refundAddress
) external override {
emit GasPaidForContractCall(
sender,
destinationChain,
destinationAddress,
keccak256(payload),
gasToken,
gasFeeAmount,
refundAddress
);
IERC20(gasToken).safeTransferFrom(msg.sender, address(this), gasFeeAmount);
}
/**
* @notice Pay for gas using ERC20 tokens for a contract call with tokens on a destination chain.
* @dev This function is called on the source chain before calling the gateway to execute a remote contract.
* @param sender The address making the payment
* @param destinationChain The target chain where the contract call with tokens will be made
* @param destinationAddress The target address on the destination chain
* @param payload Data payload for the contract call with tokens
* @param symbol The symbol of the token to be sent with the call
* @param amount The amount of tokens to be sent with the call
* @param gasToken The address of the ERC20 token used to pay for gas
* @param gasFeeAmount The amount of tokens to pay for gas
* @param refundAddress The address where refunds, if any, should be sent
*/
function payGasForContractCallWithToken(
address sender,
string calldata destinationChain,
string calldata destinationAddress,
bytes calldata payload,
string memory symbol,
uint256 amount,
address gasToken,
uint256 gasFeeAmount,
address refundAddress
) external override {
emit GasPaidForContractCallWithToken(
sender,
destinationChain,
destinationAddress,
keccak256(payload),
symbol,
amount,
gasToken,
gasFeeAmount,
refundAddress
);
IERC20(gasToken).safeTransferFrom(msg.sender, address(this), gasFeeAmount);
}
/**
* @notice Pay for gas using native currency for a contract call on a destination chain.
* @dev This function is called on the source chain before calling the gateway to execute a remote contract.
* @param sender The address making the payment
* @param destinationChain The target chain where the contract call will be made
* @param destinationAddress The target address on the destination chain
* @param payload Data payload for the contract call
* @param refundAddress The address where refunds, if any, should be sent
*/
function payNativeGasForContractCall(
address sender,
string calldata destinationChain,
string calldata destinationAddress,
bytes calldata payload,
address refundAddress
) external payable override {
emit NativeGasPaidForContractCall(sender, destinationChain, destinationAddress, keccak256(payload), msg.value, refundAddress);
}
/**
* @notice Pay for gas using native currency for a contract call with tokens on a destination chain.
* @dev This function is called on the source chain before calling the gateway to execute a remote contract.
* @param sender The address making the payment
* @param destinationChain The target chain where the contract call with tokens will be made
* @param destinationAddress The target address on the destination chain
* @param payload Data payload for the contract call with tokens
* @param symbol The symbol of the token to be sent with the call
* @param amount The amount of tokens to be sent with the call
* @param refundAddress The address where refunds, if any, should be sent
*/
function payNativeGasForContractCallWithToken(
address sender,
string calldata destinationChain,
string calldata destinationAddress,
bytes calldata payload,
string calldata symbol,
uint256 amount,
address refundAddress
) external payable override {
emit NativeGasPaidForContractCallWithToken(
sender,
destinationChain,
destinationAddress,
keccak256(payload),
symbol,
amount,
msg.value,
refundAddress
);
}
/**
* @notice Pay for gas using ERC20 tokens for an express contract call on a destination chain.
* @dev This function is called on the source chain before calling the gateway to express execute a remote contract.
* @param sender The address making the payment
* @param destinationChain The target chain where the contract call will be made
* @param destinationAddress The target address on the destination chain
* @param payload Data payload for the contract call
* @param gasToken The address of the ERC20 token used to pay for gas
* @param gasFeeAmount The amount of tokens to pay for gas
* @param refundAddress The address where refunds, if any, should be sent
*/
function payGasForExpressCall(
address sender,
string calldata destinationChain,
string calldata destinationAddress,
bytes calldata payload,
address gasToken,
uint256 gasFeeAmount,
address refundAddress
) external override {
emit GasPaidForExpressCall(sender, destinationChain, destinationAddress, keccak256(payload), gasToken, gasFeeAmount, refundAddress);
IERC20(gasToken).safeTransferFrom(msg.sender, address(this), gasFeeAmount);
}
/**
* @notice Pay for gas using ERC20 tokens for an express contract call with tokens on a destination chain.
* @dev This function is called on the source chain before calling the gateway to express execute a remote contract.
* @param sender The address making the payment
* @param destinationChain The target chain where the contract call with tokens will be made
* @param destinationAddress The target address on the destination chain
* @param payload Data payload for the contract call with tokens
* @param symbol The symbol of the token to be sent with the call
* @param amount The amount of tokens to be sent with the call
* @param gasToken The address of the ERC20 token used to pay for gas
* @param gasFeeAmount The amount of tokens to pay for gas
* @param refundAddress The address where refunds, if any, should be sent
*/
function payGasForExpressCallWithToken(
address sender,
string calldata destinationChain,
string calldata destinationAddress,
bytes calldata payload,
string memory symbol,
uint256 amount,
address gasToken,
uint256 gasFeeAmount,
address refundAddress
) external override {
emit GasPaidForExpressCallWithToken(
sender,
destinationChain,
destinationAddress,
keccak256(payload),
symbol,
amount,
gasToken,
gasFeeAmount,
refundAddress
);
IERC20(gasToken).safeTransferFrom(msg.sender, address(this), gasFeeAmount);
}
/**
* @notice Pay for gas using native currency for an express contract call on a destination chain.
* @dev This function is called on the source chain before calling the gateway to execute a remote contract.
* @param sender The address making the payment
* @param destinationChain The target chain where the contract call will be made
* @param destinationAddress The target address on the destination chain
* @param payload Data payload for the contract call
* @param refundAddress The address where refunds, if any, should be sent
*/
function payNativeGasForExpressCall(
address sender,
string calldata destinationChain,
string calldata destinationAddress,
bytes calldata payload,
address refundAddress
) external payable override {
emit NativeGasPaidForExpressCall(sender, destinationChain, destinationAddress, keccak256(payload), msg.value, refundAddress);
}
/**
* @notice Pay for gas using native currency for an express contract call with tokens on a destination chain.
* @dev This function is called on the source chain before calling the gateway to execute a remote contract.
* @param sender The address making the payment
* @param destinationChain The target chain where the contract call with tokens will be made
* @param destinationAddress The target address on the destination chain
* @param payload Data payload for the contract call with tokens
* @param symbol The symbol of the token to be sent with the call
* @param amount The amount of tokens to be sent with the call
* @param refundAddress The address where refunds, if any, should be sent
*/
function payNativeGasForExpressCallWithToken(
address sender,
string calldata destinationChain,
string calldata destinationAddress,
bytes calldata payload,
string calldata symbol,
uint256 amount,
address refundAddress
) external payable override {
emit NativeGasPaidForExpressCallWithToken(
sender,
destinationChain,
destinationAddress,
keccak256(payload),
symbol,
amount,
msg.value,
refundAddress
);
}
/**
* @notice Add additional gas payment using ERC20 tokens after initiating a cross-chain call.
* @dev This function can be called on the source chain after calling the gateway to execute a remote contract.
* @param txHash The transaction hash of the cross-chain call
* @param logIndex The log index for the cross-chain call
* @param gasToken The ERC20 token address used to add gas
* @param gasFeeAmount The amount of tokens to add as gas
* @param refundAddress The address where refunds, if any, should be sent
*/
function addGas(
bytes32 txHash,
uint256 logIndex,
address gasToken,
uint256 gasFeeAmount,
address refundAddress
) external override {
emit GasAdded(txHash, logIndex, gasToken, gasFeeAmount, refundAddress);
IERC20(gasToken).safeTransferFrom(msg.sender, address(this), gasFeeAmount);
}
/**
* @notice Add additional gas payment using native currency after initiating a cross-chain call.
* @dev This function can be called on the source chain after calling the gateway to execute a remote contract.
* @param txHash The transaction hash of the cross-chain call
* @param logIndex The log index for the cross-chain call
* @param refundAddress The address where refunds, if any, should be sent
*/
function addNativeGas(
bytes32 txHash,
uint256 logIndex,
address refundAddress
) external payable override {
emit NativeGasAdded(txHash, logIndex, msg.value, refundAddress);
}
/**
* @notice Add additional gas payment using ERC20 tokens after initiating an express cross-chain call.
* @dev This function can be called on the source chain after calling the gateway to express execute a remote contract.
* @param txHash The transaction hash of the cross-chain call
* @param logIndex The log index for the cross-chain call
* @param gasToken The ERC20 token address used to add gas
* @param gasFeeAmount The amount of tokens to add as gas
* @param refundAddress The address where refunds, if any, should be sent
*/
function addExpressGas(
bytes32 txHash,
uint256 logIndex,
address gasToken,
uint256 gasFeeAmount,
address refundAddress
) external override {
emit ExpressGasAdded(txHash, logIndex, gasToken, gasFeeAmount, refundAddress);
IERC20(gasToken).safeTransferFrom(msg.sender, address(this), gasFeeAmount);
}
/**
* @notice Add additional gas payment using native currency after initiating an express cross-chain call.
* @dev This function can be called on the source chain after calling the gateway to express execute a remote contract.
* @param txHash The transaction hash of the cross-chain call
* @param logIndex The log index for the cross-chain call
* @param refundAddress The address where refunds, if any, should be sent
*/
function addNativeExpressGas(
bytes32 txHash,
uint256 logIndex,
address refundAddress
) external payable override {
emit NativeExpressGasAdded(txHash, logIndex, msg.value, refundAddress);
}
/**
* @notice Updates the gas price for a specific chain.
* @dev This function is called by the gas oracle to update the gas prices for a specific chains.
* @param chains Array of chain names
* @param gasUpdates Array of gas updates
*/
function updateGasInfo(string[] calldata chains, GasInfo[] calldata gasUpdates) external onlyCollector {
uint256 chainsLength = chains.length;
if (chainsLength != gasUpdates.length) revert InvalidGasUpdates();
for (uint256 i; i < chainsLength; i++) {
string calldata chain = chains[i];
GasInfo calldata gasUpdate = gasUpdates[i];
_setGasInfo(chain, gasUpdate);
}
}
/**
* @notice Allows the gasCollector to collect accumulated fees from the contract.
* @dev Use address(0) as the token address for native currency.
* @param receiver The address to receive the collected fees
* @param tokens Array of token addresses to be collected
* @param amounts Array of amounts to be collected for each respective token address
*/
function collectFees(
address payable receiver,
address[] calldata tokens,
uint256[] calldata amounts
) external onlyCollector {
if (receiver == address(0)) revert InvalidAddress();
uint256 tokensLength = tokens.length;
if (tokensLength != amounts.length) revert InvalidAmounts();
for (uint256 i; i < tokensLength; i++) {
address token = tokens[i];
uint256 amount = amounts[i];
if (amount == 0) revert InvalidAmounts();
if (token == address(0)) {
if (amount <= address(this).balance) receiver.safeNativeTransfer(amount);
} else {
// slither-disable-next-line calls-loop
if (amount <= IERC20(token).balanceOf(address(this))) IERC20(token).safeTransfer(receiver, amount);
}
}
}
/**
* @dev Deprecated refund function, kept for backward compatibility.
*/
function refund(
address payable receiver,
address token,
uint256 amount
) external onlyCollector {
_refund(bytes32(0), 0, receiver, token, amount);
}
/**
* @notice Refunds gas payment to the receiver in relation to a specific cross-chain transaction.
* @dev Only callable by the gasCollector.
* @dev Use address(0) as the token address to refund native currency.
* @param txHash The transaction hash of the cross-chain call
* @param logIndex The log index for the cross-chain call
* @param receiver The address to receive the refund
* @param token The token address to be refunded
* @param amount The amount to refund
*/
function refund(
bytes32 txHash,
uint256 logIndex,
address payable receiver,
address token,
uint256 amount
) external onlyCollector {
_refund(txHash, logIndex, receiver, token, amount);
}
/**
* @dev Internal function to implement gas refund logic.
*/
function _refund(
bytes32 txHash,
uint256 logIndex,
address payable receiver,
address token,
uint256 amount
) private {
if (receiver == address(0)) revert InvalidAddress();
emit Refunded(txHash, logIndex, receiver, token, amount);
if (token == address(0)) {
receiver.safeNativeTransfer(amount);
} else {
IERC20(token).safeTransfer(receiver, amount);
}
}
/**
* @notice Returns a unique identifier for the contract.
* @return bytes32 Hash of the contract identifier
*/
function contractId() external pure returns (bytes32) {
return keccak256('axelar-gas-service');
}
}