Transaction Hash:
Block:
24508722 at Feb-22-2026 12:08:11 AM +UTC
Transaction Fee:
0.000021570746850257 ETH
$0.04
Gas Used:
341,791 Gas / 0.063110927 Gwei
Emitted Events:
| 220 |
0x4f4495243837681061c4743b74b3eedf548d56a5.0x9991faa1f435675159ffae64b66d7ecfdb55c29755869a18db8497b4392347e0( 0x9991faa1f435675159ffae64b66d7ecfdb55c29755869a18db8497b4392347e0, 0x3bb55ec3f284afb1bab7f062855a0e6311c08182812195190cc563c1b38583cd, 0x000000000000000000000000ce16f69375520ab01377ce7b88f5ba8c48f8d666, 0xd3f86ec380012c9f05c3141b2d060c29a88e0c38a743292487aa5c8316a725c6, 00000000000000000000000000000000000000000000000000000000000000c0, 0000000000000000000000000000000000000000000000000000000000000100, 0000000000000000000000000000000000000000000000000000000000000160, 000000000000000000000000000000000000000000000000000000000112e6df, 57284fa896b2bb5b9b3c2595e34147018d3f1fedad44da4137c5a7f5f3e3bc8d, 00000000000000000000000000000000000000000000000000000000004caf05, 0000000000000000000000000000000000000000000000000000000000000007, 6f736d6f73697300000000000000000000000000000000000000000000000000, 000000000000000000000000000000000000000000000000000000000000003f, 6f736d6f316e366e657939747366353565747a396e726d7a7964387761376536, 3471643373303661373466717333306b61387070733663767174737963723600, 0000000000000000000000000000000000000000000000000000000000000004, 5553444300000000000000000000000000000000000000000000000000000000 )
|
| 221 |
0x4f4495243837681061c4743b74b3eedf548d56a5.0xa74c8847d513feba22a0f0cb38d53081abf97562cdb293926ba243689e7c41ca( 0xa74c8847d513feba22a0f0cb38d53081abf97562cdb293926ba243689e7c41ca, 0x3bb55ec3f284afb1bab7f062855a0e6311c08182812195190cc563c1b38583cd )
|
Account State Difference:
| Address | Before | After | State Difference | ||
|---|---|---|---|---|---|
| 0x2102C32A...a185bDa43 | (Axelar: Relayer 2) |
7.005937478372419691 Eth
Nonce: 46287
|
7.005915907625569434 Eth
Nonce: 46288
| 0.000021570746850257 | |
|
0x4838B106...B0BAD5f97
Miner
| (Titan Builder) | 13.073578541847078021 Eth | 13.073588795577078021 Eth | 0.00001025373 | |
| 0x4F449524...f548D56A5 | (Axelar: Gateway) |
Execution Trace
Axelar: Gateway.09c5eabe( )
AxelarGateway.execute( input=0x000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000003E000000000000000000000000000000000000000000000000000000000000003800000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000C0000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000013BB55EC3F284AFB1BAB7F062855A0E6311C08182812195190CC563C1B38583CD00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001B617070726F7665436F6E747261637443616C6C576974684D696E7400000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001E000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000CE16F69375520AB01377CE7B88F5BA8C48F8D666D3F86EC380012C9F05C3141B2D060C29A88E0C38A743292487AA5C8316A725C600000000000000000000000000000000000000000000000000000000000001A0000000000000000000000000000000000000000000000000000000000112E6DF57284FA896B2BB5B9B3C2595E34147018D3F1FEDAD44DA4137C5A7F5F3E3BC8D00000000000000000000000000000000000000000000000000000000004CAF0500000000000000000000000000000000000000000000000000000000000000076F736D6F73697300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003F6F736D6F316E366E657939747366353565747A396E726D7A79643877613765363471643373303661373466717333306B61387070733663767174737963723600000000000000000000000000000000000000000000000000000000000000000455534443000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001DE00000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000070000000000000000000000000000000000000000000000000000000000000135CE0000000000000000000000000000000000000000000000000000000000000D800000000000000000000000000000000000000000000000000000000000000033000000000000000000000000021D936762FDE8C622EC526E91408E9CF33B525600000000000000000000000003FE99104A9532F2727DB3AE31FD0CEB1188EFD00000000000000000000000000BF5AE5C4944A0E04D905B9F3BC9773C379EFC76000000000000000000000000123042337808EFC01C1AD7E506265F6F3756DC1900000000000000000000000013C1B3365B323FA2A4A0EAB145F98781A467521700000000000000000000000018E83FBA9969A4365CDE27F0CB30A61C9BBC467300000000000000000000000020B025C80D90C4CBD8F96A4852E8D986946DA60E00000000000000000000000021BDF788ED9A267B80280BEF415CC9C6A77AECFD0000000000000000000000002C2981DAF35371A38580EB685CB8F5137643434C0000000000000000000000002DF880D8CE5CE9CD8794187A822CED334A413B5D000000000000000000000000368E5E728DC48D13594F0D33334D9C283735BFCB0000000000000000000000003743F0BD81E54405C3273112C59DA102F702DAE40000000000000000000000003C549AD7A10887BF6C562512738713E7A96411DD00000000000000000000000045A83A274072E2BC99EAF2E80DA94E6F2C5D7DA6000000000000000000000000470D96B3513D6C63CD72C74F3681CE74B9FC639400000000000000000000000047507803BE195917EF141AB7B3AFA6D623535A32000000000000000000000000491FF43DE09F2B80CA92DF31BDF13855B0F4589C0000000000000000000000005222D090E999BB4DF82930EE233C02A3B8B4EB56000000000000000000000000540C87A42334DD6219C0AC7869F1D7C41BD3275400000000000000000000000055BEB9723BD5BC7226ED2E6D4F69476E65A0F7FA00000000000000000000000061946E5B9B8A231185937688E6AC232773FF8F000000000000000000000000006AF295171A45431325FC303F099782FA9526E0E60000000000000000000000006DD7998D0324D41B56DEE6215D588BD53F81828E000000000000000000000000737BFE757AD5A7A3451ABEBB612C3FE82286CDD3000000000000000000000000761E62677645DF454690F2FF7FBEABCB9C1184110000000000000000000000007B9B3D251E63E6A2937149BB93E7DC5E19C279DD0000000000000000000000007FFE9724BBDECE33E76D93C61A7EEB62AEF93BEF00000000000000000000000082E916A86F9AF6F58A65695D4378D4668AA367E70000000000000000000000008333AD83E30C3B7F969607994FC503E04EAEC615000000000000000000000000846A30460057EB4937369739C7BA5EE53041CDFF0000000000000000000000008CBDEB8E33676AC67222E791512C8E48E32EFED30000000000000000000000008CD3E02A4A3E26FC6416B8AEEBF71DD54FC8DB4200000000000000000000000094A847553D021DCF48A347CE1BBAA07F52C61D880000000000000000000000009539BAE27FC438841FC9239D769C051726B111580000000000000000000000009F27636B753A5FB7E710D28E336D087746C45633000000000000000000000000A08AFBE0249F85EA9EE03FE959155CCECA4C7914000000000000000000000000A6D985FD129440C87237E599D1F3EC4BA4AE51EC000000000000000000000000A8894919703FB09F295B85261FA3F87E02ABB57F000000000000000000000000AC380E34CF1E4AB0828900FFD91C184F8546CCEC000000000000000000000000B51B532603E57B829924F5B8696476153D30C9E8000000000000000000000000B8D60504ABEB077966E81B7936961C00736EB1FE000000000000000000000000C14385B5EA7ED5FE6F2695134D86C9772A520663000000000000000000000000C8998EF7B6D7B078A436F1CE0901520F5DB1464E000000000000000000000000D262A00AE2634FB38BE42378D140BA0A0FBB95C5000000000000000000000000EAB6E4115CE343CCCD6BD86E9CA03A7A2891E4CD000000000000000000000000EFB62752DA4F2D9FEC4E1911AF01B808264052AA000000000000000000000000F023857C179161F7D43AA2F9A3DF88E099B22280000000000000000000000000F04FC428343330869B7C9CC30A27CD494A83B869000000000000000000000000F2043DF655AD6E0291216656E598157025A8A7E9000000000000000000000000F75D8553954C360FA0BEFC9CBFC71E7D7D4BE139000000000000000000000000FC85C30E2381C86772B1A23F9BFB86C3B161683C0000000000000000000000000000000000000000000000000000000000000033000000000000000000000000000000000000000000000000000000000000090E00000000000000000000000000000000000000000000000000000000000008C7000000000000000000000000000000000000000000000000000000000000003A0000000000000000000000000000000000000000000000000000000000000B520000000000000000000000000000000000000000000000000000000000000AE50000000000000000000000000000000000000000000000000000000000000B13000000000000000000000000000000000000000000000000000000000000099A00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000A7C0000000000000000000000000000000000000000000000000000000000000AEA0000000000000000000000000000000000000000000000000000000000000A490000000000000000000000000000000000000000000000000000000000000C3C00000000000000000000000000000000000000000000000000000000000005850000000000000000000000000000000000000000000000000000000000000F0B0000000000000000000000000000000000000000000000000000000000000BA500000000000000000000000000000000000000000000000000000000000008BC000000000000000000000000000000000000000000000000000000000000034F0000000000000000000000000000000000000000000000000000000000000B170000000000000000000000000000000000000000000000000000000000000B1900000000000000000000000000000000000000000000000000000000000009300000000000000000000000000000000000000000000000000000000000000ADC0000000000000000000000000000000000000000000000000000000000000F7500000000000000000000000000000000000000000000000000000000000009BF0000000000000000000000000000000000000000000000000000000000000AC6000000000000000000000000000000000000000000000000000000000000004800000000000000000000000000000000000000000000000000000000000008D00000000000000000000000000000000000000000000000000000000000000B740000000000000000000000000000000000000000000000000000000000000B750000000000000000000000000000000000000000000000000000000000000AB2000000000000000000000000000000000000000000000000000000000000091700000000000000000000000000000000000000000000000000000000000008C40000000000000000000000000000000000000000000000000000000000000DBC000000000000000000000000000000000000000000000000000000000000056E0000000000000000000000000000000000000000000000000000000000000DAE0000000000000000000000000000000000000000000000000000000000000B18000000000000000000000000000000000000000000000000000000000000087D0000000000000000000000000000000000000000000000000000000000000B6F0000000000000000000000000000000000000000000000000000000000000B2000000000000000000000000000000000000000000000000000000000000008120000000000000000000000000000000000000000000000000000000000000AB60000000000000000000000000000000000000000000000000000000000000F700000000000000000000000000000000000000000000000000000000000000D1E00000000000000000000000000000000000000000000000000000000000000BF0000000000000000000000000000000000000000000000000000000000000D060000000000000000000000000000000000000000000000000000000000000A580000000000000000000000000000000000000000000000000000000000000B5C0000000000000000000000000000000000000000000000000000000000000AB300000000000000000000000000000000000000000000000000000000000000CF0000000000000000000000000000000000000000000000000000000000000B120000000000000000000000000000000000000000000000000000000000000EBE0000000000000000000000000000000000000000000000000000000000000ABC000000000000000000000000000000000000000000000000000000000000001A000000000000000000000000000000000000000000000000000000000000034000000000000000000000000000000000000000000000000000000000000003C0000000000000000000000000000000000000000000000000000000000000044000000000000000000000000000000000000000000000000000000000000004C0000000000000000000000000000000000000000000000000000000000000054000000000000000000000000000000000000000000000000000000000000005C0000000000000000000000000000000000000000000000000000000000000064000000000000000000000000000000000000000000000000000000000000006C0000000000000000000000000000000000000000000000000000000000000074000000000000000000000000000000000000000000000000000000000000007C0000000000000000000000000000000000000000000000000000000000000084000000000000000000000000000000000000000000000000000000000000008C0000000000000000000000000000000000000000000000000000000000000094000000000000000000000000000000000000000000000000000000000000009C00000000000000000000000000000000000000000000000000000000000000A400000000000000000000000000000000000000000000000000000000000000AC00000000000000000000000000000000000000000000000000000000000000B400000000000000000000000000000000000000000000000000000000000000BC00000000000000000000000000000000000000000000000000000000000000C400000000000000000000000000000000000000000000000000000000000000CC00000000000000000000000000000000000000000000000000000000000000D400000000000000000000000000000000000000000000000000000000000000DC00000000000000000000000000000000000000000000000000000000000000E400000000000000000000000000000000000000000000000000000000000000EC00000000000000000000000000000000000000000000000000000000000000F400000000000000000000000000000000000000000000000000000000000000FC000000000000000000000000000000000000000000000000000000000000000410CA2FA0B6C6651E0B4665A7EB8C4037D255CF129BAEFF090B056EF5F67D47DD37120A8B311B7096FBE158C93B47194CA09DAB72B6CAC1513A7D490CFEEAE247A1B000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041471DAA16525F41EECB9E61AE99223040BA37545A1EA96DDA773E6BB8C5A61B5464DDDB4E20EC3949B998200241DE9C49FB8045723B81BC78CC1FCBA9323ABD3D1C0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000419067FB85D0C3E267F6D2FD61BD5085CD8846F38C3D32A5FD0B4C933F7264310208DB54AB2C078302EFABF9FAAA994F50AEC7DCD40F1D3B76205833F9EC89F5C51B0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000419DD2D127717002EA2DC3A24DCB453725868BF777DD95EB9D819A82D884A3683A4269DEAE28C59B5486952972816BBD839D5C5A8203981042A45F81B6D4E7AB8D1B0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000418CD6BC6C41F0D9A359E5ED006BB882168F46AAE3961EA52BD847DF222563EB6E4CEBCCFF59C368FDDC1B02C7A9FB57357E85309101CEF37B24FD233A9DC2CD951C000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041B026E17268496BCD7E0E90DFA353C632830F315F49D71B17EFFE60122FAD8C717205C959BD739898B77BFB7B5F01ACEFF225D571B13CCB7930DDC918DBF8E76A1B000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041BDBB56293271E82E2DDF84C9CAA10AE8138CA0DF93CD8FEC9B2A695CC75316C427F562BA3AD768F1FAD579BF399A2E7A026ADE257C4F88E7FC05D7F5D4BAA7271C000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041765B7EDAAF3D91CA456A7040EEAB9A122465CD163DE4C555F90CF180F18DC2592C675D07CD38ED07E3957EE2F27A8F0FF590F9D2474C6CBD94D18EB0CBA574531B000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041753A3323E4F6CCAA4BB0BAF40281043AC90BE1470AF1FECC90356595592F869203AF7A3F5527E09A0B99E82A19D913EC693E4591BEB204C9E9845339869C50111B0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000413D33EB329CE91ECC31FE2C590DA36B08799A3629B253B563259AF136D5B030D1558C5E6B05675BDF7550ADF4021E7C0295AF605F5AE3293FD5199BE55D58E2B61C000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041EDB75D628115598F05DC4163064613231DC433637426435783AA1A8F670970337630902AAA174EADC9B3844987BC79C8B10BC6E980417A1FF178B4AA5B2BA74E1C000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041A8D6BE332CEE8F49179B1F541AE5736E24CABD52C5BDB2806B140C4C471A797A46D385DB66F31150C2DF589A0D1ED7EDE3FD31BB67EA67F2B2BB8B2F8C28C0261B000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041C32AFA4116A29CB779E97F12F49B0822F7EC196902830B87917949C1B1F78034321DA225A22AC27D111B42DB1005A45ABF6FCE0E444356BE90EAE6F51EF8D4981B000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041CA79B641C30D9DB1C60DCE0FD9D9BE7E32C90598568CAEA741B906F85DE1098C4B8CEB52784D573B50FA0DB64EAE0CB4DAE3CD71F1403E746AB60F246BAA37571B0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000415CA6593BE8E2825708D87465C1258C1D9A30676A0A3FD1E060A516933AEFF8C46B2610B0890A8BE23EFB3D9A3F77D23D40001B71A844E280D9CAC544D587E0621C000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041FA663E9713E92A0200A06F85B3F8CB7B5EEDC66D03D26F3B261BF5AE28A0125F6AD1CF590BEE65ACD381FE80175502B96E2ECCEB869285DCAA5D48B918E5DF611C0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000415E93E331150D74E4F52D313C2C319D601B40B048CC61D4596DADD25623CA2FAA46BB4B032BB61D27B1327D1BB1BAC1624361005400D8F2D7302D19DA443A4CF71C0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000411BE78602E005563C59658F690EBC1B87D82C8E39696848F50F8F60DD9DE67ABF245A6EC483E86631601650045D7A76732DBA5382DA27991850E4539949DE6C321C000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041B363631B9DB1CA6F15C50D8DDB5B37B6E8893D5ECA4B1A86DDB5F9C7EA2E52F63952187B8DCABA0AB5EC8AC4C68CBE17AE3F7AD91584C65C27D3CFCD3135D5D91B000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041FC55F600C048EFCCC2EA798D260197B1C393456028998CED16CBBFBF1B37F37E46F4E4FC0B5D57BDD41C751364F041FC3E0C1B38BCAF3DAAB28D5EE1DAC207A91B0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000410D80CEFA91265C781E9D0B2D30CE28150FCB6C2D454A638B240BDBD72BF5E92E3884FC1111AF437A7CE17E458E35686B7EAFD22CB6E7F94C37A3A971F7EA023B1B0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000417005430F31A9C252B9A6A1FD529B6686150F1B1F4DB8D8B0E7107EDB83F7FFD278C41924C673EB86F3BC1E1774CAFB2DEAC18C2AE56AFB88E250F302B148669B1C00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004175D64F85BB5850DF8F7ECDAADD5FBEFDB2762403EA8C58DB158C591441B50CA515D2ED000B648135C7DAF55C7AEC5670981B872D97252157583594F5848BD6D61B000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041961BD26E9C9D015769509B38E7FA347CD4AD60CF1B5EA9864E77DFFC7A0ABC447CDD16C41DF4284D3153798ADB136D519CF4C92D8C3177806BCAA454068535131B0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000419E2D6C8D56169E35C4EDF8A0234301AA4F5F2F1B185E23FF54E66F126161385978C8972E19A326CD0298536ABD46323A54B1B4F94202A7C699D58F7C920FE54D1B0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000413016B94A10C4368BE9BE782104CB490CEB84F6644F3ADE773661CF465BF72A3B64EEFF82C73F1C0D37A4D0B5C0827C65B2F1769444FED45FE6F4954D302B0B251B00000000000000000000000000000000000000000000000000000000000000 )AxelarAuthWeighted.validateProof( messageHash=81365954DE0E254174365D05139FDC9525D0C73A06C959ACD7E8A5E5EBE83EE4, proof=0x0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000070000000000000000000000000000000000000000000000000000000000000135CE0000000000000000000000000000000000000000000000000000000000000D800000000000000000000000000000000000000000000000000000000000000033000000000000000000000000021D936762FDE8C622EC526E91408E9CF33B525600000000000000000000000003FE99104A9532F2727DB3AE31FD0CEB1188EFD00000000000000000000000000BF5AE5C4944A0E04D905B9F3BC9773C379EFC76000000000000000000000000123042337808EFC01C1AD7E506265F6F3756DC1900000000000000000000000013C1B3365B323FA2A4A0EAB145F98781A467521700000000000000000000000018E83FBA9969A4365CDE27F0CB30A61C9BBC467300000000000000000000000020B025C80D90C4CBD8F96A4852E8D986946DA60E00000000000000000000000021BDF788ED9A267B80280BEF415CC9C6A77AECFD0000000000000000000000002C2981DAF35371A38580EB685CB8F5137643434C0000000000000000000000002DF880D8CE5CE9CD8794187A822CED334A413B5D000000000000000000000000368E5E728DC48D13594F0D33334D9C283735BFCB0000000000000000000000003743F0BD81E54405C3273112C59DA102F702DAE40000000000000000000000003C549AD7A10887BF6C562512738713E7A96411DD00000000000000000000000045A83A274072E2BC99EAF2E80DA94E6F2C5D7DA6000000000000000000000000470D96B3513D6C63CD72C74F3681CE74B9FC639400000000000000000000000047507803BE195917EF141AB7B3AFA6D623535A32000000000000000000000000491FF43DE09F2B80CA92DF31BDF13855B0F4589C0000000000000000000000005222D090E999BB4DF82930EE233C02A3B8B4EB56000000000000000000000000540C87A42334DD6219C0AC7869F1D7C41BD3275400000000000000000000000055BEB9723BD5BC7226ED2E6D4F69476E65A0F7FA00000000000000000000000061946E5B9B8A231185937688E6AC232773FF8F000000000000000000000000006AF295171A45431325FC303F099782FA9526E0E60000000000000000000000006DD7998D0324D41B56DEE6215D588BD53F81828E000000000000000000000000737BFE757AD5A7A3451ABEBB612C3FE82286CDD3000000000000000000000000761E62677645DF454690F2FF7FBEABCB9C1184110000000000000000000000007B9B3D251E63E6A2937149BB93E7DC5E19C279DD0000000000000000000000007FFE9724BBDECE33E76D93C61A7EEB62AEF93BEF00000000000000000000000082E916A86F9AF6F58A65695D4378D4668AA367E70000000000000000000000008333AD83E30C3B7F969607994FC503E04EAEC615000000000000000000000000846A30460057EB4937369739C7BA5EE53041CDFF0000000000000000000000008CBDEB8E33676AC67222E791512C8E48E32EFED30000000000000000000000008CD3E02A4A3E26FC6416B8AEEBF71DD54FC8DB4200000000000000000000000094A847553D021DCF48A347CE1BBAA07F52C61D880000000000000000000000009539BAE27FC438841FC9239D769C051726B111580000000000000000000000009F27636B753A5FB7E710D28E336D087746C45633000000000000000000000000A08AFBE0249F85EA9EE03FE959155CCECA4C7914000000000000000000000000A6D985FD129440C87237E599D1F3EC4BA4AE51EC000000000000000000000000A8894919703FB09F295B85261FA3F87E02ABB57F000000000000000000000000AC380E34CF1E4AB0828900FFD91C184F8546CCEC000000000000000000000000B51B532603E57B829924F5B8696476153D30C9E8000000000000000000000000B8D60504ABEB077966E81B7936961C00736EB1FE000000000000000000000000C14385B5EA7ED5FE6F2695134D86C9772A520663000000000000000000000000C8998EF7B6D7B078A436F1CE0901520F5DB1464E000000000000000000000000D262A00AE2634FB38BE42378D140BA0A0FBB95C5000000000000000000000000EAB6E4115CE343CCCD6BD86E9CA03A7A2891E4CD000000000000000000000000EFB62752DA4F2D9FEC4E1911AF01B808264052AA000000000000000000000000F023857C179161F7D43AA2F9A3DF88E099B22280000000000000000000000000F04FC428343330869B7C9CC30A27CD494A83B869000000000000000000000000F2043DF655AD6E0291216656E598157025A8A7E9000000000000000000000000F75D8553954C360FA0BEFC9CBFC71E7D7D4BE139000000000000000000000000FC85C30E2381C86772B1A23F9BFB86C3B161683C0000000000000000000000000000000000000000000000000000000000000033000000000000000000000000000000000000000000000000000000000000090E00000000000000000000000000000000000000000000000000000000000008C7000000000000000000000000000000000000000000000000000000000000003A0000000000000000000000000000000000000000000000000000000000000B520000000000000000000000000000000000000000000000000000000000000AE50000000000000000000000000000000000000000000000000000000000000B13000000000000000000000000000000000000000000000000000000000000099A00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000A7C0000000000000000000000000000000000000000000000000000000000000AEA0000000000000000000000000000000000000000000000000000000000000A490000000000000000000000000000000000000000000000000000000000000C3C00000000000000000000000000000000000000000000000000000000000005850000000000000000000000000000000000000000000000000000000000000F0B0000000000000000000000000000000000000000000000000000000000000BA500000000000000000000000000000000000000000000000000000000000008BC000000000000000000000000000000000000000000000000000000000000034F0000000000000000000000000000000000000000000000000000000000000B170000000000000000000000000000000000000000000000000000000000000B1900000000000000000000000000000000000000000000000000000000000009300000000000000000000000000000000000000000000000000000000000000ADC0000000000000000000000000000000000000000000000000000000000000F7500000000000000000000000000000000000000000000000000000000000009BF0000000000000000000000000000000000000000000000000000000000000AC6000000000000000000000000000000000000000000000000000000000000004800000000000000000000000000000000000000000000000000000000000008D00000000000000000000000000000000000000000000000000000000000000B740000000000000000000000000000000000000000000000000000000000000B750000000000000000000000000000000000000000000000000000000000000AB2000000000000000000000000000000000000000000000000000000000000091700000000000000000000000000000000000000000000000000000000000008C40000000000000000000000000000000000000000000000000000000000000DBC000000000000000000000000000000000000000000000000000000000000056E0000000000000000000000000000000000000000000000000000000000000DAE0000000000000000000000000000000000000000000000000000000000000B18000000000000000000000000000000000000000000000000000000000000087D0000000000000000000000000000000000000000000000000000000000000B6F0000000000000000000000000000000000000000000000000000000000000B2000000000000000000000000000000000000000000000000000000000000008120000000000000000000000000000000000000000000000000000000000000AB60000000000000000000000000000000000000000000000000000000000000F700000000000000000000000000000000000000000000000000000000000000D1E00000000000000000000000000000000000000000000000000000000000000BF0000000000000000000000000000000000000000000000000000000000000D060000000000000000000000000000000000000000000000000000000000000A580000000000000000000000000000000000000000000000000000000000000B5C0000000000000000000000000000000000000000000000000000000000000AB300000000000000000000000000000000000000000000000000000000000000CF0000000000000000000000000000000000000000000000000000000000000B120000000000000000000000000000000000000000000000000000000000000EBE0000000000000000000000000000000000000000000000000000000000000ABC000000000000000000000000000000000000000000000000000000000000001A000000000000000000000000000000000000000000000000000000000000034000000000000000000000000000000000000000000000000000000000000003C0000000000000000000000000000000000000000000000000000000000000044000000000000000000000000000000000000000000000000000000000000004C0000000000000000000000000000000000000000000000000000000000000054000000000000000000000000000000000000000000000000000000000000005C0000000000000000000000000000000000000000000000000000000000000064000000000000000000000000000000000000000000000000000000000000006C0000000000000000000000000000000000000000000000000000000000000074000000000000000000000000000000000000000000000000000000000000007C0000000000000000000000000000000000000000000000000000000000000084000000000000000000000000000000000000000000000000000000000000008C0000000000000000000000000000000000000000000000000000000000000094000000000000000000000000000000000000000000000000000000000000009C00000000000000000000000000000000000000000000000000000000000000A400000000000000000000000000000000000000000000000000000000000000AC00000000000000000000000000000000000000000000000000000000000000B400000000000000000000000000000000000000000000000000000000000000BC00000000000000000000000000000000000000000000000000000000000000C400000000000000000000000000000000000000000000000000000000000000CC00000000000000000000000000000000000000000000000000000000000000D400000000000000000000000000000000000000000000000000000000000000DC00000000000000000000000000000000000000000000000000000000000000E400000000000000000000000000000000000000000000000000000000000000EC00000000000000000000000000000000000000000000000000000000000000F400000000000000000000000000000000000000000000000000000000000000FC000000000000000000000000000000000000000000000000000000000000000410CA2FA0B6C6651E0B4665A7EB8C4037D255CF129BAEFF090B056EF5F67D47DD37120A8B311B7096FBE158C93B47194CA09DAB72B6CAC1513A7D490CFEEAE247A1B000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041471DAA16525F41EECB9E61AE99223040BA37545A1EA96DDA773E6BB8C5A61B5464DDDB4E20EC3949B998200241DE9C49FB8045723B81BC78CC1FCBA9323ABD3D1C0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000419067FB85D0C3E267F6D2FD61BD5085CD8846F38C3D32A5FD0B4C933F7264310208DB54AB2C078302EFABF9FAAA994F50AEC7DCD40F1D3B76205833F9EC89F5C51B0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000419DD2D127717002EA2DC3A24DCB453725868BF777DD95EB9D819A82D884A3683A4269DEAE28C59B5486952972816BBD839D5C5A8203981042A45F81B6D4E7AB8D1B0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000418CD6BC6C41F0D9A359E5ED006BB882168F46AAE3961EA52BD847DF222563EB6E4CEBCCFF59C368FDDC1B02C7A9FB57357E85309101CEF37B24FD233A9DC2CD951C000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041B026E17268496BCD7E0E90DFA353C632830F315F49D71B17EFFE60122FAD8C717205C959BD739898B77BFB7B5F01ACEFF225D571B13CCB7930DDC918DBF8E76A1B000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041BDBB56293271E82E2DDF84C9CAA10AE8138CA0DF93CD8FEC9B2A695CC75316C427F562BA3AD768F1FAD579BF399A2E7A026ADE257C4F88E7FC05D7F5D4BAA7271C000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041765B7EDAAF3D91CA456A7040EEAB9A122465CD163DE4C555F90CF180F18DC2592C675D07CD38ED07E3957EE2F27A8F0FF590F9D2474C6CBD94D18EB0CBA574531B000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041753A3323E4F6CCAA4BB0BAF40281043AC90BE1470AF1FECC90356595592F869203AF7A3F5527E09A0B99E82A19D913EC693E4591BEB204C9E9845339869C50111B0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000413D33EB329CE91ECC31FE2C590DA36B08799A3629B253B563259AF136D5B030D1558C5E6B05675BDF7550ADF4021E7C0295AF605F5AE3293FD5199BE55D58E2B61C000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041EDB75D628115598F05DC4163064613231DC433637426435783AA1A8F670970337630902AAA174EADC9B3844987BC79C8B10BC6E980417A1FF178B4AA5B2BA74E1C000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041A8D6BE332CEE8F49179B1F541AE5736E24CABD52C5BDB2806B140C4C471A797A46D385DB66F31150C2DF589A0D1ED7EDE3FD31BB67EA67F2B2BB8B2F8C28C0261B000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041C32AFA4116A29CB779E97F12F49B0822F7EC196902830B87917949C1B1F78034321DA225A22AC27D111B42DB1005A45ABF6FCE0E444356BE90EAE6F51EF8D4981B000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041CA79B641C30D9DB1C60DCE0FD9D9BE7E32C90598568CAEA741B906F85DE1098C4B8CEB52784D573B50FA0DB64EAE0CB4DAE3CD71F1403E746AB60F246BAA37571B0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000415CA6593BE8E2825708D87465C1258C1D9A30676A0A3FD1E060A516933AEFF8C46B2610B0890A8BE23EFB3D9A3F77D23D40001B71A844E280D9CAC544D587E0621C000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041FA663E9713E92A0200A06F85B3F8CB7B5EEDC66D03D26F3B261BF5AE28A0125F6AD1CF590BEE65ACD381FE80175502B96E2ECCEB869285DCAA5D48B918E5DF611C0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000415E93E331150D74E4F52D313C2C319D601B40B048CC61D4596DADD25623CA2FAA46BB4B032BB61D27B1327D1BB1BAC1624361005400D8F2D7302D19DA443A4CF71C0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000411BE78602E005563C59658F690EBC1B87D82C8E39696848F50F8F60DD9DE67ABF245A6EC483E86631601650045D7A76732DBA5382DA27991850E4539949DE6C321C000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041B363631B9DB1CA6F15C50D8DDB5B37B6E8893D5ECA4B1A86DDB5F9C7EA2E52F63952187B8DCABA0AB5EC8AC4C68CBE17AE3F7AD91584C65C27D3CFCD3135D5D91B000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041FC55F600C048EFCCC2EA798D260197B1C393456028998CED16CBBFBF1B37F37E46F4E4FC0B5D57BDD41C751364F041FC3E0C1B38BCAF3DAAB28D5EE1DAC207A91B0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000410D80CEFA91265C781E9D0B2D30CE28150FCB6C2D454A638B240BDBD72BF5E92E3884FC1111AF437A7CE17E458E35686B7EAFD22CB6E7F94C37A3A971F7EA023B1B0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000417005430F31A9C252B9A6A1FD529B6686150F1B1F4DB8D8B0E7107EDB83F7FFD278C41924C673EB86F3BC1E1774CAFB2DEAC18C2AE56AFB88E250F302B148669B1C00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004175D64F85BB5850DF8F7ECDAADD5FBEFDB2762403EA8C58DB158C591441B50CA515D2ED000B648135C7DAF55C7AEC5670981B872D97252157583594F5848BD6D61B000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041961BD26E9C9D015769509B38E7FA347CD4AD60CF1B5EA9864E77DFFC7A0ABC447CDD16C41DF4284D3153798ADB136D519CF4C92D8C3177806BCAA454068535131B0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000419E2D6C8D56169E35C4EDF8A0234301AA4F5F2F1B185E23FF54E66F126161385978C8972E19A326CD0298536ABD46323A54B1B4F94202A7C699D58F7C920FE54D1B0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000413016B94A10C4368BE9BE782104CB490CEB84F6644F3ADE773661CF465BF72A3B64EEFF82C73F1C0D37A4D0B5C0827C65B2F1769444FED45FE6F4954D302B0B251B00000000000000000000000000000000000000000000000000000000000000 ) => ( True )-
Null: 0x000...001.81365954( ) -
Null: 0x000...001.81365954( ) -
Null: 0x000...001.81365954( ) -
Null: 0x000...001.81365954( ) -
Null: 0x000...001.81365954( ) -
Null: 0x000...001.81365954( ) -
Null: 0x000...001.81365954( ) -
Null: 0x000...001.81365954( ) -
Null: 0x000...001.81365954( ) -
Null: 0x000...001.81365954( ) -
Null: 0x000...001.81365954( ) -
Null: 0x000...001.81365954( ) -
Null: 0x000...001.81365954( ) -
Null: 0x000...001.81365954( ) -
Null: 0x000...001.81365954( ) -
Null: 0x000...001.81365954( ) -
Null: 0x000...001.81365954( ) -
Null: 0x000...001.81365954( ) -
Null: 0x000...001.81365954( ) -
Null: 0x000...001.81365954( ) -
Null: 0x000...001.81365954( ) -
Null: 0x000...001.81365954( ) -
Null: 0x000...001.81365954( ) -
Null: 0x000...001.81365954( ) -
Null: 0x000...001.81365954( ) -
Null: 0x000...001.81365954( )
-
Axelar: Gateway.585a9fd4( )-
AxelarGateway.approveContractCallWithMint( params=0x00000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000CE16F69375520AB01377CE7B88F5BA8C48F8D666D3F86EC380012C9F05C3141B2D060C29A88E0C38A743292487AA5C8316A725C600000000000000000000000000000000000000000000000000000000000001A0000000000000000000000000000000000000000000000000000000000112E6DF57284FA896B2BB5B9B3C2595E34147018D3F1FEDAD44DA4137C5A7F5F3E3BC8D00000000000000000000000000000000000000000000000000000000004CAF0500000000000000000000000000000000000000000000000000000000000000076F736D6F73697300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003F6F736D6F316E366E657939747366353565747A396E726D7A79643877613765363471643373303661373466717333306B6138707073366376717473796372360000000000000000000000000000000000000000000000000000000000000000045553444300000000000000000000000000000000000000000000000000000000, commandId=3BB55EC3F284AFB1BAB7F062855A0E6311C08182812195190CC563C1B38583CD )
-
File 1 of 2: AxelarGateway
File 2 of 2: AxelarAuthWeighted
// 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 2 of 2: AxelarAuthWeighted
// 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 '../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 { IAxelarAuthWeighted } from '../interfaces/IAxelarAuthWeighted.sol';
import { ECDSA } from '../ECDSA.sol';
import { Ownable } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/utils/Ownable.sol';
contract AxelarAuthWeighted is Ownable, IAxelarAuthWeighted {
uint256 public currentEpoch;
mapping(uint256 => bytes32) public hashForEpoch;
mapping(bytes32 => uint256) public epochForHash;
uint256 internal constant OLD_KEY_RETENTION = 16;
constructor(bytes[] memory recentOperators) Ownable(msg.sender) {
uint256 length = recentOperators.length;
for (uint256 i; i < length; ++i) {
_transferOperatorship(recentOperators[i]);
}
}
/**************************\\
|* External Functionality *|
\\**************************/
/// @dev This function takes messageHash and proof data and reverts if proof is invalid
/// @return True if provided operators are the current ones
function validateProof(bytes32 messageHash, bytes calldata proof) external view returns (bool) {
(address[] memory operators, uint256[] memory weights, uint256 threshold, bytes[] memory signatures) = abi.decode(
proof,
(address[], uint256[], uint256, bytes[])
);
bytes32 operatorsHash = keccak256(abi.encode(operators, weights, threshold));
uint256 operatorsEpoch = epochForHash[operatorsHash];
uint256 epoch = currentEpoch;
if (operatorsEpoch == 0 || epoch - operatorsEpoch >= OLD_KEY_RETENTION) revert InvalidOperators();
_validateSignatures(messageHash, operators, weights, threshold, signatures);
return operatorsEpoch == epoch;
}
/***********************\\
|* Owner Functionality *|
\\***********************/
function transferOperatorship(bytes calldata params) external onlyOwner {
_transferOperatorship(params);
}
/**************************\\
|* Internal Functionality *|
\\**************************/
function _transferOperatorship(bytes memory params) internal {
(address[] memory newOperators, uint256[] memory newWeights, uint256 newThreshold) = abi.decode(
params,
(address[], uint256[], uint256)
);
uint256 operatorsLength = newOperators.length;
uint256 weightsLength = newWeights.length;
// operators must be sorted binary or alphabetically in lower case
if (operatorsLength == 0 || !_isSortedAscAndContainsNoDuplicate(newOperators)) revert InvalidOperators();
if (weightsLength != operatorsLength) revert InvalidWeights();
uint256 totalWeight;
for (uint256 i; i < weightsLength; ++i) {
totalWeight = totalWeight + newWeights[i];
}
if (newThreshold == 0 || totalWeight < newThreshold) revert InvalidThreshold();
bytes32 newOperatorsHash = keccak256(params);
if (epochForHash[newOperatorsHash] != 0) revert DuplicateOperators();
uint256 epoch = currentEpoch + 1;
// slither-disable-next-line costly-loop
currentEpoch = epoch;
hashForEpoch[epoch] = newOperatorsHash;
epochForHash[newOperatorsHash] = epoch;
emit OperatorshipTransferred(newOperators, newWeights, newThreshold);
}
function _validateSignatures(
bytes32 messageHash,
address[] memory operators,
uint256[] memory weights,
uint256 threshold,
bytes[] memory signatures
) internal pure {
uint256 operatorsLength = operators.length;
uint256 signaturesLength = signatures.length;
uint256 operatorIndex;
uint256 weight;
// looking for signers within operators
// assuming that both operators and signatures are sorted
for (uint256 i; i < signaturesLength; ++i) {
address signer = ECDSA.recover(messageHash, signatures[i]);
// looping through remaining operators to find a match
for (; operatorIndex < operatorsLength && signer != operators[operatorIndex]; ++operatorIndex) {}
// checking if we are out of operators
if (operatorIndex == operatorsLength) revert MalformedSigners();
// accumulating signatures weight
weight = weight + weights[operatorIndex];
// weight needs to reach or surpass threshold
if (weight >= threshold) return;
// increasing operators index if match was found
++operatorIndex;
}
// if weight sum below threshold
revert LowSignaturesWeight();
}
function _isSortedAscAndContainsNoDuplicate(address[] memory accounts) internal pure returns (bool) {
uint256 accountsLength = accounts.length;
address prevAccount = accounts[0];
if (prevAccount == address(0)) return false;
for (uint256 i = 1; i < accountsLength; ++i) {
address currAccount = accounts[i];
if (prevAccount >= currAccount) {
return false;
}
prevAccount = currAccount;
}
return true;
}
}
// 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.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.0;
import { IAxelarAuth } from './IAxelarAuth.sol';
interface IAxelarAuthWeighted is IAxelarAuth {
error InvalidOperators();
error InvalidThreshold();
error DuplicateOperators();
error MalformedSigners();
error LowSignaturesWeight();
error InvalidWeights();
event OperatorshipTransferred(address[] newOperators, uint256[] newWeights, uint256 newThreshold);
function currentEpoch() external view returns (uint256);
function hashForEpoch(uint256 epoch) external view returns (bytes32);
function epochForHash(bytes32 hash) external view returns (uint256);
}