From 37b380f5c8e979c0058fb8e847817bf966032474 Mon Sep 17 00:00:00 2001 From: ss-sonic Date: Tue, 17 Sep 2024 01:41:11 +0530 Subject: [PATCH] added router nitro app --- bun.lockb | Bin 28441 -> 29288 bytes foundry.toml | 89 ++-- package.json | 4 +- remappings.txt | 2 + script/Base.s.sol | 2 +- script/Deploy.s.sol | 2 +- src/Foo.sol | 2 +- src/accounts/utils/RequestTypes.sol | 14 + src/apps/base/AppAccountBase.sol | 401 ++++++++++++++++++ src/apps/base/AppBase.sol | 98 +++++ src/apps/base/AppBeaconBase.sol | 91 ++++ src/apps/base/AppERC2771Context.sol | 68 +++ src/apps/base/AppSecurityModifiers.sol | 108 +++++ src/apps/curve/CurveAppError.sol | 14 + src/apps/curve/CurveStableSwapApp.sol | 302 +++++++++++++ src/apps/curve/CurveStableSwapAppBeacon.sol | 83 ++++ src/apps/nitro/RouterNitroApp.sol | 141 ++++++ src/apps/nitro/RouterNitroAppBeacon.sol | 36 ++ .../accounts/IAccountUtilsModule.sol | 136 ++++++ src/interfaces/accounts/IBaseModule.sol | 119 ++++++ src/interfaces/apps/base/IAppAccountBase.sol | 173 ++++++++ src/interfaces/apps/base/IAppBeaconBase.sol | 75 ++++ src/interfaces/curve/ICurveStableSwapApp.sol | 131 ++++++ .../curve/ICurveStableSwapAppBeacon.sol | 86 ++++ .../curve/ICurveStableSwapFactoryNG.sol | 324 ++++++++++++++ src/interfaces/curve/ICurveStableSwapNG.sol | 134 ++++++ src/interfaces/nitro/IRouterNitroApp.sol | 34 ++ .../nitro/IRouterNitroAppBeacon.sol | 8 + src/interfaces/nitro/IRouterNitroGateway.sol | 126 ++++++ src/libraries/Error.sol | 143 +++++++ test/Foo.t.sol | 7 +- 31 files changed, 2902 insertions(+), 51 deletions(-) create mode 100644 src/accounts/utils/RequestTypes.sol create mode 100644 src/apps/base/AppAccountBase.sol create mode 100644 src/apps/base/AppBase.sol create mode 100644 src/apps/base/AppBeaconBase.sol create mode 100644 src/apps/base/AppERC2771Context.sol create mode 100644 src/apps/base/AppSecurityModifiers.sol create mode 100644 src/apps/curve/CurveAppError.sol create mode 100644 src/apps/curve/CurveStableSwapApp.sol create mode 100644 src/apps/curve/CurveStableSwapAppBeacon.sol create mode 100644 src/apps/nitro/RouterNitroApp.sol create mode 100644 src/apps/nitro/RouterNitroAppBeacon.sol create mode 100644 src/interfaces/accounts/IAccountUtilsModule.sol create mode 100644 src/interfaces/accounts/IBaseModule.sol create mode 100644 src/interfaces/apps/base/IAppAccountBase.sol create mode 100644 src/interfaces/apps/base/IAppBeaconBase.sol create mode 100644 src/interfaces/curve/ICurveStableSwapApp.sol create mode 100644 src/interfaces/curve/ICurveStableSwapAppBeacon.sol create mode 100644 src/interfaces/curve/ICurveStableSwapFactoryNG.sol create mode 100644 src/interfaces/curve/ICurveStableSwapNG.sol create mode 100644 src/interfaces/nitro/IRouterNitroApp.sol create mode 100644 src/interfaces/nitro/IRouterNitroAppBeacon.sol create mode 100644 src/interfaces/nitro/IRouterNitroGateway.sol create mode 100644 src/libraries/Error.sol diff --git a/bun.lockb b/bun.lockb index 86d5c93cd74609bc9ea3f2465af343852a58d7c6..b94afed7721d5b8c9cbc8688b677a4b0c7358dc4 100755 GIT binary patch delta 5341 zcmeHLdvH|M89(k#11)NSBJlP-LW znfdnH-#Op;&iA_aoO>R6Pa64)w8@$KGy5A+yWY&tcTCD{87lcn)P_q3hi}{T{KEa) zwy*!<$rZbf^$5Mq-Rw>6-&z|0f!{bG2G-*51aL2SCb$PY0lc@nv%RCMPpt6!`uaM2 z{xn$#3;O%o7Ipd{6CcE$kU;+^a3scufqDLG;1S^Gf^x5hV!R(6`|yVcYE42|!MB66 z;`bpK4c@kV71Rr{(%<1&-Ydk4m0fEVcQ5g+4HIH4#+QPR1NVZne6R2JRX)EEDP|!Q z;5BWXogKY>!U}ziJG+;(^|lMKyw|aYmPnKPKSwVQJcLzQ!8!0K@Q$u_pTDE8x1(!m zXUDSkzOxWwLqmg#E=vKt4sP;%KR6p+1TSL2^zkO96F25`u)pr849aE|EUn1I{` z7|im)g@fxBUt9Q0(4)4lzD|F!5Ju>Y5I4c9@lf!lL}{Hk;h0YDTotG*!MQgsQ_;c_f4e!&OB^4XKba)8=ruvJQyn zgzD%IKpr5OksNKJL9?pdj3T=s&}nkZJ56L!RQZ&Ns=@4GGzhjajE2AtgptLfN|$Ml zr9!C+7orZ+Bwg2o;WT7X<&VP25}_(NsJAH?gQc*c4X6|-V8esKG2K3S*i4p4RgP1r zI#N|Mq@>I+MY#(Ikw=_1o88JUfeL`kh@)Hjiso1=lswcJOGq3^xz$2LR#kZhowL4H zas{aJYY|Tb4MwT*>IfQ&Qk54_f*dN{1?3zN8x3T3%l1ebj8>KVQ9Rrku>Ex)jwUh} z=9Vk1R2`$5?n7NIBCBPpbb(%vsgR4J$P$a(MNxIEsvJUrx-dnidc`d#M$=HNYFdaY zFQbj&Q{^quR6Ryj&Y*)W38C0nx11S6L+JFOmU%UahT+}gF;qQPRiaSHUmGOPj-?@V z{v4g0*AP8YX4E*NfX%akIK6PolXr-yJ9l{bK<0$F%WHjkz1I8~W~Hwi~9oSjsD z0K_Q?DBBCfb_$*#eMxiTE9Bg9G#IZcemoFd4}yjjw{j5Z79hAC>6TOCs5(KF{c$u1 zb}){H64d0s;T6m82)774imM8pWzIq&LgR%Ew!8lGC=$gW-&lx&f2nrJO`ZH^*of46I1b47Gg60pfTrJjv#k}bNepN za{2n`H#yselGXK;1jm`PJRTCAb40j->+AC$A%k>ZzQtJ~QldBJp&;a2Z`X5P;KpD- zbJjN##9_hvM_+gjIJavY+aFJ?1~Jz_+?a=uV|=P4Xy*9Tepc21Vww3MZr{b(xF&t{ zo1Eu02m8Oxx%)pZAH)7<(m>O=Kt#1 z-?u+(8~IRF)fCr9@OZR42=?qXT6=ry;EYnI`GBjx{odg;InwhCdakM$pOWP(|=^{`9 z&6wz=*;!V4YN943(q}-UK#f_NG=Uz^^3pTeRx)L4QZm(Nd#NeMN_&7(Ny_n(*={9Y zjwYqki$MGAQ50{lmNKc$?xprzD;=_HQWnML!oNxIFISUt=m5}RpsY!nluJF6;9nm6 z1InZHJoq;m{^e8iU@d-JaXg>GOPp+h#C-2 z;Ejd*a7fg5C!WA&^CCC^3Rcd2b3xpP0O@k9Im(}pA z7^f;eyXi*&oLU9WHGB*dMIb&d@KJ(~DC0rcV?`1u50nMsjg7q7rc z;{=;PIBe=0yA|95;&CnlBokkX!H#I~C=lZqP%MZYG>S=cpHhI7^%@E(b0JPCb_e zr<2p_0kQF%EzSlTZA6`o=FAmvJ)uo$32@7_7FX%Qm1Qa^bqO45O08Sk0F$`>;K6|L z{VB_V2|}#JA5_?Y@r5c&w-z}|`JnB=2%ZW;TcM{6;wbJ=nLPCavQ?&o^)5MHCTD|#sMMr%b?=&nc3>m$0(QQ9>xF{n%SgwUVo*^-QF zlTVi1-1zRa-&J0-U^s-^ zal(~;`GCGs5o^GX)wRHuWZb{}wQK90<19NfA%M(tQS6DLCl%N=KAsnA*IJof^j`F^2}iL(W7B2uwHQ> zUgv4QH?h>Xruk%a(Yvi<&BfQojSHKPBhwNi?#yyw1dgyP`$8$I$;Ou0n_Na>jBA;@ zE8d<^edKTz#Df`L8b+I%D#t9ry8y3?fxClG)ZH!gL6gVwLJ%J5p@QZ#JUmp_?4?c3 z2`P@1E0+1&miYK?&(pi6tFPVH*Ri^zJAZMv-{+u5o3rx&rFU_6SD(LaabIt~BOvy5 ohXc`gUt!q*Q?eggR`#hca+d$t(g$FORn7?H#_7038?hycK`qY delta 4956 zcmeHLdvH|M89(PHo9xZ*CL|=V8v=R1HpwQtd2B-9Cc%W)4PcP4NkDCYV6rX&lJIak zV**HvQft+l8WEkz1WokFAxOp!usNq^tny$~JiO#4^= z=*j%@?e9CU?{V%q=ibwQlHWTazvU@>^W!sb{kG*v*WWJAPS3e(b<;g7U)A$hE}!xJ z8vPf)Ik8~m?p|S(=HWp4;6r+tg#V`qF;tJAlOg-phPr!t2gJI*u7QD`u0F36A`be2 z&K039w8@X~7A34#A?>LD5R&`92Wf*Gi?llu6|R2+lJ&Pl3*opKKc2!59{4;OCO~!u zHyYM#=<6v7_6xCYL+>3c*RJZ?X%S)~>Ni0qLv}-Q`~I#q8@u|1D2)|D1;3*+6zb_8 z5Yr^BklJ7rbgUl1KW;t-84uah+uhaIGtl4DyE@c!Tlc_aYuK(7n8EGiAlc4)@sIg7 zNH(((P9{R;Lr#V4>g!wESJJh)#u08mDN?_(yEAmV5Q8EOv+;!8o&6=NL!Gx-LD>G! z1OnWQ!)q950{A5SMY1jFn|N>!0as93N#&h5R%);h~bfmBlA{VpZF5OiZO;d zdj~>&6+)PSHUrv&=%%9nkAmJ&@(x&|_9|)seda=Eq4lwT;Fi&>R8NA{gDFOn zjA(MJntD5y#;mXsvp*M2BdVtSHJU~tYAocA)0Ev73PQYNp^-REjf)Xtq0!-j#jm!3 z-3TU)G55t#(55N>h@lairdDIMOh;XeF?%0a6_{a*Wgpkk8f&fcVJwZ``RE5 zEUCt8OR#lNmEjm30}IFgLX2N& zwbSSX&9Vn;Wd%JIS1Tp;B-Sa74jM^B@*FgpsHsP>f_xZ2p$iz*9Z$g|P3^@BWk18| zdOV&+p}YhoTN6dkB>I(#1PV^n)Qwo1JR>4(^D942piwB(v2d>$rL<3=V6vvZ1SRJ; z%7~T=YnEBq?shOvGe$vNe*nhaqd1c439wqQIAa02l4vwVQ}v{9)M5#as^0`-bHjtZ z4#sv0?jS96W>TFpeImIhYw9*Ea9$6niu0@Qf-MJw>vq3VnoOgUHDy~ext*HwZZZX( z+Eg1hM1JP|cnbIt+vxRFd%QxVK%Yj#TKYUJP{H8B5|fKbOi;#4>{Vh4fM-;s0bE#O z)r)i{lIWl{1UEem>n>OlP)O2?w%SAO_@TlMJXd#k*@Uk)6L zZCcfO>Z_J3Q`gRW^y_Dz$PGT36+f2z`i8F@E4;Jg#Dkwa=i2k`3p?K`zPkV1uiv9J z)0|Y9uFELek{+OfbO(I|hNCAVK<|UypP|cGIt8{P!$D1%x>RX6GeFgu4*CSlMh#g3 zIt}(%mM$H10c=;6gIcq7nLrO`2WW1#gDg3^Or(~a09^)q7HlHP+yL#%aZp#TE>q|k zu(n(WrR3>yGIiz!$eQP%SHWDAm>-~@fo;gwkh8tfhls-9GqNuFILUz8wCQ!#}V(%B+BY zVB0Hn*+6fDZLV-oWu-1}pe>c~uM+-&HIcUp{(;?JrOP>V3T#Ie{PXK_9u529pCA5# z>C{lYiB4AAU3>6A@R`Khn0Ys^#fFrC^Jvb23ANP{%`EoFrT84KKn|{!6t{Gq*B4)JXG0aWhhTq(db$kGDJ6x6wBY50JKo%=;M>3Q=2<{I? z!-B{W5?V-qU#Q6#s$5h%$Y(?guo$=rSOW0KJQAOYxTT010B5Zz0X#q{PzDqMe3tVq z0q;-33Alg)APeA$*+ce-Lx^jampaI~}&H^|zrksu6oG(*yN*KE$2j_YAP|($d(+1f_9$Eo7csvz%<>7d` zdVtMfU)i^GU>d-tvhS$?`_I|qOh*H}lyDE^*CKv{F|y9}oHBq?U?R z;VknFinGEuBxJ@2IIG^F5tIb(@W$(QtvRwsp)q$*xXJC-!3nJ?y#{&d?Pra1r1V^(-7JaB7F z()lIMH1pN}pCP$_T~^~Uqk+$Zg%%!6X;DaPbyk_51kSGNX#HTuOO4k}_zXl?;3U*5ziK3S<_P-y&_|Sl{SFF#OS#L|I zMpqxkM(Cc3rue1KD)U2v>(H?J!S?-68k2juf%#_q`yF{#wPib0)Of>Y?X=MHrOiqy z-VY%zTj}!BW*p8kG1 zD+i645H~z*c6zhTnPz^IxYYZ=*5tUyv(cc;<73ZWb9}qqH{}bWS{|;q(t>uEk`_6X-C8Grw4z-gi&J(W5czuDc>=YKN=J z{Bm*j@`?{`ooKDNUT=QG_}HG|vTe=vp(c|5H=-!i;bcp`*5Uh-RGA+y?y5U7W&Ytq zb1(vD8L`-8p>rLLNgMF|V@nvyYd0Puk=Ks%Et%Su702BeQElm^`<7+U{$&AbTAoHp OH|I<5f$2AUQvL-RNMRuW diff --git a/foundry.toml b/foundry.toml index dacd6da..9565327 100644 --- a/foundry.toml +++ b/foundry.toml @@ -1,55 +1,56 @@ # Full reference https://github.com/foundry-rs/foundry/tree/master/crates/config [profile.default] - auto_detect_solc = false - block_timestamp = 1_680_220_800 # March 31, 2023 at 00:00 GMT - bytecode_hash = "none" - evm_version = "shanghai" - fuzz = { runs = 1_000 } - gas_reports = ["*"] - optimizer = true - optimizer_runs = 10_000 - out = "out" - script = "script" - solc = "0.8.25" - src = "src" - test = "test" +auto_detect_solc = false +block_timestamp = 1_680_220_800 # March 31, 2023 at 00:00 GMT +bytecode_hash = "none" +evm_version = "shanghai" +fuzz = { runs = 1_000 } +gas_reports = ["*"] +optimizer = true +optimizer_runs = 10_000 +out = "out" +script = "script" +solc = "0.8.21" +src = "src" +test = "test" +libs = ["node_modules", "lib"] [profile.ci] - fuzz = { runs = 10_000 } - verbosity = 4 +fuzz = { runs = 10_000 } +verbosity = 4 [etherscan] - arbitrum = { key = "${API_KEY_ARBISCAN}" } - avalanche = { key = "${API_KEY_SNOWTRACE}" } - base = { key = "${API_KEY_BASESCAN}" } - bnb_smart_chain = { key = "${API_KEY_BSCSCAN}" } - gnosis_chain = { key = "${API_KEY_GNOSISSCAN}" } - goerli = { key = "${API_KEY_ETHERSCAN}" } - mainnet = { key = "${API_KEY_ETHERSCAN}" } - optimism = { key = "${API_KEY_OPTIMISTIC_ETHERSCAN}" } - polygon = { key = "${API_KEY_POLYGONSCAN}" } - sepolia = { key = "${API_KEY_ETHERSCAN}" } +arbitrum = { key = "${API_KEY_ARBISCAN}" } +avalanche = { key = "${API_KEY_SNOWTRACE}" } +base = { key = "${API_KEY_BASESCAN}" } +bnb_smart_chain = { key = "${API_KEY_BSCSCAN}" } +gnosis_chain = { key = "${API_KEY_GNOSISSCAN}" } +goerli = { key = "${API_KEY_ETHERSCAN}" } +mainnet = { key = "${API_KEY_ETHERSCAN}" } +optimism = { key = "${API_KEY_OPTIMISTIC_ETHERSCAN}" } +polygon = { key = "${API_KEY_POLYGONSCAN}" } +sepolia = { key = "${API_KEY_ETHERSCAN}" } [fmt] - bracket_spacing = true - int_types = "long" - line_length = 120 - multiline_func_header = "all" - number_underscore = "thousands" - quote_style = "double" - tab_width = 4 - wrap_comments = true +bracket_spacing = true +int_types = "long" +line_length = 120 +multiline_func_header = "all" +number_underscore = "thousands" +quote_style = "double" +tab_width = 4 +wrap_comments = true [rpc_endpoints] - arbitrum = "https://arbitrum-mainnet.infura.io/v3/${API_KEY_INFURA}" - avalanche = "https://avalanche-mainnet.infura.io/v3/${API_KEY_INFURA}" - base = "https://mainnet.base.org" - bnb_smart_chain = "https://bsc-dataseed.binance.org" - gnosis_chain = "https://rpc.gnosischain.com" - goerli = "https://goerli.infura.io/v3/${API_KEY_INFURA}" - localhost = "http://localhost:8545" - mainnet = "https://eth-mainnet.g.alchemy.com/v2/${API_KEY_ALCHEMY}" - optimism = "https://optimism-mainnet.infura.io/v3/${API_KEY_INFURA}" - polygon = "https://polygon-mainnet.infura.io/v3/${API_KEY_INFURA}" - sepolia = "https://sepolia.infura.io/v3/${API_KEY_INFURA}" +arbitrum = "https://arbitrum-mainnet.infura.io/v3/${API_KEY_INFURA}" +avalanche = "https://avalanche-mainnet.infura.io/v3/${API_KEY_INFURA}" +base = "https://mainnet.base.org" +bnb_smart_chain = "https://bsc-dataseed.binance.org" +gnosis_chain = "https://rpc.gnosischain.com" +goerli = "https://goerli.infura.io/v3/${API_KEY_INFURA}" +localhost = "http://localhost:8545" +mainnet = "https://eth-mainnet.g.alchemy.com/v2/${API_KEY_ALCHEMY}" +optimism = "https://optimism-mainnet.infura.io/v3/${API_KEY_INFURA}" +polygon = "https://polygon-mainnet.infura.io/v3/${API_KEY_INFURA}" +sepolia = "https://sepolia.infura.io/v3/${API_KEY_INFURA}" diff --git a/package.json b/package.json index 8c8d160..d13df23 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,9 @@ "url": "https://github.com/PaulRBerg" }, "dependencies": { - "@openzeppelin/contracts": "^5.0.1" + "@openzeppelin/contracts": "^5.0.2", + "@openzeppelin/contracts-upgradeable": "^5.0.2", + "@synthetixio/core-contracts": "^3.6.1" }, "devDependencies": { "forge-std": "github:foundry-rs/forge-std#v1.8.1", diff --git a/remappings.txt b/remappings.txt index 550f908..5376b56 100644 --- a/remappings.txt +++ b/remappings.txt @@ -1,2 +1,4 @@ @openzeppelin/contracts/=node_modules/@openzeppelin/contracts/ +@openzeppelin/contracts-upgradeable/=node_modules/@openzeppelin/contracts-upgradeable/ +@synthetixio/core-contracts/=node_modules/@synthetixio/core-contracts/ forge-std/=node_modules/forge-std/ diff --git a/script/Base.s.sol b/script/Base.s.sol index 07135a2..cc41c66 100644 --- a/script/Base.s.sol +++ b/script/Base.s.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.25 <0.9.0; +pragma solidity >=0.8.21; import { Script } from "forge-std/src/Script.sol"; diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol index 498db52..8d7ac32 100644 --- a/script/Deploy.s.sol +++ b/script/Deploy.s.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity >=0.8.25 <0.9.0; +pragma solidity >=0.8.21; import { Foo } from "../src/Foo.sol"; diff --git a/src/Foo.sol b/src/Foo.sol index 7483070..06d3aaa 100644 --- a/src/Foo.sol +++ b/src/Foo.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity >=0.8.25; +pragma solidity >=0.8.21; contract Foo { function id(uint256 value) external pure returns (uint256) { diff --git a/src/accounts/utils/RequestTypes.sol b/src/accounts/utils/RequestTypes.sol new file mode 100644 index 0000000..80db536 --- /dev/null +++ b/src/accounts/utils/RequestTypes.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.21; + +contract RequestTypes { + struct Request { + address _address; + address _address2; + uint256 _uint256; + bytes32 _nonce; + uint32 _uint32; + bool _bool; + bytes4 _selector; + } +} diff --git a/src/apps/base/AppAccountBase.sol b/src/apps/base/AppAccountBase.sol new file mode 100644 index 0000000..b7a63b4 --- /dev/null +++ b/src/apps/base/AppAccountBase.sol @@ -0,0 +1,401 @@ +// c=< +// | +// | ////\ 1@2 +// @@ | /___\** @@@2 @@@@@@@@@@@@@@@@@@@@@@ +// @@@ | |~L~ |* @@@@@@ @@@ @@@@@ @@@@ @@@ @@@@ @@@ @@@@@@@@ @@@@ @@@@ @@@ @@@@@@@@@ @@@@ @@@@ +// @@@@@ | \=_/8 @@@@1@@ @@@ @@@@@ @@@@ @@@@ @@@ @@@@@ @@@ @@@@@@@@@ @@@@ @@@@@ @@@@ @@@@@@@@@ @@@@ +// @@@@ +// @@@@@@| _ /| |\__ @@@@@@@@2 @@@ @@@@@ @@@@ @@@@ @@@ @@@@@@@ @@@ @@@@ @@@@ @@@@@@ @@@@ @@@ @@@@@@@ +// 1@@@@@@|\ \___/) @@1@@@@@2 ~~~ ~~~~~ @@@@ ~~@@ ~~~ ~~~~~~~~~~~ ~~~~ ~~~~ ~~~~~~~~~~~ ~@@ @@@@@ +// 2@@@@@ | \ \ / | @@@@@@2 @@@ @@@@@ @@@@ @@@@ @@@ @@@@@@@@@@@ @@@@@@@@@ @@@@ @@@@@@@@@@@ @@@@@@@@@ @@@@@ +// 2@@@@ |_ > <|__ @@1@12 @@@ @@@@@ @@@@ @@@@ @@@ @@@@ @@@@@@ @@@@ @@@@ @@@@ @@@@@@ @@@ @@@@@@@ +// @@@@ / _| / \/ \ @@1@ @@@ @@@ @@@@ @@@@ @@@ @@@@ @@@@@ @@@@ @@@@ @@@@ @@@@@ @@@@@@@@@ @@@@ +// @@@@ +// @@ / |^\/ | | @@1 @@@ @@@@ @@@@ @@@ @@@@ @@@ @@@@ @@@@ @@@ @@@@ @@@@@@@@@ @@@@ @@@@ +// / / ---- \ \\\= @@ @@@@@@@@@@@@@@@@@@@@@@ +// \___/ -------- ~~ @@@ +// @@ | | | | -- @@ +// ———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— + +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.21; + +import { ERC165 } from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; +import { ERC721Holder } from "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol"; +import { ERC1155Holder } from "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol"; +import { ReentrancyGuardUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol"; +import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; + +import { IUUPSImplementation } from "@synthetixio/core-contracts/contracts/interfaces/IUUPSImplementation.sol"; +import { UUPSImplementation } from "@synthetixio/core-contracts/contracts/proxy/UUPSImplementation.sol"; + +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { IERC165 } from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; +import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; +import { IERC1155 } from "@openzeppelin/contracts/token/ERC1155/IERC1155.sol"; + +import { IAppAccountBase } from "src/interfaces/apps/base/IAppAccountBase.sol"; +import { IAppBeaconBase } from "src/interfaces/apps/base/IAppBeaconBase.sol"; + +import { AppBase } from "src/apps/base/AppBase.sol"; +import { AppSecurityModifiers } from "src/apps/base/AppSecurityModifiers.sol"; + +abstract contract AppAccountBase is + IAppAccountBase, + UUPSImplementation, + AppSecurityModifiers, + ERC165, + ERC721Holder, + ERC1155Holder, + ReentrancyGuardUpgradeable +{ + /*/////////////////////////////////////////////////////////////// + FALLBACK + ///////////////////////////////////////////////////////////////*/ + + receive() external payable { } + + /*/////////////////////////////////////////////////////////////// + CONSTRUCTOR + ///////////////////////////////////////////////////////////////*/ + + constructor() { + _disableInitializers(); + } + + /*/////////////////////////////////////////////////////////////// + INITIALIZER + ///////////////////////////////////////////////////////////////*/ + + /** + * @notice Initialize the app account with the main account and the app beacon. + * @param _mainAccount the address of the main account, this is the owner of the app. + * @param _appBeacon the beacon for the app account. + */ + function initialize(address _mainAccount, address _appBeacon) external virtual initializer { + AppBase._setMainAccount(_mainAccount); + if (!IERC165(_appBeacon).supportsInterface(type(IAppBeaconBase).interfaceId)) { + revert InvalidAppBeacon(); + } + AppBase._setAppBeacon(_appBeacon); + } + + /*/////////////////////////////////////////////////////////////// + VIEW FUNCTIONS + ///////////////////////////////////////////////////////////////*/ + + /** + * @dev See {IERC165-supportsInterface}. + */ + function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, ERC1155Holder) returns (bool) { + return interfaceId == type(IAppAccountBase).interfaceId || super.supportsInterface(interfaceId); + } + + /** + * @notice Returns the app version number of the app account. + * @return A uint64 representing the version of the app. + * @dev NOTE: This number must be updated whenever a new version is deployed. + * The number should always only be incremented by 1. + */ + function appVersion() public pure virtual returns (uint64) { + return 1; + } + + /** + * @notice Get the app's main account. + * @return The main account associated with this app. + */ + function getMainAccount() external view returns (address) { + return AppBase._getMainAccount(); + } + + /** + * @notice Get the app config beacon. + * @return The app config beacon address. + */ + function getAppBeacon() external view returns (address) { + return AppBase._getAppBeacon(); + } + + /*/////////////////////////////////////////////////////////////// + MUTATIVE FUNCTIONS + ///////////////////////////////////////////////////////////////*/ + + /** + * @notice Transfer Ether to the main account from the app account. + * @param _amount The amount of Ether to transfer. + */ + function transferEtherToMainAccount(uint256 _amount) external nonReentrant requiresAuthorizedOperationsParty { + _transferEtherToMainAccount(_amount); + } + + /** + * @notice Transfer ERC20 tokens to the main account from the app account. + * @param _token The address of the ERC20 token. + * @param _amount The amount of tokens to transfer. + */ + function transferERC20ToMainAccount( + address _token, + uint256 _amount + ) + external + nonReentrant + requiresAuthorizedOperationsParty + { + _transferERC20ToMainAccount(_token, _amount); + } + + /** + * @notice Transfer ERC721 tokens to the main account from the app account. + * @param _token The address of the ERC721 token. + * @param _tokenId The token ID to transfer. + */ + function transferERC721ToMainAccount( + address _token, + uint256 _tokenId + ) + external + nonReentrant + requiresAuthorizedOperationsParty + { + _transferERC721ToMainAccount(_token, _tokenId); + } + + /** + * @notice Transfer ERC1155 tokens to the main account from the app account. + * @param _token The address of the ERC1155 token. + * @param _tokenId The token ID to transfer. + * @param _amount The amount of tokens to transfer. + * @param _data Additional data to pass in the transfer. + */ + function transferERC1155ToMainAccount( + address _token, + uint256 _tokenId, + uint256 _amount, + bytes calldata _data + ) + external + nonReentrant + requiresAuthorizedOperationsParty + { + _transferERC1155ToMainAccount(_token, _tokenId, _amount, _data); + } + + /** + * @notice Transfers batch ERC1155 tokens to the main account from the app account. + * @param _token The address of the ERC1155 token. + * @param _ids The IDs of the ERC1155 tokens. + * @param _amounts The amounts of the ERC1155 tokens. + * @param _data Data to send with the transfer. + */ + function transferERC1155BatchToMainAccount( + address _token, + uint256[] calldata _ids, + uint256[] calldata _amounts, + bytes calldata _data + ) + external + nonReentrant + requiresAuthorizedOperationsParty + { + _transferERC1155BatchToMainAccount(_token, _ids, _amounts, _data); + } + + /** + * @notice Recovers all ether in the app account to the main account. + * @dev Requires the sender to be an authorized recovery party. + */ + function recoverEtherToMainAccount() external requiresAuthorizedRecoveryParty nonReentrant { + emit EtherRecoveredToMainAccount(address(this).balance); + _transferEtherToMainAccount(address(this).balance); + } + + /** + * @notice Recovers the full balance of an ERC20 token to the main account. + * @param _token The address of the token to be recovered to the main account. + * @dev Requires the sender to be an authorized recovery party. + */ + function recoverERC20ToMainAccount(address _token) public nonReentrant requiresAuthorizedRecoveryParty { + uint256 balance = IERC20(_token).balanceOf(address(this)); + emit ERC20RecoveredToMainAccount(_token, balance); + _transferERC20ToMainAccount(_token, balance); + } + + /** + * @notice Recovers a specified ERC721 token to the main account. + * @param _token The ERC721 token address to recover. + * @param _tokenId The ID of the ERC721 token to recover. + * @dev Requires the sender to be an authorized recovery party. + */ + function recoverERC721ToMainAccount( + address _token, + uint256 _tokenId + ) + external + requiresAuthorizedRecoveryParty + nonReentrant + { + emit ERC721RecoveredToMainAccount(_token, _tokenId); + _transferERC721ToMainAccount(_token, _tokenId); + } + + /** + * @notice Recovers a specified ERC1155 token to the main account. + * @param _token The ERC1155 token address to recover. + * @param _tokenId The id of the token to recover. + * @param _data The data for the transaction. + * @dev Requires the sender to be an authorized recovery party. + */ + function recoverERC1155ToMainAccount( + address _token, + uint256 _tokenId, + bytes calldata _data + ) + external + requiresAuthorizedRecoveryParty + nonReentrant + { + uint256 balance = IERC1155(_token).balanceOf(address(this), _tokenId); + emit ERC1155RecoveredToMainAccount(_token, _tokenId, balance, _data); + _transferERC1155ToMainAccount(_token, _tokenId, balance, _data); + } + + /** + * @notice Recovers multiple ERC1155 tokens to the main account. + * @param _token The address of the ERC1155 token. + * @param _tokenIds The IDs of the ERC1155 tokens. + * @param _amounts The values of the ERC1155 tokens. + * @param _data Data to send with the transfer. + * @dev Requires the sender to be an authorized recovery party. + */ + function recoverERC1155BatchToMainAccount( + address _token, + uint256[] calldata _tokenIds, + uint256[] calldata _amounts, + bytes calldata _data + ) + external + requiresAuthorizedRecoveryParty + nonReentrant + { + emit ERC1155BatchRecoveredToMainAccount(_token, _tokenIds, _amounts, _data); + _transferERC1155BatchToMainAccount(_token, _tokenIds, _amounts, _data); + } + + /** + * @notice Upgrade to a new account implementation + * @param _newImplementation The address of the new account implementation + * @dev when this function is called, the UUPSImplementation contract will do + * a simulation of the upgrade. If the simulation fails, the upgrade will not be performed. + * So when simulatingUpgrade is true, we bypass the security logic as the way the simulation is + * done would always revert. + * @dev NOTE: DO NOT CALL THIS FUNCTION DIRECTLY. USE upgradeAppVersion INSTEAD. + */ + function upgradeTo(address _newImplementation) public { + /// @dev if we are in the middle of a simulation, then we use the default _upgradeTo function + if (_proxyStore().simulatingUpgrade) { + _upgradeTo(_newImplementation); + return; + } + } + + /** + * @notice Upgrade the app account to a new app beacon and its implementation. + * @param _appBeacon The address of the new app beacon. + * @dev Requires the sender to be the main account. + * If the new app beacon has the same implementation as the current one, then no upgrade is performed. + */ + function upgradeAppVersion(address _appBeacon) external requiresMainAccountSender { + if (_appBeacon == AppBase._getAppBeacon()) { + revert SameAddress(); + } + + AppBase._setAppBeacon(_appBeacon); + address latestAppImplementation = IAppBeaconBase(_appBeacon).getLatestAppImplementation(); + if (latestAppImplementation != IUUPSImplementation(address(this)).getImplementation()) { + _upgradeToLatestImplementation(latestAppImplementation); + } + } + + /*/////////////////////////////////////////////////////////////// + INTERNAL FUNCTIONS + ///////////////////////////////////////////////////////////////*/ + + /** + * @notice Upgrade the account implementation to the latest version + * @dev Checks are done in the main account + * @dev requires the sender to be the main account + * This is marked as virtual so that it can be overridden by an app implementation to perform migration steps + */ + function _upgradeToLatestImplementation(address _newImplementation) internal virtual { + _upgradeTo(_newImplementation); + } + + /** + * @notice Transfer Ether to the main account from the app account. + * @param _amount The amount of Ether to transfer. + */ + function _transferEtherToMainAccount(uint256 _amount) internal { + emit EtherTransferredToMainAccount(_amount); + (bool success,) = payable(AppBase._getMainAccount()).call{ value: _amount }(""); + if (!success) revert ETHTransferFailed(); + } + + /** + * @notice Transfer ERC20 tokens to the main account from the app account. + * @param _token The address of the ERC20 token. + * @param _amount The amount of tokens to transfer. + */ + function _transferERC20ToMainAccount(address _token, uint256 _amount) internal { + emit ERC20TransferredToMainAccount(_token, _amount); + SafeERC20.safeTransfer(IERC20(_token), AppBase._getMainAccount(), _amount); + } + + /** + * @notice Transfer ERC721 tokens to the main account from the app account. + * @param _token The address of the ERC721 token. + * @param _tokenId The token ID to transfer. + */ + function _transferERC721ToMainAccount(address _token, uint256 _tokenId) internal { + emit ERC721TransferredToMainAccount(_token, _tokenId); + IERC721(_token).safeTransferFrom(address(this), AppBase._getMainAccount(), _tokenId); + } + + /** + * @notice Transfer ERC1155 tokens to the main account from the app account. + * @param _token The address of the ERC1155 token. + * @param _tokenId The token ID to transfer. + * @param _amount The amount of tokens to transfer. + * @param _data Additional data to pass in the transfer. + */ + function _transferERC1155ToMainAccount( + address _token, + uint256 _tokenId, + uint256 _amount, + bytes calldata _data + ) + internal + { + emit ERC1155TransferredToMainAccount(_token, _tokenId, _amount, _data); + IERC1155(_token).safeTransferFrom(address(this), AppBase._getMainAccount(), _tokenId, _amount, _data); + } + + /** + * @notice Transfers multiple ERC1155 tokens to the main account from the app account. + * @param _token The address of the ERC1155 token. + * @param _tokenIds The IDs of the ERC1155 token. + * @param _amounts The amounts of tokens to transfer. + * @param _data Data to send with the transfer. + */ + function _transferERC1155BatchToMainAccount( + address _token, + uint256[] calldata _tokenIds, + uint256[] calldata _amounts, + bytes calldata _data + ) + internal + { + emit ERC1155BatchTransferredToMainAccount(_token, _tokenIds, _amounts, _data); + IERC1155(_token).safeBatchTransferFrom(address(this), AppBase._getMainAccount(), _tokenIds, _amounts, _data); + } +} diff --git a/src/apps/base/AppBase.sol b/src/apps/base/AppBase.sol new file mode 100644 index 0000000..7801e38 --- /dev/null +++ b/src/apps/base/AppBase.sol @@ -0,0 +1,98 @@ +// c=< +// | +// | ////\ 1@2 +// @@ | /___\** @@@2 @@@@@@@@@@@@@@@@@@@@@@ +// @@@ | |~L~ |* @@@@@@ @@@ @@@@@ @@@@ @@@ @@@@ @@@ @@@@@@@@ @@@@ @@@@ @@@ @@@@@@@@@ @@@@ @@@@ +// @@@@@ | \=_/8 @@@@1@@ @@@ @@@@@ @@@@ @@@@ @@@ @@@@@ @@@ @@@@@@@@@ @@@@ @@@@@ @@@@ @@@@@@@@@ @@@@ +// @@@@ +// @@@@@@| _ /| |\__ @@@@@@@@2 @@@ @@@@@ @@@@ @@@@ @@@ @@@@@@@ @@@ @@@@ @@@@ @@@@@@ @@@@ @@@ @@@@@@@ +// 1@@@@@@|\ \___/) @@1@@@@@2 ~~~ ~~~~~ @@@@ ~~@@ ~~~ ~~~~~~~~~~~ ~~~~ ~~~~ ~~~~~~~~~~~ ~@@ @@@@@ +// 2@@@@@ | \ \ / | @@@@@@2 @@@ @@@@@ @@@@ @@@@ @@@ @@@@@@@@@@@ @@@@@@@@@ @@@@ @@@@@@@@@@@ @@@@@@@@@ @@@@@ +// 2@@@@ |_ > <|__ @@1@12 @@@ @@@@@ @@@@ @@@@ @@@ @@@@ @@@@@@ @@@@ @@@@ @@@@ @@@@@@ @@@ @@@@@@@ +// @@@@ / _| / \/ \ @@1@ @@@ @@@ @@@@ @@@@ @@@ @@@@ @@@@@ @@@@ @@@@ @@@@ @@@@@ @@@@@@@@@ @@@@ +// @@@@ +// @@ / |^\/ | | @@1 @@@ @@@@ @@@@ @@@ @@@@ @@@ @@@@ @@@@ @@@ @@@@ @@@@@@@@@ @@@@ @@@@ +// / / ---- \ \\\= @@ @@@@@@@@@@@@@@@@@@@@@@ +// \___/ -------- ~~ @@@ +// @@ | | | | -- @@ +// ———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— + +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.21; + +import { Error } from "src/libraries/Error.sol"; + +/** + * @title AppBase storage struct + */ +library AppBase { + struct Data { + address mainAccount; // main account address + address appBeacon; // Address of beacon for app configuration + } + + /*/////////////////////////////////////////////////////////////// + EVENTS / ERRORS + ///////////////////////////////////////////////////////////////*/ + + event MainAccountSet(address mainAccount); + event AppBeaconSet(address appBeacon); + + /** + * @dev Returns the account stored at the specified account id. + */ + function getStorage() internal pure returns (Data storage data) { + bytes32 s = keccak256(abi.encode("io.infinex.AppBase")); + assembly { + data.slot := s + } + } + + /*/////////////////////////////////////////////////////////////// + VIEW FUNCTIONS + ///////////////////////////////////////////////////////////////*/ + + /** + * @notice Get the Main Account. + * @return The Main Account address. + */ + function _getMainAccount() internal view returns (address) { + Data storage data = getStorage(); + return data.mainAccount; + } + + /** + * @notice Get the App Beacon. + * @return The App Beacon. + */ + function _getAppBeacon() internal view returns (address) { + Data storage data = getStorage(); + return data.appBeacon; + } + + /*/////////////////////////////////////////////////////////////// + MUTATIVE FUNCTIONS + ///////////////////////////////////////////////////////////////*/ + + /** + * @notice Set the Main Account address. + * @param _mainAccount The address to be set as Main Account. + */ + function _setMainAccount(address _mainAccount) internal { + Data storage data = getStorage(); + if (_mainAccount == address(0)) revert Error.NullAddress(); + emit MainAccountSet(_mainAccount); + data.mainAccount = _mainAccount; + } + + /** + * @notice Set an app beacon for the account. + * @param _appBeacon The app beacon associated with the account. + */ + function _setAppBeacon(address _appBeacon) internal { + Data storage data = getStorage(); + if (_appBeacon == address(0)) revert Error.NullAddress(); + emit AppBeaconSet(_appBeacon); + data.appBeacon = _appBeacon; + } +} diff --git a/src/apps/base/AppBeaconBase.sol b/src/apps/base/AppBeaconBase.sol new file mode 100644 index 0000000..d6e6ba0 --- /dev/null +++ b/src/apps/base/AppBeaconBase.sol @@ -0,0 +1,91 @@ +// c=< +// | +// | ////\ 1@2 +// @@ | /___\** @@@2 @@@@@@@@@@@@@@@@@@@@@@ +// @@@ | |~L~ |* @@@@@@ @@@ @@@@@ @@@@ @@@ @@@@ @@@ @@@@@@@@ @@@@ @@@@ @@@ @@@@@@@@@ @@@@ @@@@ +// @@@@@ | \=_/8 @@@@1@@ @@@ @@@@@ @@@@ @@@@ @@@ @@@@@ @@@ @@@@@@@@@ @@@@ @@@@@ @@@@ @@@@@@@@@ @@@@ +// @@@@ +// @@@@@@| _ /| |\__ @@@@@@@@2 @@@ @@@@@ @@@@ @@@@ @@@ @@@@@@@ @@@ @@@@ @@@@ @@@@@@ @@@@ @@@ @@@@@@@ +// 1@@@@@@|\ \___/) @@1@@@@@2 ~~~ ~~~~~ @@@@ ~~@@ ~~~ ~~~~~~~~~~~ ~~~~ ~~~~ ~~~~~~~~~~~ ~@@ @@@@@ +// 2@@@@@ | \ \ / | @@@@@@2 @@@ @@@@@ @@@@ @@@@ @@@ @@@@@@@@@@@ @@@@@@@@@ @@@@ @@@@@@@@@@@ @@@@@@@@@ @@@@@ +// 2@@@@ |_ > <|__ @@1@12 @@@ @@@@@ @@@@ @@@@ @@@ @@@@ @@@@@@ @@@@ @@@@ @@@@ @@@@@@ @@@ @@@@@@@ +// @@@@ / _| / \/ \ @@1@ @@@ @@@ @@@@ @@@@ @@@ @@@@ @@@@@ @@@@ @@@@ @@@@ @@@@@ @@@@@@@@@ @@@@ +// @@@@ +// @@ / |^\/ | | @@1 @@@ @@@@ @@@@ @@@ @@@@ @@@ @@@@ @@@@ @@@ @@@@ @@@@@@@@@ @@@@ @@@@ +// / / ---- \ \\\= @@ @@@@@@@@@@@@@@@@@@@@@@ +// \___/ -------- ~~ @@@ +// @@ | | | | -- @@ +// ———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— + +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.21; + +import { Ownable2Step, Ownable } from "@openzeppelin/contracts/access/Ownable2Step.sol"; +import { ERC165 } from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; +import { IERC165 } from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; + +import { IAppAccountBase } from "src/interfaces/apps/base/IAppAccountBase.sol"; +import { IAppBeaconBase } from "src/interfaces/apps/base/IAppBeaconBase.sol"; + +abstract contract AppBeaconBase is IAppBeaconBase, ERC165, Ownable2Step { + address public immutable APP_IMPLEMENTATION; + AppBeaconConfig public appBeaconConfig; + + constructor(address _owner, address _appImplementation, string memory _appName) Ownable(_owner) { + appBeaconConfig.appName = _appName; + if (!IERC165(_appImplementation).supportsInterface(type(IAppAccountBase).interfaceId)) { + revert InvalidAppAccountImplementation(); + } + APP_IMPLEMENTATION = _appImplementation; + appBeaconConfig.latestAppBeacon = address(this); + } + + /*/////////////////////////////////////////////////////////////// + VIEW FUNCTIONS + ///////////////////////////////////////////////////////////////*/ + + /** + * @dev See {IERC165-supportsInterface}. + */ + function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165) returns (bool) { + return interfaceId == type(IAppBeaconBase).interfaceId || super.supportsInterface(interfaceId); + } + + /** + * @notice Gets the name of the app associated to the beacon. + * @return The name of the app beacon. + */ + function getAppName() external view returns (string memory) { + return appBeaconConfig.appName; + } + + /** + * @notice Gets the latest app implementation. + * @return The address of the latest app implementation. + */ + function getLatestAppImplementation() external view returns (address) { + return APP_IMPLEMENTATION; + } + + /** + * @notice Gets the latest beacon address for the app. + * @return The address of the latest app beacon. + */ + function getLatestAppBeacon() external view returns (address) { + return appBeaconConfig.latestAppBeacon; + } + + /*/////////////////////////////////////////////////////////////// + MUTATIVE FUNCTIONS + ///////////////////////////////////////////////////////////////*/ + + /** + * @notice Sets the latest app beacon address. + * @param _latestAppBeacon The address of the latest app beacon associated with the app. + */ + function setLatestAppBeacon(address _latestAppBeacon) external onlyOwner { + if (_latestAppBeacon == address(0)) revert ZeroAddress(); + emit LatestAppBeaconSet(_latestAppBeacon); + appBeaconConfig.latestAppBeacon = _latestAppBeacon; + } +} diff --git a/src/apps/base/AppERC2771Context.sol b/src/apps/base/AppERC2771Context.sol new file mode 100644 index 0000000..f473b0f --- /dev/null +++ b/src/apps/base/AppERC2771Context.sol @@ -0,0 +1,68 @@ +// c=< +// | +// | ////\ 1@2 +// @@ | /___\** @@@2 @@@@@@@@@@@@@@@@@@@@@@ +// @@@ | |~L~ |* @@@@@@ @@@ @@@@@ @@@@ @@@ @@@@ @@@ @@@@@@@@ @@@@ @@@@ @@@ @@@@@@@@@ @@@@ @@@@ +// @@@@@ | \=_/8 @@@@1@@ @@@ @@@@@ @@@@ @@@@ @@@ @@@@@ @@@ @@@@@@@@@ @@@@ @@@@@ @@@@ @@@@@@@@@ @@@@ +// @@@@ +// @@@@@@| _ /| |\__ @@@@@@@@2 @@@ @@@@@ @@@@ @@@@ @@@ @@@@@@@ @@@ @@@@ @@@@ @@@@@@ @@@@ @@@ @@@@@@@ +// 1@@@@@@|\ \___/) @@1@@@@@2 ~~~ ~~~~~ @@@@ ~~@@ ~~~ ~~~~~~~~~~~ ~~~~ ~~~~ ~~~~~~~~~~~ ~@@ @@@@@ +// 2@@@@@ | \ \ / | @@@@@@2 @@@ @@@@@ @@@@ @@@@ @@@ @@@@@@@@@@@ @@@@@@@@@ @@@@ @@@@@@@@@@@ @@@@@@@@@ @@@@@ +// 2@@@@ |_ > <|__ @@1@12 @@@ @@@@@ @@@@ @@@@ @@@ @@@@ @@@@@@ @@@@ @@@@ @@@@ @@@@@@ @@@ @@@@@@@ +// @@@@ / _| / \/ \ @@1@ @@@ @@@ @@@@ @@@@ @@@ @@@@ @@@@@ @@@@ @@@@ @@@@ @@@@@ @@@@@@@@@ @@@@ +// @@@@ +// @@ / |^\/ | | @@1 @@@ @@@@ @@@@ @@@ @@@@ @@@ @@@@ @@@@ @@@ @@@@ @@@@@@@@@ @@@@ @@@@ +// / / ---- \ \\\= @@ @@@@@@@@@@@@@@@@@@@@@@ +// \___/ -------- ~~ @@@ +// @@ | | | | -- @@ +// ———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— + +// SPDX-License-Identifier: MIT +// Originally sourced from OpenZeppelin Contracts (last updated v4.9.3) (metatx/ERC2771Context.sol) +pragma solidity >=0.8.21; + +import { AppBase } from "src/apps/base/AppBase.sol"; +import { IBaseModule } from "src/interfaces/accounts/IBaseModule.sol"; + +/** + * @dev Context variant with ERC2771 support. + */ +library AppERC2771Context { + function _msgSender() internal view returns (address) { + uint256 calldataLength = msg.data.length; + uint256 contextSuffixLength = _contextSuffixLength(); + if (_isTrustedForwarder(msg.sender) && calldataLength >= contextSuffixLength) { + return address(bytes20(msg.data[calldataLength - contextSuffixLength:])); + } else { + return msg.sender; + } + } + + // slither-disable-start dead-code + function _msgData() internal view returns (bytes calldata) { + uint256 calldataLength = msg.data.length; + uint256 contextSuffixLength = _contextSuffixLength(); + if (_isTrustedForwarder(msg.sender) && calldataLength >= contextSuffixLength) { + return msg.data[:calldataLength - contextSuffixLength]; + } else { + return msg.data; + } + } + + /** + * @notice Checks if a forwarder is trusted. + * @param forwarder The address of the forwarder to check. + * @return A boolean indicating whether the forwarder is trusted or not. + */ + function _isTrustedForwarder(address forwarder) internal view returns (bool) { + return IBaseModule(AppBase._getMainAccount()).isTrustedForwarder(forwarder); + } + + /** + * @dev ERC-2771 specifies the context as being a single address (20 bytes). + */ + function _contextSuffixLength() internal pure returns (uint256) { + return 20; + } + // slither-disable-end dead-code +} diff --git a/src/apps/base/AppSecurityModifiers.sol b/src/apps/base/AppSecurityModifiers.sol new file mode 100644 index 0000000..9bd2199 --- /dev/null +++ b/src/apps/base/AppSecurityModifiers.sol @@ -0,0 +1,108 @@ +// c=< +// | +// | ////\ 1@2 +// @@ | /___\** @@@2 @@@@@@@@@@@@@@@@@@@@@@ +// @@@ | |~L~ |* @@@@@@ @@@ @@@@@ @@@@ @@@ @@@@ @@@ @@@@@@@@ @@@@ @@@@ @@@ @@@@@@@@@ @@@@ @@@@ +// @@@@@ | \=_/8 @@@@1@@ @@@ @@@@@ @@@@ @@@@ @@@ @@@@@ @@@ @@@@@@@@@ @@@@ @@@@@ @@@@ @@@@@@@@@ @@@@ +// @@@@ +// @@@@@@| _ /| |\__ @@@@@@@@2 @@@ @@@@@ @@@@ @@@@ @@@ @@@@@@@ @@@ @@@@ @@@@ @@@@@@ @@@@ @@@ @@@@@@@ +// 1@@@@@@|\ \___/) @@1@@@@@2 ~~~ ~~~~~ @@@@ ~~@@ ~~~ ~~~~~~~~~~~ ~~~~ ~~~~ ~~~~~~~~~~~ ~@@ @@@@@ +// 2@@@@@ | \ \ / | @@@@@@2 @@@ @@@@@ @@@@ @@@@ @@@ @@@@@@@@@@@ @@@@@@@@@ @@@@ @@@@@@@@@@@ @@@@@@@@@ @@@@@ +// 2@@@@ |_ > <|__ @@1@12 @@@ @@@@@ @@@@ @@@@ @@@ @@@@ @@@@@@ @@@@ @@@@ @@@@ @@@@@@ @@@ @@@@@@@ +// @@@@ / _| / \/ \ @@1@ @@@ @@@ @@@@ @@@@ @@@ @@@@ @@@@@ @@@@ @@@@ @@@@ @@@@@ @@@@@@@@@ @@@@ +// @@@@ +// @@ / |^\/ | | @@1 @@@ @@@@ @@@@ @@@ @@@@ @@@ @@@@ @@@@ @@@ @@@@ @@@@@@@@@ @@@@ @@@@ +// / / ---- \ \\\= @@ @@@@@@@@@@@@@@@@@@@@@@ +// \___/ -------- ~~ @@@ +// @@ | | | | -- @@ +// ———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— + +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.21; + +import { AppERC2771Context } from "src/apps/base/AppERC2771Context.sol"; +import { AppBase } from "src/apps/base/AppBase.sol"; + +import { IAccountUtilsModule } from "src/interfaces/accounts/IAccountUtilsModule.sol"; + +contract AppSecurityModifiers { + /*/////////////////////////////////////////////////////////////// + ERRORS + ///////////////////////////////////////////////////////////////*/ + + error InvalidKeySignature(address from); + + /*/////////////////////////////////////////////////////////////// + SECURITY CHECK MODIFIERS + ///////////////////////////////////////////////////////////////*/ + + /** + * @notice Modifier to check if the sender is the main account. + */ + modifier requiresMainAccountSender() { + if (msg.sender != AppBase._getMainAccount()) { + revert InvalidKeySignature(msg.sender); + } + + _; + } + + /** + * @notice Modifier to check if the sender is an sudo key. + */ + modifier requiresSudoKeySender() { + if (!_isValidSudoKey(AppERC2771Context._msgSender())) { + revert InvalidKeySignature(AppERC2771Context._msgSender()); + } + + _; + } + + /** + * @notice Modifier to check if the sender is a sudo or operation key. + * If not, it reverts with an error message. + */ + modifier requiresAuthorizedOperationsParty() { + if (!_isAuthorizedOperationsParty(AppERC2771Context._msgSender())) { + revert InvalidKeySignature(AppERC2771Context._msgSender()); + } + + _; + } + + /** + * @notice Modifier to check if the sender is an sudo key, a recovery key or a trusted recovery keeper. + * If not, it reverts with an error message. + */ + modifier requiresAuthorizedRecoveryParty() { + if (!_isAuthorizedRecoveryParty(AppERC2771Context._msgSender())) { + revert InvalidKeySignature(AppERC2771Context._msgSender()); + } + + _; + } + + /** + * @notice Validate with the parent account if a key is a sudoKey. + * @param _key The key to check. + */ + function _isValidSudoKey(address _key) internal view returns (bool) { + return IAccountUtilsModule(AppBase._getMainAccount()).isValidSudoKey(_key); + } + + /** + * @notice Validate with the parent account if a key is an authorized operations party. + * @param _key The key to check. + */ + function _isAuthorizedOperationsParty(address _key) internal view returns (bool) { + return IAccountUtilsModule(AppBase._getMainAccount()).isAuthorizedOperationsParty(_key); + } + + /** + * @notice Validate with the parent account if a key is an authorized recovery party. + * @param _key The key to check. + */ + function _isAuthorizedRecoveryParty(address _key) internal view returns (bool) { + return IAccountUtilsModule(AppBase._getMainAccount()).isAuthorizedRecoveryParty(_key); + } +} diff --git a/src/apps/curve/CurveAppError.sol b/src/apps/curve/CurveAppError.sol new file mode 100644 index 0000000..d3edc2a --- /dev/null +++ b/src/apps/curve/CurveAppError.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.21; + +library CurveAppError { + /*/////////////////////////////////////////////////////////////// + GENERIC + ///////////////////////////////////////////////////////////////*/ + + error TokenIndexMismatch(); + error InvalidPoolAddress(address poolAddress); + error UnsupportedPool(address poolAddress); + error InvalidToken(); + error ZeroAddress(); +} diff --git a/src/apps/curve/CurveStableSwapApp.sol b/src/apps/curve/CurveStableSwapApp.sol new file mode 100644 index 0000000..5b8a9f0 --- /dev/null +++ b/src/apps/curve/CurveStableSwapApp.sol @@ -0,0 +1,302 @@ +// c=< +// | +// | ////\ 1@2 +// @@ | /___\** @@@2 @@@@@@@@@@@@@@@@@@@@@@ +// @@@ | |~L~ |* @@@@@@ @@@ @@@@@ @@@@ @@@ @@@@ @@@ @@@@@@@@ @@@@ @@@@ @@@ @@@@@@@@@ @@@@ @@@@ +// @@@@@ | \=_/8 @@@@1@@ @@@ @@@@@ @@@@ @@@@ @@@ @@@@@ @@@ @@@@@@@@@ @@@@ @@@@@ @@@@ @@@@@@@@@ @@@@ +// @@@@ +// @@@@@@| _ /| |\__ @@@@@@@@2 @@@ @@@@@ @@@@ @@@@ @@@ @@@@@@@ @@@ @@@@ @@@@ @@@@@@ @@@@ @@@ @@@@@@@ +// 1@@@@@@|\ \___/) @@1@@@@@2 ~~~ ~~~~~ @@@@ ~~@@ ~~~ ~~~~~~~~~~~ ~~~~ ~~~~ ~~~~~~~~~~~ ~@@ @@@@@ +// 2@@@@@ | \ \ / | @@@@@@2 @@@ @@@@@ @@@@ @@@@ @@@ @@@@@@@@@@@ @@@@@@@@@ @@@@ @@@@@@@@@@@ @@@@@@@@@ @@@@@ +// 2@@@@ |_ > <|__ @@1@12 @@@ @@@@@ @@@@ @@@@ @@@ @@@@ @@@@@@ @@@@ @@@@ @@@@ @@@@@@ @@@ @@@@@@@ +// @@@@ / _| / \/ \ @@1@ @@@ @@@ @@@@ @@@@ @@@ @@@@ @@@@@ @@@@ @@@@ @@@@ @@@@@ @@@@@@@@@ @@@@ +// @@@@ +// @@ / |^\/ | | @@1 @@@ @@@@ @@@@ @@@ @@@@ @@@ @@@@ @@@@ @@@ @@@@ @@@@@@@@@ @@@@ @@@@ +// / / ---- \ \\\= @@ @@@@@@@@@@@@@@@@@@@@@@ +// \___/ -------- ~~ @@@ +// @@ | | | | -- @@ +// ———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.21; + +import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +import { AppAccountBase } from "src/apps/base/AppAccountBase.sol"; +import { AppBase } from "src/apps/base/AppBase.sol"; +import { CurveAppError } from "src/apps/curve/CurveAppError.sol"; + +import { ICurveStableSwapApp } from "src/interfaces/curve/ICurveStableSwapApp.sol"; +import { ICurveStableSwapAppBeacon } from "src/interfaces/curve/ICurveStableSwapAppBeacon.sol"; +import { ICurveStableSwapNG } from "src/interfaces/curve/ICurveStableSwapNG.sol"; +import { ICurveStableSwapFactoryNG } from "src/interfaces/curve/ICurveStableSwapFactoryNG.sol"; + +contract CurveStableSwapApp is AppAccountBase, ICurveStableSwapApp { + using SafeERC20 for IERC20; + + constructor() { + _disableInitializers(); + } + + /*/////////////////////////////////////////////////////////////// + MUTATIVE FUNCTIONS + ///////////////////////////////////////////////////////////////*/ + + /** + * @notice Allows the authorized operations party to exchange tokens on the stable swap pool. + * @param _stableSwapPool The address of the stable swap pool. + * @param _fromToken The address of the ERC20 token provided to exchange. + * @param _toToken The address of the ERC20 token to receive from the exchange. + * @param _fromAmount The amount of tokens to exchange. + * @param _minToAmount The minimum amount of tokens to receive. + */ + function exchange( + address _stableSwapPool, + address _fromToken, + address _toToken, + uint256 _fromAmount, + uint256 _minToAmount + ) + external + nonReentrant + requiresAuthorizedOperationsParty + { + _validatePoolAddress(_stableSwapPool); + // convert token addresses to pool indices, reverts if pool and tokens aren't valid + (int128 fromTokenIndex, int128 toTokenIndex,) = ICurveStableSwapFactoryNG( + _getAppBeacon().curveStableswapFactoryNG() + ).get_coin_indices(_stableSwapPool, _fromToken, _toToken); + IERC20(_fromToken).approve(_stableSwapPool, _fromAmount); + uint256 receivedAmount = + ICurveStableSwapNG(_stableSwapPool).exchange(fromTokenIndex, toTokenIndex, _fromAmount, _minToAmount); + emit TokensExchanged(_stableSwapPool, _fromToken, _toToken, _fromAmount, receivedAmount); + } + + /** + * @notice Adds liquidity to the specified pool + * @dev The arrays indices have to match the indices of the tokens in the pool. + * @param _stableSwapPool The address of the pool to add liquidity to. + * @param _tokens An array of token addresses to add as liquidity. + * @param _amounts An array of token amounts to add as liquidity. + * @param _minLPAmount The minimum amount of LP tokens to receive. + */ + function addLiquidity( + address _stableSwapPool, + address[] calldata _tokens, + uint256[] calldata _amounts, + uint256 _minLPAmount + ) + external + nonReentrant + requiresAuthorizedOperationsParty + { + _validatePoolAddress(_stableSwapPool); + address[] memory coins = + ICurveStableSwapFactoryNG(_getAppBeacon().curveStableswapFactoryNG()).get_coins(_stableSwapPool); + // check and approve the pool to add the tokens as liquidity + for (uint256 i = 0; i < _tokens.length; i++) { + if (coins[i] != _tokens[i]) revert CurveAppError.InvalidToken(); + IERC20(_tokens[i]).approve(_stableSwapPool, _amounts[i]); + } + + // provide the liquidity + uint256 lpAmount = ICurveStableSwapNG(_stableSwapPool).add_liquidity(_amounts, _minLPAmount); + emit LiquidityAdded(_stableSwapPool, _amounts, lpAmount); + } + + /** + * @notice Removes liquidity for a single token from the Curve stable swap pool. + * @param _stableSwapPool The address of the Curve stable swap pool. + * @param _tokenIndex The index of the token to remove liquidity. + * @param _lpAmount The amount of LP tokens to burn. + * @param _minReceiveAmount The minimum amount of tokens to receive in return. + * @return The amount of tokens received after removing liquidity. + */ + function removeSingleTokenLiquidity( + address _stableSwapPool, + int128 _tokenIndex, + uint256 _lpAmount, + uint256 _minReceiveAmount + ) + external + nonReentrant + requiresAuthorizedOperationsParty + returns (uint256) + { + return _removeSingleTokenLiquidity(_stableSwapPool, _tokenIndex, _lpAmount, _minReceiveAmount); + } + + /** + * @notice Withdraw coins from a Curve stable swap pool in an imbalanced amount. + * @param _stableSwapPool The address of the Curve stable swap pool. + * @param _lpAmount The max amount of LP tokens to burn. + * @param _amounts The amount of tokens to receive in return. + */ + function removeLiquidityImbalance( + address _stableSwapPool, + uint256 _lpAmount, + uint256[] calldata _amounts + ) + external + nonReentrant + requiresAuthorizedOperationsParty + { + _removeLiquidityImbalance(_stableSwapPool, _lpAmount, _amounts); + } + + /** + * @notice Swaps ERC20 tokens to USDC at the current exchange amount and then recovers to mainAccount + * @param _stableSwapPool The address of the stable swap pool. + * @param _fromToken The address of the ERC20 token to recover. + * @param _minToAmount The minimum amount of USDC to receive. + * @dev This function must be called by an authorized recovery party. + */ + function recoverERC20ToUSDC( + address _stableSwapPool, + address _fromToken, + uint256 _minToAmount + ) + external + nonReentrant + requiresAuthorizedRecoveryParty + { + _validatePoolAddress(_stableSwapPool); + uint256 balance = IERC20(_fromToken).balanceOf(address(this)); + ICurveStableSwapAppBeacon appBeacon = _getAppBeacon(); + address USDC = appBeacon.USDC(); + // convert token addresses to pool indices, reverts if pool and tokens aren't valid + (int128 fromTokenIndex, int128 toTokenIndex,) = ICurveStableSwapFactoryNG( + _getAppBeacon().curveStableswapFactoryNG() + ).get_coin_indices(_stableSwapPool, _fromToken, USDC); + IERC20(_fromToken).approve(_stableSwapPool, balance); + // swap to USDC + uint256 receivedAmount = + ICurveStableSwapNG(_stableSwapPool).exchange(fromTokenIndex, toTokenIndex, balance, _minToAmount); + emit TokensExchanged(_stableSwapPool, _fromToken, USDC, balance, receivedAmount); + uint256 USDCRecoverBalance = IERC20(USDC).balanceOf(address(this)); + emit ERC20RecoveredToMainAccount(USDC, USDCRecoverBalance); + _transferERC20ToMainAccount(USDC, USDCRecoverBalance); + } + + /** + * @notice Removes all Liquidity as USDC with specified slippage amount and then recovers to mainAccount + * @param _LPToken The address of the pool to remove liquidity from. + * @param _USDCIndex The address of the LP token and pool to recover from. + * @param _minReceiveAmount The minimum amount of USDC to receive. + * @dev This function must be called by an authorized recovery party. + */ + function recoverUSDCFromLP( + address _LPToken, + int128 _USDCIndex, + uint256 _minReceiveAmount + ) + external + nonReentrant + requiresAuthorizedRecoveryParty + { + // pool address is validated by _removeSingleTokenLiquidity + ICurveStableSwapAppBeacon appBeacon = _getAppBeacon(); + address USDC = appBeacon.USDC(); + + if (ICurveStableSwapNG(_LPToken).coins(uint256(uint128(_USDCIndex))) != USDC) { + revert CurveAppError.TokenIndexMismatch(); + } + + // recover funds + uint256 lpBalance = IERC20(_LPToken).balanceOf(address(this)); + + // the LP token is the pool address + _removeSingleTokenLiquidity(_LPToken, _USDCIndex, lpBalance, _minReceiveAmount); + + uint256 USDCBalance = IERC20(USDC).balanceOf(address(this)); + + emit ERC20RecoveredToMainAccount(USDC, USDCBalance); + _transferERC20ToMainAccount(USDC, USDCBalance); + } + + /** + * @notice Removes a single token from an LP for the purpose of recovery + * @param _LPToken The address of the LP token/pool to remove liquidity from. + * @param _tokenIndex The index of the token to remove from the liquidity pool + * @param _minToAmount The minimum amount of token to withdraw from the liquidity pool. + * @dev This function must be called by an authorized recovery party. + */ + function recoverERC20FromLP( + address _LPToken, + int128 _tokenIndex, + uint256 _minToAmount + ) + external + nonReentrant + requiresAuthorizedRecoveryParty + { + // pool address is validated by _removeSingleTokenLiquidity + // remove token from pool + uint256 lpBalance = IERC20(_LPToken).balanceOf(address(this)); + _removeSingleTokenLiquidity(_LPToken, _tokenIndex, lpBalance, _minToAmount); + } + + /*/////////////////////////////////////////////////////////////// + INTERNAL FUNCTIONS + ///////////////////////////////////////////////////////////////*/ + + /** + * @notice Removes liquidity for a single token from the Curve stable swap pool. + * @param _stableSwapPool The address of the Curve stable swap pool. + * @param _tokenIndex The index of the token to remove liquidity for. + * @param _lpAmount The amount of LP tokens to burn. + * @param _minReceiveAmount The minimum amount of tokens to receive in return. + * @return amountRemoved The amount of tokens that were removed from the pool. + */ + function _removeSingleTokenLiquidity( + address _stableSwapPool, + int128 _tokenIndex, + uint256 _lpAmount, + uint256 _minReceiveAmount + ) + internal + returns (uint256 amountRemoved) + { + _validatePoolAddress(_stableSwapPool); + amountRemoved = + ICurveStableSwapNG(_stableSwapPool).remove_liquidity_one_coin(_lpAmount, _tokenIndex, _minReceiveAmount); + emit LiquidityRemovedSingleToken(_stableSwapPool, amountRemoved, _lpAmount); + } + + /** + * @notice Withdraw coins from a Curve stable swap pool in an imbalanced amount. + * @param _stableSwapPool The address of the Curve stable swap pool. + * @param _lpAmount The max amount of LP tokens to be burned for the token amounts to be removed + * @param _amounts The amounts of tokens to be removed from the pool. + */ + function _removeLiquidityImbalance( + address _stableSwapPool, + uint256 _lpAmount, + uint256[] calldata _amounts + ) + internal + { + _validatePoolAddress(_stableSwapPool); + uint256 amountLPBurnt = ICurveStableSwapNG(_stableSwapPool).remove_liquidity_imbalance(_amounts, _lpAmount); + emit LiquidityRemoved(_stableSwapPool, _amounts, amountLPBurnt); + } + + function _validatePoolAddress(address _stableSwapPool) internal view { + ICurveStableSwapAppBeacon appBeacon = _getAppBeacon(); + if (!appBeacon.isSupportedPool(_stableSwapPool)) revert CurveAppError.UnsupportedPool(_stableSwapPool); + if ( + ICurveStableSwapFactoryNG(appBeacon.curveStableswapFactoryNG()).get_implementation_address(_stableSwapPool) + == address(0) + ) { + revert CurveAppError.InvalidPoolAddress(_stableSwapPool); + } + } + + /** + * @dev Returns the beacon contract for the Curve StableSwap app. + * @return The beacon contract for the Curve StableSwap app. + */ + function _getAppBeacon() internal view returns (ICurveStableSwapAppBeacon) { + return (ICurveStableSwapAppBeacon(AppBase._getAppBeacon())); + } +} diff --git a/src/apps/curve/CurveStableSwapAppBeacon.sol b/src/apps/curve/CurveStableSwapAppBeacon.sol new file mode 100644 index 0000000..3dc4ced --- /dev/null +++ b/src/apps/curve/CurveStableSwapAppBeacon.sol @@ -0,0 +1,83 @@ +// c=< +// | +// | ////\ 1@2 +// @@ | /___\** @@@2 @@@@@@@@@@@@@@@@@@@@@@ +// @@@ | |~L~ |* @@@@@@ @@@ @@@@@ @@@@ @@@ @@@@ @@@ @@@@@@@@ @@@@ @@@@ @@@ @@@@@@@@@ @@@@ @@@@ +// @@@@@ | \=_/8 @@@@1@@ @@@ @@@@@ @@@@ @@@@ @@@ @@@@@ @@@ @@@@@@@@@ @@@@ @@@@@ @@@@ @@@@@@@@@ @@@@ +// @@@@ +// @@@@@@| _ /| |\__ @@@@@@@@2 @@@ @@@@@ @@@@ @@@@ @@@ @@@@@@@ @@@ @@@@ @@@@ @@@@@@ @@@@ @@@ @@@@@@@ +// 1@@@@@@|\ \___/) @@1@@@@@2 ~~~ ~~~~~ @@@@ ~~@@ ~~~ ~~~~~~~~~~~ ~~~~ ~~~~ ~~~~~~~~~~~ ~@@ @@@@@ +// 2@@@@@ | \ \ / | @@@@@@2 @@@ @@@@@ @@@@ @@@@ @@@ @@@@@@@@@@@ @@@@@@@@@ @@@@ @@@@@@@@@@@ @@@@@@@@@ @@@@@ +// 2@@@@ |_ > <|__ @@1@12 @@@ @@@@@ @@@@ @@@@ @@@ @@@@ @@@@@@ @@@@ @@@@ @@@@ @@@@@@ @@@ @@@@@@@ +// @@@@ / _| / \/ \ @@1@ @@@ @@@ @@@@ @@@@ @@@ @@@@ @@@@@ @@@@ @@@@ @@@@ @@@@@ @@@@@@@@@ @@@@ +// @@@@ +// @@ / |^\/ | | @@1 @@@ @@@@ @@@@ @@@ @@@@ @@@ @@@@ @@@@ @@@ @@@@ @@@@@@@@@ @@@@ @@@@ +// / / ---- \ \\\= @@ @@@@@@@@@@@@@@@@@@@@@@ +// \___/ -------- ~~ @@@ +// @@ | | | | -- @@ +// ———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.21; + +import { AppBeaconBase } from "src/apps/base/AppBeaconBase.sol"; +import { ICurveStableSwapFactoryNG } from "src/interfaces/curve/ICurveStableSwapFactoryNG.sol"; +import { ICurveStableSwapNG } from "src/interfaces/curve/ICurveStableSwapNG.sol"; +import { ICurveStableSwapAppBeacon } from "src/interfaces/curve/ICurveStableSwapAppBeacon.sol"; + +import { CurveAppError } from "src/apps/curve/CurveAppError.sol"; + +contract CurveStableSwapAppBeacon is AppBeaconBase, ICurveStableSwapAppBeacon { + address public immutable curveStableswapFactoryNG; + mapping(address => bool) public isSupportedPool; + address public immutable USDC; + + constructor( + address _owner, + address _latestAppImplementation, + address _curveStableswapFactoryNG, + address _usdc + ) + AppBeaconBase(_owner, _latestAppImplementation, "CurveStableswap") + { + if (_curveStableswapFactoryNG == address(0)) revert CurveAppError.ZeroAddress(); + if (_usdc == address(0)) revert CurveAppError.ZeroAddress(); + curveStableswapFactoryNG = _curveStableswapFactoryNG; + USDC = _usdc; + } + + /** + * @notice Get the pool data for the given tokens. Data will be empty if type is underyling + * @param _fromToken The address of the token to swap from. + * @param _toToken The address of the token to swap to. + * @return poolData The pool data for the given tokens. + */ + function getPoolDatafromTokens( + address _fromToken, + address _toToken, + uint256 _fromAmount + ) + public + view + returns (PoolData memory poolData) + { + poolData.pool = ICurveStableSwapFactoryNG(curveStableswapFactoryNG).find_pool_for_coins(_fromToken, _toToken); + poolData.tokens = ICurveStableSwapFactoryNG(curveStableswapFactoryNG).get_coins(poolData.pool); + (poolData.fromTokenIndex, poolData.toTokenIndex, poolData.isUnderlying) = + ICurveStableSwapFactoryNG(curveStableswapFactoryNG).get_coin_indices(poolData.pool, _fromToken, _toToken); + poolData.balances = ICurveStableSwapFactoryNG(curveStableswapFactoryNG).get_balances(poolData.pool); + poolData.decimals = ICurveStableSwapFactoryNG(curveStableswapFactoryNG).get_decimals(poolData.pool); + poolData.amountReceived = + ICurveStableSwapNG(poolData.pool).get_dy(poolData.fromTokenIndex, poolData.toTokenIndex, _fromAmount); + } + + /** + * @notice A safety feature to limit the pools that can be used by the app to only vetted and suppported pools + * @dev Only the contract owner can call this function. + * @param _pool The address of the pool. + * @param _supported The supported status of the pool. + */ + function setIsSupportedPool(address _pool, bool _supported) external onlyOwner { + if (_pool == address(0)) revert CurveAppError.ZeroAddress(); + isSupportedPool[_pool] = _supported; + } +} diff --git a/src/apps/nitro/RouterNitroApp.sol b/src/apps/nitro/RouterNitroApp.sol new file mode 100644 index 0000000..15b08e7 --- /dev/null +++ b/src/apps/nitro/RouterNitroApp.sol @@ -0,0 +1,141 @@ +// c=< +// | +// | ////\ 1@2 +// @@ | /___\** @@@2 @@@@@@@@@@@@@@@@@@@@@@ +// @@@ | |~L~ |* @@@@@@ @@@ @@@@@ @@@@ @@@ @@@@ @@@ @@@@@@@@ @@@@ @@@@ @@@ @@@@@@@@@ @@@@ @@@@ +// @@@@@ | \=_/8 @@@@1@@ @@@ @@@@@ @@@@ @@@@ @@@ @@@@@ @@@ @@@@@@@@@ @@@@ @@@@@ @@@@ @@@@@@@@@ @@@@ +// @@@@ +// @@@@@@| _ /| |\__ @@@@@@@@2 @@@ @@@@@ @@@@ @@@@ @@@ @@@@@@@ @@@ @@@@ @@@@ @@@@@@ @@@@ @@@ @@@@@@@ +// 1@@@@@@|\ \___/) @@1@@@@@2 ~~~ ~~~~~ @@@@ ~~@@ ~~~ ~~~~~~~~~~~ ~~~~ ~~~~ ~~~~~~~~~~~ ~@@ @@@@@ +// 2@@@@@ | \ \ / | @@@@@@2 @@@ @@@@@ @@@@ @@@@ @@@ @@@@@@@@@@@ @@@@@@@@@ @@@@ @@@@@@@@@@@ @@@@@@@@@ @@@@@ +// 2@@@@ |_ > <|__ @@1@12 @@@ @@@@@ @@@@ @@@@ @@@ @@@@ @@@@@@ @@@@ @@@@ @@@@ @@@@@@ @@@ @@@@@@@ +// @@@@ / _| / \/ \ @@1@ @@@ @@@ @@@@ @@@@ @@@ @@@@ @@@@@ @@@@ @@@@ @@@@ @@@@@ @@@@@@@@@ @@@@ +// @@@@ +// @@ / |^\/ | | @@1 @@@ @@@@ @@@@ @@@ @@@@ @@@ @@@@ @@@@ @@@ @@@@ @@@@@@@@@ @@@@ @@@@ +// / / ---- \ \\\= @@ @@@@@@@@@@@@@@@@@@@@@@ +// \___/ -------- ~~ @@@ +// @@ | | | | -- @@ +// ———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.21; + +import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +import { AppAccountBase } from "src/apps/base/AppAccountBase.sol"; +import { AppBase } from "src/apps/base/AppBase.sol"; + +import { IRouterNitroAppBeacon } from "src/interfaces/nitro/IRouterNitroAppBeacon.sol"; +import { IRouterNitroApp } from "src/interfaces/nitro/IRouterNitroApp.sol"; +import { IRouterNitroGateway } from "src/interfaces/nitro/IRouterNitroGateway.sol"; + +contract RouterNitroApp is AppAccountBase, IRouterNitroApp { + using SafeERC20 for IERC20; + + constructor() { + _disableInitializers(); + } + + /*/////////////////////////////////////////////////////////////// + MUTATIVE FUNCTIONS + ///////////////////////////////////////////////////////////////*/ + + /** + * @dev This function is used to bridge assets from one network to another. + * @param depositData The data related to the deposit. + * @param destToken The destination token address in bytes. + * @param recipient The recipient address in bytes. + */ + function bridgeAssets( + IRouterNitroGateway.DepositData memory depositData, + bytes memory destToken, + bytes memory recipient + ) + external + payable + nonReentrant + requiresAuthorizedOperationsParty + { + // Get the app beacon instance + IRouterNitroAppBeacon appBeacon = _getAppBeacon(); + + // Get the gateway instance from the app beacon + IRouterNitroGateway gateway = IRouterNitroGateway(appBeacon.routerNitroGateway()); + + // Approve the gateway to spend the source token on behalf of the contract + IERC20(depositData.srcToken).approve(address(gateway), depositData.amount); + + // Call the deposit function on the gateway + gateway.iDeposit{ value: msg.value }(depositData, destToken, recipient); + } + + /** + * @dev This function is used to update the bridge transaction data. + * @param srcToken The source token address. + * @param feeAmount The fee amount. + * @param depositId The deposit ID. + * @param initiatewithdrawal A boolean value indicating whether to initiate withdrawal or not. + */ + function updateBridgeTxnData( + address srcToken, + uint256 feeAmount, + uint256 depositId, + bool initiatewithdrawal + ) + external + payable + nonReentrant + requiresAuthorizedOperationsParty + { + // Get the app beacon instance + IRouterNitroAppBeacon appBeacon = _getAppBeacon(); + + // Get the gateway instance from the app beacon + IRouterNitroGateway gateway = IRouterNitroGateway(appBeacon.routerNitroGateway()); + + // Approve the gateway to spend the source token on behalf of the contract + IERC20(srcToken).approve(address(gateway), feeAmount); + + // Call the deposit info update function on the gateway + gateway.iDepositInfoUpdate{ value: msg.value }(srcToken, feeAmount, depositId, initiatewithdrawal); + } + + /** + * @dev This function is used to bridge assets with a message from one network to another. + * @param depositData The data related to the deposit. + * @param destToken The destination token address in bytes. + * @param recipient The recipient address in bytes. + * @param message The message to be sent along with the transaction. + */ + function bridgeAssetsWithMessage( + IRouterNitroGateway.DepositData memory depositData, + bytes memory destToken, + bytes memory recipient, + bytes memory message + ) + external + payable + nonReentrant + requiresAuthorizedOperationsParty + { + // Get the app beacon instance + IRouterNitroAppBeacon appBeacon = _getAppBeacon(); + + // Get the gateway instance from the app beacon + IRouterNitroGateway gateway = IRouterNitroGateway(appBeacon.routerNitroGateway()); + + // Approve the gateway to spend the source token on behalf of the contract + IERC20(depositData.srcToken).approve(address(gateway), depositData.amount); + + // Call the deposit function on the gateway with the message + gateway.iDepositMessage{ value: msg.value }(depositData, destToken, recipient, message); + } + + /** + * @dev Returns the beacon contract for the Curve StableSwap app. + * @return The beacon contract for the Curve StableSwap app. + */ + function _getAppBeacon() internal view returns (IRouterNitroAppBeacon) { + return (IRouterNitroAppBeacon(AppBase._getAppBeacon())); + } +} diff --git a/src/apps/nitro/RouterNitroAppBeacon.sol b/src/apps/nitro/RouterNitroAppBeacon.sol new file mode 100644 index 0000000..0b9f8ac --- /dev/null +++ b/src/apps/nitro/RouterNitroAppBeacon.sol @@ -0,0 +1,36 @@ +// c=< +// | +// | ////\ 1@2 +// @@ | /___\** @@@2 @@@@@@@@@@@@@@@@@@@@@@ +// @@@ | |~L~ |* @@@@@@ @@@ @@@@@ @@@@ @@@ @@@@ @@@ @@@@@@@@ @@@@ @@@@ @@@ @@@@@@@@@ @@@@ @@@@ +// @@@@@ | \=_/8 @@@@1@@ @@@ @@@@@ @@@@ @@@@ @@@ @@@@@ @@@ @@@@@@@@@ @@@@ @@@@@ @@@@ @@@@@@@@@ @@@@ +// @@@@ +// @@@@@@| _ /| |\__ @@@@@@@@2 @@@ @@@@@ @@@@ @@@@ @@@ @@@@@@@ @@@ @@@@ @@@@ @@@@@@ @@@@ @@@ @@@@@@@ +// 1@@@@@@|\ \___/) @@1@@@@@2 ~~~ ~~~~~ @@@@ ~~@@ ~~~ ~~~~~~~~~~~ ~~~~ ~~~~ ~~~~~~~~~~~ ~@@ @@@@@ +// 2@@@@@ | \ \ / | @@@@@@2 @@@ @@@@@ @@@@ @@@@ @@@ @@@@@@@@@@@ @@@@@@@@@ @@@@ @@@@@@@@@@@ @@@@@@@@@ @@@@@ +// 2@@@@ |_ > <|__ @@1@12 @@@ @@@@@ @@@@ @@@@ @@@ @@@@ @@@@@@ @@@@ @@@@ @@@@ @@@@@@ @@@ @@@@@@@ +// @@@@ / _| / \/ \ @@1@ @@@ @@@ @@@@ @@@@ @@@ @@@@ @@@@@ @@@@ @@@@ @@@@ @@@@@ @@@@@@@@@ @@@@ +// @@@@ +// @@ / |^\/ | | @@1 @@@ @@@@ @@@@ @@@ @@@@ @@@ @@@@ @@@@ @@@ @@@@ @@@@@@@@@ @@@@ @@@@ +// / / ---- \ \\\= @@ @@@@@@@@@@@@@@@@@@@@@@ +// \___/ -------- ~~ @@@ +// @@ | | | | -- @@ +// ———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.21; + +import { AppBeaconBase } from "src/apps/base/AppBeaconBase.sol"; + +contract RouterNitroAppBeacon is AppBeaconBase { + address public immutable routerNitroGateway; + + constructor( + address _owner, + address _latestAppImplementation, + address _routerNitroGateway + ) + AppBeaconBase(_owner, _latestAppImplementation, "RouterNitro") + { + routerNitroGateway = _routerNitroGateway; + } +} diff --git a/src/interfaces/accounts/IAccountUtilsModule.sol b/src/interfaces/accounts/IAccountUtilsModule.sol new file mode 100644 index 0000000..e9fc920 --- /dev/null +++ b/src/interfaces/accounts/IAccountUtilsModule.sol @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.21; + +interface IAccountUtilsModule { + event AccountInfinexProtocolBeaconImplementationUpgraded(address infinexProtocolConfigBeacon); + + event AccountSynthetixInformationBeaconUpgraded(address synthetixInformationBeacon); + + event AccountCircleBridgeParamsUpgraded( + address circleBridge, address circleMinter, uint32 defaultDestinationCCTPDomain + ); + + event AccountWormholeCircleBridgeParamsUpgraded( + address wormholeCircleBridge, uint16 defaultDestinationWormholeChainId + ); + + event AccountUSDCAddressUpgraded(address USDC); + + /*/////////////////////////////////////////////////////////////// + VIEW FUNCTIONS + ///////////////////////////////////////////////////////////////*/ + + /** + * @notice Get the Infinex Protocol Config + * @return The Infinex Protocol Config Beacon + */ + function infinexProtocolConfigBeacon() external view returns (address); + + /** + * @notice Check if the provided operation key is valid + * @param _operationKey The operation key to check + * @return A boolean indicating if the key is valid + */ + function isValidOperationKey(address _operationKey) external view returns (bool); + + /** + * @notice Check if the provided sudo key is valid + * @param _sudoKey The sudo key to check + * @return A boolean indicating if the sudo key is valid + */ + function isValidSudoKey(address _sudoKey) external view returns (bool); + + /** + * @notice Check if the provided recovery key is valid + * @param _recoveryKey The recovery key to check + * @return A boolean indicating if the recovery key is valid + */ + function isValidRecoveryKey(address _recoveryKey) external view returns (bool); + + /** + * @notice Checks if the given address is an authorized operations party. + * @param _key The address to check. + * @return A boolean indicating whether the address is an authorized operations party. + * @dev Update this function whenever the logic for requiresAuthorizedOperationsParty + * from SecurityModifiers changes + */ + function isAuthorizedOperationsParty(address _key) external view returns (bool); + + /** + * @notice Checks if the given address is an authorized recovery party. + * @param _key The address to check. + * @return A boolean indicating whether the address is an authorized recovery party. + * @dev Update this function whenever the logic for requiresAuthorizedRecoveryParty + * from SecurityModifiers changes + */ + function isAuthorizedRecoveryParty(address _key) external view returns (bool); + + /** + * @notice Retrieves the Circle Bridge parameters. + * @return The address of the circleBridge + * @return The address of the minter. + * @return The default circle bridge destination domain. + */ + function getCircleBridgeParams() external view returns (address, address, uint32); + + /** + * @notice Retrieves the wormhole circle bridge + * @return The wormhole circle bridge address. + */ + function getWormholeCircleBridge() external view returns (address); + + /** + * @notice Retrieves the Wormhole Circle Bridge parameters. + * @return The address of the wormholeCircleBridge + * @return The address of the wormholeCircleBridge and the default defaultDestinationWormholeChainId + */ + function getWormholeCircleBridgeParams() external view returns (address, uint16); + + /** + * @notice Retrieves the USDC address. + * @return The address of USDC + */ + function getUSDCAddress() external view returns (address); + + /** + * @notice Retrieves the maximum withdrawal fee. + * @return The maximum withdrawal fee. + */ + function getMaxWithdrawalFee() external pure returns (uint256); + + /*/////////////////////////////////////////////////////////////// + MUTATIVE FUNCTIONS + ///////////////////////////////////////////////////////////////*/ + + /** + * @notice Upgrade to a new beacon implementation and updates any new parameters along with it + * @param _newInfinexProtocolConfigBeacon The address of the new beacon + * @dev requires the sender to be the sudo key + * @dev Requires passing the new beacon address which matches the latest to ensure that the upgrade both + * is as the user intended, and is to the latest beacon implementation. Prevents the user from opting in to a + * specific version and upgrading to a later version that may have been deployed between the opt-in and the upgrade + */ + function upgradeProtocolBeaconParameters(address _newInfinexProtocolConfigBeacon) external; + + /** + * @notice Updates the parameters for the Circle Bridge to the latest from the Infinex Protocol Config Beacon. + * Update is opt in to prevent malicious automatic updates. + * @dev requires the sender to be the sudo key + */ + function updateCircleBridgeParams() external; + + /** + * @notice Updates the parameters for the Wormhole Circle Bridge to the latest from the Infinex Protocol Config + * Beacon. + * Update is opt in to prevent malicious automatic updates. + * @dev requires the sender to be the sudo key + */ + function updateWormholeCircleBridge() external; + + /** + * @notice Updates the USDC address from the Infinex Protocol Config Beacon. + * Update is opt in to prevent malicious automatic updates. + * @dev requires the sender to be the sudo key + */ + function updateUSDCAddress() external; +} diff --git a/src/interfaces/accounts/IBaseModule.sol b/src/interfaces/accounts/IBaseModule.sol new file mode 100644 index 0000000..b70e026 --- /dev/null +++ b/src/interfaces/accounts/IBaseModule.sol @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.21; + +import { RequestTypes } from "src/accounts/utils/RequestTypes.sol"; + +interface IBaseModule { + /*/////////////////////////////////////////////////////////////// + EVENTS / ERRORS + ///////////////////////////////////////////////////////////////*/ + + event AccountImplementationUpgraded(address accountImplementation); + event AccountMigratedFrom(uint64 previousVersion, uint64 currentVersion); + + /*/////////////////////////////////////////////////////////////// + INITIALIZER + ///////////////////////////////////////////////////////////////*/ + + /** + * @notice Initialize the account with the sudo key + */ + function initialize(address _sudoKey) external; + + /** + * @notice Reinitialize the account with the current version + * @dev Only to be called by the upgradeTo function + */ + function reinitialize(uint64 _previousVersion) external; + + /** + * @notice Reinitialize the account with the current version + * @dev Only to be called once to reinitialize accounts created with v1 + */ + function reinitializeLegacyAccount() external; + + /*/////////////////////////////////////////////////////////////// + VIEW FUNCTIONS + ///////////////////////////////////////////////////////////////*/ + + /** + * @notice Returns the version number of the account. + * @return A uint64 representing the version of the account. + * @dev The version number is provided by the OZ Initializable library + */ + function accountVersion() external view returns (uint64); + + /** + * @notice Check if the provided nonce is valid + * @param _nonce The nonce to check + * @return A boolean indicating if the nonce is valid + */ + function isValidNonce(bytes32 _nonce) external view returns (bool); + + /** + * @notice Check if the provided forwarder is trusted + * @param _forwarder The forwarder to check + * @return A boolean indicating if the forwarder is trusted + */ + function isTrustedForwarder(address _forwarder) external view returns (bool); + + /** + * @notice Get all trusted forwarders + * @return An array of addresses of all trusted forwarders + */ + function trustedForwarders() external view returns (address[] memory); + + /*/////////////////////////////////////////////////////////////// + MUTATIVE FUNCTIONS + ///////////////////////////////////////////////////////////////*/ + + /** + * @notice Enables or disables an operation key for the account + * @param _operationKey The address of the operation key to be set + * @param _isValid Whether the key is to be set as valid or invalid + * @dev This function requires the sender to be the sudo key holder + */ + function setOperationKeyStatus(address _operationKey, bool _isValid) external; + + /** + * @notice Enables or disables a recovery key for the account + * @param _recoveryKey The address of the recovery key to be set + * @param _isValid Whether the key is to be set as valid or invalid + * @dev This function requires the sender to be the sudo key holder + */ + function setRecoveryKeyStatus(address _recoveryKey, bool _isValid) external; + + /** + * @notice Enables or disables a sudo key for the account + * @param _sudoKey The address of the sudo key to be set + * @param _isValid Whether the key is to be set as valid or invalid + * @dev This function requires the sender to be the sudo key holder + */ + function setSudoKeyStatus(address _sudoKey, bool _isValid) external; + + /** + * @notice Add a new trusted forwarder + * @param _request The Request struct containing: + * RequestData { + * address _address; - The address of the new trusted forwarder. + * bytes32 _nonce; - The nonce of the signature + * } + * @param _signature The required signature for executing the transaction + * Required signature: + * - sudo key + */ + function addTrustedForwarder(RequestTypes.Request calldata _request, bytes calldata _signature) external; + + /** + * @notice Remove a trusted forwarder + * @param _request The Request struct containing: + * RequestData { + * address _address; - The address of the trusted forwarder to be removed. + * bytes32 _nonce; - The nonce of the signature + * } + * @param _signature The required signature for executing the transaction + * Required signature: + * - sudo key + */ + function removeTrustedForwarder(RequestTypes.Request calldata _request, bytes calldata _signature) external; +} diff --git a/src/interfaces/apps/base/IAppAccountBase.sol b/src/interfaces/apps/base/IAppAccountBase.sol new file mode 100644 index 0000000..e7a193f --- /dev/null +++ b/src/interfaces/apps/base/IAppAccountBase.sol @@ -0,0 +1,173 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.21; + +/** + * @title IAppAccountBase + * @notice Interface for the App Account Base + */ +interface IAppAccountBase { + /*/////////////////////////////////////////////////////////////// + EVENTS + ///////////////////////////////////////////////////////////////*/ + + event EtherTransferredToMainAccount(uint256 amount); + event ERC20TransferredToMainAccount(address indexed token, uint256 amount); + event ERC721TransferredToMainAccount(address indexed token, uint256 tokenId); + event ERC1155TransferredToMainAccount(address indexed token, uint256 tokenId, uint256 amount, bytes data); + event ERC1155BatchTransferredToMainAccount(address indexed token, uint256[] _ids, uint256[] _values, bytes _data); + event EtherRecoveredToMainAccount(uint256 amount); + event ERC20RecoveredToMainAccount(address indexed token, uint256 amount); + event ERC721RecoveredToMainAccount(address indexed token, uint256 tokenId); + event ERC1155RecoveredToMainAccount(address indexed token, uint256 tokenId, uint256 amount, bytes data); + event ERC1155BatchRecoveredToMainAccount(address indexed token, uint256[] tokenIds, uint256[] amounts, bytes _data); + + /*/////////////////////////////////////////////////////////////// + ERRORS + ///////////////////////////////////////////////////////////////*/ + + error SameAddress(); + error InvalidAppBeacon(); + error ImplementationMismatch(address implementation, address latestImplementation); + error ETHTransferFailed(); + + /*/////////////////////////////////////////////////////////////// + INITIALIZER + ///////////////////////////////////////////////////////////////*/ + + /** + * @notice Initialize the app account with the main account and the app beacon. + * @param _mainAccount the address of the main account, this is the owner of the app. + * @param _appBeacon the beacon for the app account. + */ + function initialize(address _mainAccount, address _appBeacon) external; + + /*/////////////////////////////////////////////////////////////// + VIEW FUNCTIONS + ///////////////////////////////////////////////////////////////*/ + + /** + * @notice Returns the app version number of the app account. + * @return A uint64 representing the version of the app. + * @dev NOTE: This number must be updated whenever a new version is deployed. + * The number should always only be incremented by 1. + */ + function appVersion() external pure returns (uint64); + + /** + * @notice Get the app's main account. + * @return The main account associated with this app. + */ + function getMainAccount() external view returns (address); + + /** + * @notice Get the app config beacon. + * @return The app config beacon address. + */ + function getAppBeacon() external view returns (address); + + /*/////////////////////////////////////////////////////////////// + MUTATIVE FUNCTIONS + ///////////////////////////////////////////////////////////////*/ + + /** + * @notice Transfer Ether to the main account from the app account. + * @param _amount The amount of Ether to transfer. + */ + function transferEtherToMainAccount(uint256 _amount) external; + + /** + * @notice Transfer ERC20 tokens to the main account from the app account. + * @param _token The address of the ERC20 token. + * @param _amount The amount of tokens to transfer. + */ + function transferERC20ToMainAccount(address _token, uint256 _amount) external; + + /** + * @notice Transfer ERC721 tokens to the main account from the app account. + * @param _token The address of the ERC721 token. + * @param _tokenId The ID of the ERC721 token. + */ + function transferERC721ToMainAccount(address _token, uint256 _tokenId) external; + + /** + * @notice Transfer ERC1155 tokens to the main account from the app account. + * @param _token The address of the ERC1155 token. + * @param _tokenId The ID of the ERC1155 token. + * @param _amount The amount of tokens to transfer. + * @param _data Data to send with the transfer. + */ + function transferERC1155ToMainAccount( + address _token, + uint256 _tokenId, + uint256 _amount, + bytes calldata _data + ) + external; + + /** + * @notice Transfers batch ERC1155 tokens to the main account from the app account. + * @param _token The address of the ERC1155 token. + * @param _ids The IDs of the ERC1155 tokens. + * @param _amounts The amounts of the ERC1155 tokens. + * @param _data Data to send with the transfer. + */ + function transferERC1155BatchToMainAccount( + address _token, + uint256[] calldata _ids, + uint256[] calldata _amounts, + bytes calldata _data + ) + external; + + /** + * @notice Recovers all ether in the app account to the main account. + * @dev Requires the sender to be an authorized recovery party. + */ + function recoverEtherToMainAccount() external; + + /** + * @notice Recovers the full balance of an ERC20 token to the main account. + * @param _token The address of the token to be recovered to the main account. + * @dev Requires the sender to be an authorized recovery party. + */ + function recoverERC20ToMainAccount(address _token) external; + + /** + * @notice Recovers a specified ERC721 token to the main account. + * @param _token The ERC721 token address to recover. + * @param _tokenId The ID of the ERC721 token to recover. + * @dev Requires the sender to be an authorized recovery party. + */ + function recoverERC721ToMainAccount(address _token, uint256 _tokenId) external; + + /** + * @notice Recovers all the tokens of a specified ERC1155 token to the main account. + * @param _token The ERC1155 token address to recover. + * @param _tokenId The id of the token to recover. + * @param _data The data for the transaction. + * @dev Requires the sender to be an authorized recovery party. + */ + function recoverERC1155ToMainAccount(address _token, uint256 _tokenId, bytes calldata _data) external; + + /** + * @notice Recovers batch ERC1155 tokens to the main account. + * @param _token The address of the ERC1155 token. + * @param _ids The IDs of the ERC1155 tokens. + * @param _values The values of the ERC1155 tokens. + * @param _data Data to send with the transfer. + */ + function recoverERC1155BatchToMainAccount( + address _token, + uint256[] calldata _ids, + uint256[] calldata _values, + bytes calldata _data + ) + external; + + /** + * @notice Upgrade the app account to the latest implementation and beacon. + * @param _appBeacon The address of the new app beacon. + * @dev Requires the sender to be the main account. + */ + function upgradeAppVersion(address _appBeacon) external; +} diff --git a/src/interfaces/apps/base/IAppBeaconBase.sol b/src/interfaces/apps/base/IAppBeaconBase.sol new file mode 100644 index 0000000..12d353b --- /dev/null +++ b/src/interfaces/apps/base/IAppBeaconBase.sol @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.21; + +/** + * @title IAppBeaconBase + * @notice Interface for the App Beacon Base + */ +interface IAppBeaconBase { + /*/////////////////////////////////////////////////////////////// + STRUCTS + ///////////////////////////////////////////////////////////////*/ + + /** + * @notice Struct containing the config for the app beacon. + * @param appName The name of the app. + * @param latestAppBeacon The address of the latest app beacon. + */ + struct AppBeaconConfig { + string appName; + address latestAppBeacon; + } + + /*/////////////////////////////////////////////////////////////// + ERRORS + ///////////////////////////////////////////////////////////////*/ + + error ZeroAddress(); + error InvalidAppAccountImplementation(); + error InvalidAppBeacon(); + + /*/////////////////////////////////////////////////////////////// + EVENTS + ///////////////////////////////////////////////////////////////*/ + + event LatestAppImplementationSet(address latestAppImplementation); + event LatestAppBeaconSet(address latestAppBeacon); + + /*/////////////////////////////////////////////////////////////// + VIEW FUNCTIONS + ///////////////////////////////////////////////////////////////*/ + + /** + * @notice Gets the app implementation. + * @return The address of the app implementation. + */ + function APP_IMPLEMENTATION() external view returns (address); + + /** + * @notice Gets the name of the app associated to the beacon. + * @return The name of the app beacon. + */ + function getAppName() external view returns (string memory); + + /** + * @notice Gets the app implementation. + * @return The address of the app implementation. + */ + function getLatestAppImplementation() external view returns (address); + + /** + * @notice Gets the latest beacon address for the app. + * @return The address of the latest app beacon. + */ + function getLatestAppBeacon() external view returns (address); + + /*/////////////////////////////////////////////////////////////// + MUTATIVE FUNCTIONS + ///////////////////////////////////////////////////////////////*/ + + /** + * @notice Sets the latest app beacon address. + * @param _latestAppBeacon The address of the latest app beacon associated with the app. + */ + function setLatestAppBeacon(address _latestAppBeacon) external; +} diff --git a/src/interfaces/curve/ICurveStableSwapApp.sol b/src/interfaces/curve/ICurveStableSwapApp.sol new file mode 100644 index 0000000..fe647e5 --- /dev/null +++ b/src/interfaces/curve/ICurveStableSwapApp.sol @@ -0,0 +1,131 @@ +// c=< +// | +// | ////\ 1@2 +// @@ | /___\** @@@2 @@@@@@@@@@@@@@@@@@@@@@ +// @@@ | |~L~ |* @@@@@@ @@@ @@@@@ @@@@ @@@ @@@@ @@@ @@@@@@@@ @@@@ @@@@ @@@ @@@@@@@@@ @@@@ @@@@ +// @@@@@ | \=_/8 @@@@1@@ @@@ @@@@@ @@@@ @@@@ @@@ @@@@@ @@@ @@@@@@@@@ @@@@ @@@@@ @@@@ @@@@@@@@@ @@@@ +// @@@@ +// @@@@@@| _ /| |\__ @@@@@@@@2 @@@ @@@@@ @@@@ @@@@ @@@ @@@@@@@ @@@ @@@@ @@@@ @@@@@@ @@@@ @@@ @@@@@@@ +// 1@@@@@@|\ \___/) @@1@@@@@2 ~~~ ~~~~~ @@@@ ~~@@ ~~~ ~~~~~~~~~~~ ~~~~ ~~~~ ~~~~~~~~~~~ ~@@ @@@@@ +// 2@@@@@ | \ \ / | @@@@@@2 @@@ @@@@@ @@@@ @@@@ @@@ @@@@@@@@@@@ @@@@@@@@@ @@@@ @@@@@@@@@@@ @@@@@@@@@ @@@@@ +// 2@@@@ |_ > <|__ @@1@12 @@@ @@@@@ @@@@ @@@@ @@@ @@@@ @@@@@@ @@@@ @@@@ @@@@ @@@@@@ @@@ @@@@@@@ +// @@@@ / _| / \/ \ @@1@ @@@ @@@ @@@@ @@@@ @@@ @@@@ @@@@@ @@@@ @@@@ @@@@ @@@@@ @@@@@@@@@ @@@@ +// @@@@ +// @@ / |^\/ | | @@1 @@@ @@@@ @@@@ @@@ @@@@ @@@ @@@@ @@@@ @@@ @@@@ @@@@@@@@@ @@@@ @@@@ +// / / ---- \ \\\= @@ @@@@@@@@@@@@@@@@@@@@@@ +// \___/ -------- ~~ @@@ +// @@ | | | | -- @@ +// ———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.21; + +/** + * @title ICurveStableSwapApp + * @notice Interface for the curve stable swap app. + */ +interface ICurveStableSwapApp { + /*/////////////////////////////////////////////////////////////// + EVENTS + ///////////////////////////////////////////////////////////////*/ + + event TokensExchanged( + address indexed stableSwapPool, address fromToken, address toToken, uint256 fromAmount, uint256 toAmount + ); + event LiquidityAdded(address indexed stableSwapPool, uint256[] amounts, uint256 lpAmount); + event LiquidityRemoved(address indexed stableSwapPool, uint256[] amounts, uint256 lpAmount); + event LiquidityRemovedSingleToken(address indexed stableSwapPool, uint256 amountRemoved, uint256 lpAmountBurnt); + + /*/////////////////////////////////////////////////////////////// + MUTATIVE FUNCTIONS + ///////////////////////////////////////////////////////////////*/ + + /** + * @notice Allows the authorized operations party to exchange tokens on the stable swap pool. + * @param _stableSwapPool The address of the stable swap pool. + * @param _fromToken The address of the ERC20 token provided to exchange. + * @param _toToken The address of the ERC20 token to receive from the exchange. + * @param _fromAmount The amount of tokens to exchange. + * @param _minToAmount The minimum amount of tokens to receive. + */ + function exchange( + address _stableSwapPool, + address _fromToken, + address _toToken, + uint256 _fromAmount, + uint256 _minToAmount + ) + external; + + /** + * @notice Adds liquidity to the specified pool + * @dev The arrays indices have to match the indices of the tokens in the pool. + * @param _stableSwapPool The address of the pool to add liquidity to. + * @param _tokens An array of token addresses to add as liquidity. + * @param _amounts An array of token amounts to add as liquidity. + * @param _minLPAmount The minimum amount of LP tokens to receive. + */ + function addLiquidity( + address _stableSwapPool, + address[] calldata _tokens, + uint256[] calldata _amounts, + uint256 _minLPAmount + ) + external; + + /** + * @notice Removes liquidity for a single token from the Curve stable swap pool. + * @param _stableSwapPool The address of the Curve stable swap pool. + * @param _tokenIndex The index of the token to remove liquidity. + * @param _lpAmount The amount of LP tokens to burn. + * @param _minReceiveAmount The minimum amount of tokens to receive in return. + * @return The amount of tokens received after removing liquidity. + */ + function removeSingleTokenLiquidity( + address _stableSwapPool, + int128 _tokenIndex, + uint256 _lpAmount, + uint256 _minReceiveAmount + ) + external + returns (uint256); + + /** + * @notice Withdraw coins from a Curve stable swap pool in an imbalanced amount. + * @param _stableSwapPool The address of the Curve stable swap pool. + * @param _lpAmount The max amount of LP tokens to burn. + * @param _amounts The amount of tokens to receive in return. + */ + function removeLiquidityImbalance( + address _stableSwapPool, + uint256 _lpAmount, + uint256[] calldata _amounts + ) + external; + + /** + * @notice Swaps ERC20 tokens to USDC at the current exchange amount and then recovers to mainAccount + * @param _stableSwapPool The address of the stable swap pool. + * @param _fromToken The address of the ERC20 token to recover. + * @param _minToAmount The minimum amount of USDC to receive. + * @dev This function must be called by an authorized recovery party. + */ + function recoverERC20ToUSDC(address _stableSwapPool, address _fromToken, uint256 _minToAmount) external; + + /** + * @notice Removes all Liquidity as USDC with specified slippage amount and then recovers to mainAccount + * @param _LPToken The address of the pool to remove liquidity from. + * @param _USDCIndex The address of the LP token and pool to recover from. + * @param _minReceiveAmount The minimum amount of USDC to receive. + * @dev This function must be called by an authorized recovery party. + */ + function recoverUSDCFromLP(address _LPToken, int128 _USDCIndex, uint256 _minReceiveAmount) external; + + /** + * @notice Removes a single token from an LP for the purpose of recovery + * @param _LPToken The address of the LP token/pool to remove liquidity from. + * @param _tokenIndex The index of the token to remove from the liquidity pool + * @param _minToAmount The minimum amount of token to withdraw from the liquidity pool. + * @dev This function must be called by an authorized recovery party. + */ + function recoverERC20FromLP(address _LPToken, int128 _tokenIndex, uint256 _minToAmount) external; +} diff --git a/src/interfaces/curve/ICurveStableSwapAppBeacon.sol b/src/interfaces/curve/ICurveStableSwapAppBeacon.sol new file mode 100644 index 0000000..3673f97 --- /dev/null +++ b/src/interfaces/curve/ICurveStableSwapAppBeacon.sol @@ -0,0 +1,86 @@ +// c=< +// | +// | ////\ 1@2 +// @@ | /___\** @@@2 @@@@@@@@@@@@@@@@@@@@@@ +// @@@ | |~L~ |* @@@@@@ @@@ @@@@@ @@@@ @@@ @@@@ @@@ @@@@@@@@ @@@@ @@@@ @@@ @@@@@@@@@ @@@@ @@@@ +// @@@@@ | \=_/8 @@@@1@@ @@@ @@@@@ @@@@ @@@@ @@@ @@@@@ @@@ @@@@@@@@@ @@@@ @@@@@ @@@@ @@@@@@@@@ @@@@ +// @@@@ +// @@@@@@| _ /| |\__ @@@@@@@@2 @@@ @@@@@ @@@@ @@@@ @@@ @@@@@@@ @@@ @@@@ @@@@ @@@@@@ @@@@ @@@ @@@@@@@ +// 1@@@@@@|\ \___/) @@1@@@@@2 ~~~ ~~~~~ @@@@ ~~@@ ~~~ ~~~~~~~~~~~ ~~~~ ~~~~ ~~~~~~~~~~~ ~@@ @@@@@ +// 2@@@@@ | \ \ / | @@@@@@2 @@@ @@@@@ @@@@ @@@@ @@@ @@@@@@@@@@@ @@@@@@@@@ @@@@ @@@@@@@@@@@ @@@@@@@@@ @@@@@ +// 2@@@@ |_ > <|__ @@1@12 @@@ @@@@@ @@@@ @@@@ @@@ @@@@ @@@@@@ @@@@ @@@@ @@@@ @@@@@@ @@@ @@@@@@@ +// @@@@ / _| / \/ \ @@1@ @@@ @@@ @@@@ @@@@ @@@ @@@@ @@@@@ @@@@ @@@@ @@@@ @@@@@ @@@@@@@@@ @@@@ +// @@@@ +// @@ / |^\/ | | @@1 @@@ @@@@ @@@@ @@@ @@@@ @@@ @@@@ @@@@ @@@ @@@@ @@@@@@@@@ @@@@ @@@@ +// / / ---- \ \\\= @@ @@@@@@@@@@@@@@@@@@@@@@ +// \___/ -------- ~~ @@@ +// @@ | | | | -- @@ +// ———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.21; + +/** + * @title ICurveStableSwapAppBeacon + * @notice Interface for the curve app beacon. + */ +interface ICurveStableSwapAppBeacon { + /*/////////////////////////////////////////////////////////////// + STRUCTS + ///////////////////////////////////////////////////////////////*/ + + struct PoolData { + address pool; + int128 fromTokenIndex; + int128 toTokenIndex; + uint256 amountReceived; + address[] tokens; + uint256[] balances; + uint256[] decimals; + bool isUnderlying; + } + + /*/////////////////////////////////////////////////////////////// + VIEW FUNCTIONS/VARIABLES + ///////////////////////////////////////////////////////////////*/ + + /** + * @notice Gets the curve stable swap factory address. + * @return The address of the curve stable swap factory. + */ + function curveStableswapFactoryNG() external view returns (address); + + /** + * @notice Gets the USDC address. + * @return The address of the USDC token. + */ + function USDC() external view returns (address); + + /** + * @notice Checks if a pool has been vetted by the council and can be safely used by the app + * @param _pool The address of the pool. + * @return True if the pool is supported, false otherwise. + */ + function isSupportedPool(address _pool) external view returns (bool); + + /** + * @notice Get the pool data for the given tokens. Data will be empty if type is underyling + * @param _fromToken The address of the token to swap from. + * @param _toToken The address of the token to swap to. + * @return poolData The pool data for the given tokens. + */ + function getPoolDatafromTokens( + address _fromToken, + address _toToken, + uint256 _fromAmount + ) + external + returns (PoolData memory poolData); + + /** + * @notice A safety feature to limit the pools that can be used by the app to only vetted and suppported pools + * @dev Only the contract owner can call this function. + * @param _pool The address of the pool. + * @param _supported The supported status of the pool. + */ + function setIsSupportedPool(address _pool, bool _supported) external; +} diff --git a/src/interfaces/curve/ICurveStableSwapFactoryNG.sol b/src/interfaces/curve/ICurveStableSwapFactoryNG.sol new file mode 100644 index 0000000..db066d8 --- /dev/null +++ b/src/interfaces/curve/ICurveStableSwapFactoryNG.sol @@ -0,0 +1,324 @@ +// c=< +// | +// | ////\ 1@2 +// @@ | /___\** @@@2 @@@@@@@@@@@@@@@@@@@@@@ +// @@@ | |~L~ |* @@@@@@ @@@ @@@@@ @@@@ @@@ @@@@ @@@ @@@@@@@@ @@@@ @@@@ @@@ @@@@@@@@@ @@@@ @@@@ +// @@@@@ | \=_/8 @@@@1@@ @@@ @@@@@ @@@@ @@@@ @@@ @@@@@ @@@ @@@@@@@@@ @@@@ @@@@@ @@@@ @@@@@@@@@ @@@@ +// @@@@ +// @@@@@@| _ /| |\__ @@@@@@@@2 @@@ @@@@@ @@@@ @@@@ @@@ @@@@@@@ @@@ @@@@ @@@@ @@@@@@ @@@@ @@@ @@@@@@@ +// 1@@@@@@|\ \___/) @@1@@@@@2 ~~~ ~~~~~ @@@@ ~~@@ ~~~ ~~~~~~~~~~~ ~~~~ ~~~~ ~~~~~~~~~~~ ~@@ @@@@@ +// 2@@@@@ | \ \ / | @@@@@@2 @@@ @@@@@ @@@@ @@@@ @@@ @@@@@@@@@@@ @@@@@@@@@ @@@@ @@@@@@@@@@@ @@@@@@@@@ @@@@@ +// 2@@@@ |_ > <|__ @@1@12 @@@ @@@@@ @@@@ @@@@ @@@ @@@@ @@@@@@ @@@@ @@@@ @@@@ @@@@@@ @@@ @@@@@@@ +// @@@@ / _| / \/ \ @@1@ @@@ @@@ @@@@ @@@@ @@@ @@@@ @@@@@ @@@@ @@@@ @@@@ @@@@@ @@@@@@@@@ @@@@ +// @@@@ +// @@ / |^\/ | | @@1 @@@ @@@@ @@@@ @@@ @@@@ @@@ @@@@ @@@@ @@@ @@@@ @@@@@@@@@ @@@@ @@@@ +// / / ---- \ \\\= @@ @@@@@@@@@@@@@@@@@@@@@@ +// \___/ -------- ~~ @@@ +// @@ | | | | -- @@ +// ———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.21; + +// from https://github.com/curvefi/stableswap-ng + +/** + * @title ICurveStableSwapFactoryNG + * @notice Interface for the curve stable swap factory. + */ +interface ICurveStableSwapFactoryNG { + /** + * @notice Find an available pool for exchanging two coins + * @param _from Address of coin to be sent + * @param _to Address of coin to be received + * @param i Index value. When multiple pools are available + * this value is used to return the n'th address. + * @return Pool address + */ + function find_pool_for_coins(address _from, address _to, uint256 i) external view returns (address); + + /** + * @notice Find an available pool for exchanging two coins + * @param _from Address of coin to be sent + * @param _to Address of coin to be received + * @return Pool address + */ + function find_pool_for_coins(address _from, address _to) external view returns (address); + + /** + * @notice Get the base pool for a given factory metapool + * @param _pool Metapool address + * @return Address of base pool + */ + function get_base_pool(address _pool) external view returns (address); + + /** + * @notice Get the number of coins in a pool + * @param _pool Pool address + * @return Number of coins + */ + function get_n_coins(address _pool) external view returns (uint256); + + /** + * @notice Get the coins within a pool + * @param _pool Pool address + * @return List of coin addresses + */ + function get_coins(address _pool) external view returns (address[] memory); + + /** + * @notice Get the underlying coins within a pool + * @dev Reverts if a pool does not exist or is not a metapool + * @param _pool Pool address + * @return List of coin addresses + */ + function get_underlying_coins(address _pool) external view returns (address[] memory); + + /** + * @notice Get decimal places for each coin within a pool + * @param _pool Pool address + * @return uint256 list of decimals + */ + function get_decimals(address _pool) external view returns (uint256[] memory); + + /** + * @notice Get decimal places for each underlying coin within a pool + * @param _pool Pool address + * @return uint256 list of decimals + */ + function get_underlying_decimals(address _pool) external view returns (uint256[] memory); + + /** + * @notice Get rates for coins within a metapool + * @param _pool Pool address + * @return Rates for each coin, precision normalized to 10**18 + */ + function get_metapool_rates(address _pool) external view returns (uint256[] memory); + + /** + * @notice Get balances for each coin within a pool + * @dev For pools using lending, these are the wrapped coin balances + * @param _pool Pool address + * @return uint256 list of balances + */ + function get_balances(address _pool) external view returns (uint256[] memory); + + /** + * @notice Get balances for each underlying coin within a metapool + * @param _pool Metapool address + * @return uint256 list of underlying balances + */ + function get_underlying_balances(address _pool) external view returns (uint256[] memory); + + /** + * @notice Get the amplfication co-efficient for a pool + * @param _pool Pool address + * @return uint256 A + */ + function get_A(address _pool) external view returns (uint256); + + /** + * @notice Get the fees for a pool + * @dev Fees are expressed as integers + * @param _pool Pool address + * @return Pool fee and admin fee as uint256 with 1e10 precision + */ + function get_fees(address _pool) external view returns (uint256, uint256); + + /** + * @notice Get the current admin balances (uncollected fees) for a pool + * @param _pool Pool address + * @return List of uint256 admin balances + */ + function get_admin_balances(address _pool) external view returns (uint256[] memory); + + /** + * @notice Convert coin addresses to indices for use with pool methods + * @param _pool Pool address + * @param _from Coin address to be used as `i` within a pool + * @param _to Coin address to be used as `j` within a pool + * @return int128 `i`, int128 `j`, boolean indicating if `i` and `j` are underlying coins + */ + function get_coin_indices(address _pool, address _from, address _to) external view returns (int128, int128, bool); + + /** + * @notice Get the address of the liquidity gauge contract for a factory pool + * @dev Returns `empty(address)` if a gauge has not been deployed + * @param _pool Pool address + * @return Implementation contract address + */ + function get_gauge(address _pool) external view returns (address); + + /** + * @notice Get the address of the implementation contract used for a factory pool + * @param _pool Pool address + * @return Implementation contract address + */ + function get_implementation_address(address _pool) external view returns (address); + + /** + * @notice Verify `_pool` is a metapool + * @param _pool Pool address + * @return True if `_pool` is a metapool + */ + function is_meta(address _pool) external view returns (bool); + + /** + * @notice Query the asset type of `_pool` + * @param _pool Pool Address + * @return Dynarray of uint8 indicating the pool asset type + * Asset Types: + * 0. Standard ERC20 token with no additional features + * 1. Oracle - token with rate oracle (e.g. wrapped staked ETH) + * 2. Rebasing - token with rebase (e.g. staked ETH) + * 3. ERC4626 - e.g. sDAI + */ + function get_pool_asset_types(address _pool) external view returns (uint8[] memory); + + /** + * @notice Deploy a new plain pool + * @param _name Name of the new plain pool + * @param _symbol Symbol for the new plain pool - will be + * concatenated with factory symbol + * @param _coins List of addresses of the coins being used in the pool. + * @param _A Amplification co-efficient - a lower value here means + * less tolerance for imbalance within the pool's assets. + * Suggested values include: + * * Uncollateralized algorithmic stablecoins: 5-10 + * * Non-redeemable, collateralized assets: 100 + * * Redeemable assets: 200-400 + * @param _fee Trade fee, given as an integer with 1e10 precision. The + * maximum is 1% (100000000). 50% of the fee is distributed to veCRV holders. + * @param _offpeg_fee_multiplier Off-peg fee multiplier + * @param _ma_exp_time Averaging window of oracle. Set as time_in_seconds / ln(2) + * Example: for 10 minute EMA, _ma_exp_time is 600 / ln(2) ~= 866 + * @param _implementation_idx Index of the implementation to use + * @param _asset_types Asset types for pool, as an integer + * @param _method_ids Array of first four bytes of the Keccak-256 hash of the function signatures + * of the oracle addresses that gives rate oracles. + * Calculated as: keccak(text=event_signature.replace(" ", ""))[:4] + * @param _oracles Array of rate oracle addresses. + * @return Address of the deployed pool + */ + function deploy_plain_pool( + string memory _name, + string memory _symbol, + address[] memory _coins, + uint256 _A, + uint256 _fee, + uint256 _offpeg_fee_multiplier, + uint256 _ma_exp_time, + uint256 _implementation_idx, + uint8[] memory _asset_types, + bytes4[] memory _method_ids, + address[] memory _oracles + ) + external + returns (address); + /** + * @notice Deploy a new metapool + * @param _base_pool Address of the base pool to use + * within the metapool + * @param _name Name of the new metapool + * @param _symbol Symbol for the new metapool - will be + * concatenated with the base pool symbol + * @param _coin Address of the coin being used in the metapool + * @param _A Amplification co-efficient - a higher value here means + * less tolerance for imbalance within the pool's assets. + * Suggested values include: + * * Uncollateralized algorithmic stablecoins: 5-10 + * * Non-redeemable, collateralized assets: 100 + * * Redeemable assets: 200-400 + * @param _fee Trade fee, given as an integer with 1e10 precision. The + * the maximum is 1% (100000000). + * 50% of the fee is distributed to veCRV holders. + * @param _offpeg_fee_multiplier Off-peg fee multiplier + * @param _ma_exp_time Averaging window of oracle. Set as time_in_seconds / ln(2) + * Example: for 10 minute EMA, _ma_exp_time is 600 / ln(2) ~= 866 + * @param _implementation_idx Index of the implementation to use + * @param _asset_type Asset type for token, as an integer + * @param _method_id First four bytes of the Keccak-256 hash of the function signatures + * of the oracle addresses that gives rate oracles. + * Calculated as: keccak(text=event_signature.replace(" ", ""))[:4] + * @param _oracle Rate oracle address. + * @return Address of the deployed pool + */ + function deploy_metapool( + address _base_pool, + string memory _name, + string memory _symbol, + address _coin, + uint256 _A, + uint256 _fee, + uint256 _offpeg_fee_multiplier, + uint256 _ma_exp_time, + uint256 _implementation_idx, + uint8 _asset_type, + bytes4 _method_id, + address _oracle + ) + external + returns (address); + /** + * @notice Deploy a liquidity gauge for a factory pool + * @param _pool Factory pool address to deploy a gauge for + * @return Address of the deployed gauge + */ + function deploy_gauge(address _pool) external returns (address); + + /** + * @notice Add a base pool to the registry, which may be used in factory metapools + * @dev 1. Only callable by admin + * 2. Rebasing tokens are not allowed in the base pool. + * 3. Do not add base pool which contains native tokens (e.g. ETH). + * 4. As much as possible: use standard ERC20 tokens. + * Should you choose to deviate from these recommendations, audits are advised. + * @param _base_pool Pool address to add + * @param _base_lp_token LP token of the base pool + * @param _asset_types Asset type for pool, as an integer + * @param _n_coins Number of coins in the pool + */ + function add_base_pool( + address _base_pool, + address _base_lp_token, + uint8[] memory _asset_types, + uint256 _n_coins + ) + external; + + /** + * @notice Set implementation contracts for pools + * @dev Only callable by admin + * @param _implementation_index Implementation index where implementation is stored + * @param _implementation Implementation address to use when deploying plain pools + */ + function set_pool_implementations(uint256 _implementation_index, address _implementation) external; + + /** + * @notice Set implementation contracts for metapools + * @dev Only callable by admin + * @param _implementation_index Implementation index where implementation is stored + * @param _implementation Implementation address to use when deploying meta pools + */ + function set_metapool_implementations(uint256 _implementation_index, address _implementation) external; + + /** + * @notice Set implementation contracts for StableSwap Math + * @dev Only callable by admin + * @param _math_implementation Address of the math implementation contract + */ + function set_math_implementation(address _math_implementation) external; + + /** + * @notice Set implementation contracts for liquidity gauge + * @dev Only callable by admin + * @param _gauge_implementation Address of the gauge blueprint implementation contract + */ + function set_gauge_implementation(address _gauge_implementation) external; + + /** + * @notice Set implementation contracts for Views methods + * @dev Only callable by admin + * @param _views_implementation Implementation address of views contract + */ + function set_views_implementation(address _views_implementation) external; +} diff --git a/src/interfaces/curve/ICurveStableSwapNG.sol b/src/interfaces/curve/ICurveStableSwapNG.sol new file mode 100644 index 0000000..0a3f5d7 --- /dev/null +++ b/src/interfaces/curve/ICurveStableSwapNG.sol @@ -0,0 +1,134 @@ +// c=< +// | +// | ////\ 1@2 +// @@ | /___\** @@@2 @@@@@@@@@@@@@@@@@@@@@@ +// @@@ | |~L~ |* @@@@@@ @@@ @@@@@ @@@@ @@@ @@@@ @@@ @@@@@@@@ @@@@ @@@@ @@@ @@@@@@@@@ @@@@ @@@@ +// @@@@@ | \=_/8 @@@@1@@ @@@ @@@@@ @@@@ @@@@ @@@ @@@@@ @@@ @@@@@@@@@ @@@@ @@@@@ @@@@ @@@@@@@@@ @@@@ +// @@@@ +// @@@@@@| _ /| |\__ @@@@@@@@2 @@@ @@@@@ @@@@ @@@@ @@@ @@@@@@@ @@@ @@@@ @@@@ @@@@@@ @@@@ @@@ @@@@@@@ +// 1@@@@@@|\ \___/) @@1@@@@@2 ~~~ ~~~~~ @@@@ ~~@@ ~~~ ~~~~~~~~~~~ ~~~~ ~~~~ ~~~~~~~~~~~ ~@@ @@@@@ +// 2@@@@@ | \ \ / | @@@@@@2 @@@ @@@@@ @@@@ @@@@ @@@ @@@@@@@@@@@ @@@@@@@@@ @@@@ @@@@@@@@@@@ @@@@@@@@@ @@@@@ +// 2@@@@ |_ > <|__ @@1@12 @@@ @@@@@ @@@@ @@@@ @@@ @@@@ @@@@@@ @@@@ @@@@ @@@@ @@@@@@ @@@ @@@@@@@ +// @@@@ / _| / \/ \ @@1@ @@@ @@@ @@@@ @@@@ @@@ @@@@ @@@@@ @@@@ @@@@ @@@@ @@@@@ @@@@@@@@@ @@@@ +// @@@@ +// @@ / |^\/ | | @@1 @@@ @@@@ @@@@ @@@ @@@@ @@@ @@@@ @@@@ @@@ @@@@ @@@@@@@@@ @@@@ @@@@ +// / / ---- \ \\\= @@ @@@@@@@@@@@@@@@@@@@@@@ +// \___/ -------- ~~ @@@ +// @@ | | | | -- @@ +// ———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.21; + +// from https://github.com/curvefi/stableswap-ng + +/** + * @title ICurveStableSwapNG + * @notice Interface for the curve stable swap pool. + */ +interface ICurveStableSwapNG { + /** + * @notice Calculate the current input dx given output dy + * @dev Index values can be found via the `coins` public getter method + * @param i Index value for the coin to send + * @param j Index value of the coin to receive + * @param dy Amount of `j` being received after exchange + * @return Amount of `i` predicted + */ + function get_dx(int128 i, int128 j, uint256 dy) external view returns (uint256); + + /** + * @notice Calculate the current output dy given input dx + * @dev Index values can be found via the `coins` public getter method + * @param i Index value for the coin to send + * @param j Index value of the coin to receive + * @param dx Amount of `i` being exchanged + * @return Amount of `j` predicted + */ + function get_dy(int128 i, int128 j, uint256 dx) external view returns (uint256); + + /** + * @notice Calculate the amount received when withdrawing a single coin + * @param _burn_amount Amount of LP tokens to burn in the withdrawal + * @param i Index value of the coin to withdraw + * @return Amount of coin received + */ + function calc_withdraw_one_coin(uint256 _burn_amount, int128 i) external view returns (uint256); + + /** + * @notice Returns the address of the token at the specified index. + * @param i The index of the token. + * @return The address of the token at the specified index. + */ + function coins(uint256 i) external view returns (address); + + /** + * @notice Returns the number of underlying coins in the pool. + * @return The number of underlying coins in the pool. + */ + function N_COINS() external view returns (uint256); + + /** + * @notice Perform an exchange between two coins + * @dev Index values can be found via the `coins` public getter method + * @param i Index value for the coin to send + * @param j Index value of the coin to receive + * @param _dx Amount of `i` being exchanged + * @param _min_dy Minimum amount of `j` to receive + * @return Actual amount of `j` received + */ + function exchange(int128 i, int128 j, uint256 _dx, uint256 _min_dy) external returns (uint256); + + /** + * @notice Deposit coins into the pool + * @param _amounts List of amounts of coins to deposit + * @param _min_mint_amount Minimum amount of LP tokens to mint from the deposit + * @return Amount of LP tokens received by depositing + */ + function add_liquidity(uint256[] memory _amounts, uint256 _min_mint_amount) external returns (uint256); + + /** + * @notice Withdraw a single coin from the pool + * @param _burn_amount Amount of LP tokens to burn in the withdrawal + * @param i Index value of the coin to withdraw + * @param _min_received Minimum amount of coin to receive + * @return Amount of coin received + */ + function remove_liquidity_one_coin( + uint256 _burn_amount, + int128 i, + uint256 _min_received + ) + external + returns (uint256); + + /** + * @notice Withdraw coins from the pool in an imbalanced amount + * @param _amounts List of amounts of underlying coins to withdraw + * @param _max_burn_amount Maximum amount of LP token to burn in the withdrawal + * @return Actual amount of the LP token burned in the withdrawal + */ + function remove_liquidity_imbalance( + uint256[] memory _amounts, + uint256 _max_burn_amount + ) + external + returns (uint256); + + /** + * @notice Withdraw coins from the pool + * @dev Withdrawal amounts are based on current deposit ratios + * @param _burn_amount Quantity of LP tokens to burn in the withdrawal + * @param _min_amounts Minimum amounts of underlying coins to receive + * @param _receiver Address that receives the withdrawn coins + * @param _claim_admin_fees Whether to claim admin fees + * @return List of amounts of coins that were withdrawn + */ + function remove_liquidity( + uint256 _burn_amount, + uint256[] memory _min_amounts, + address _receiver, + bool _claim_admin_fees + ) + external + returns (uint256[] memory); +} diff --git a/src/interfaces/nitro/IRouterNitroApp.sol b/src/interfaces/nitro/IRouterNitroApp.sol new file mode 100644 index 0000000..9931660 --- /dev/null +++ b/src/interfaces/nitro/IRouterNitroApp.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.21; + +import { IRouterNitroGateway } from "../nitro/IRouterNitroGateway.sol"; + +/// @title Interface for handler contracts that support deposits and deposit executions. +/// @author Router Protocol. +interface IRouterNitroApp { + function bridgeAssets( + IRouterNitroGateway.DepositData memory depositData, + bytes memory destToken, + bytes memory recipient + ) + external + payable; + + function updateBridgeTxnData( + address srcToken, + uint256 feeAmount, + uint256 depositId, + bool initiatewithdrawal + ) + external + payable; + + function bridgeAssetsWithMessage( + IRouterNitroGateway.DepositData memory depositData, + bytes memory destToken, + bytes memory recipient, + bytes memory message + ) + external + payable; +} diff --git a/src/interfaces/nitro/IRouterNitroAppBeacon.sol b/src/interfaces/nitro/IRouterNitroAppBeacon.sol new file mode 100644 index 0000000..5a814ee --- /dev/null +++ b/src/interfaces/nitro/IRouterNitroAppBeacon.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.21; + +/// @title Interface for handler contracts that support deposits and deposit executions. +/// @author Router Protocol. +interface IRouterNitroAppBeacon { + function routerNitroGateway() external view returns (address); +} diff --git a/src/interfaces/nitro/IRouterNitroGateway.sol b/src/interfaces/nitro/IRouterNitroGateway.sol new file mode 100644 index 0000000..aa1c2c0 --- /dev/null +++ b/src/interfaces/nitro/IRouterNitroGateway.sol @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.21; + +/// @title Interface for handler contracts that support deposits and deposit executions. +/// @author Router Protocol. +interface IRouterNitroGateway { + event FundsDeposited( + uint256 partnerId, + uint256 amount, + bytes32 destChainIdBytes, + uint256 destAmount, + uint256 depositId, + address srcToken, + address depositor, + bytes recipient, + bytes destToken + ); + + event iUSDCDeposited( + uint256 partnerId, + uint256 amount, + bytes32 destChainIdBytes, + uint256 usdcNonce, + address srcToken, + bytes32 recipient, + address depositor + ); + + event FundsDepositedWithMessage( + uint256 partnerId, + uint256 amount, + bytes32 destChainIdBytes, + uint256 destAmount, + uint256 depositId, + address srcToken, + bytes recipient, + address depositor, + bytes destToken, + bytes message + ); + event FundsPaid(bytes32 messageHash, address forwarder, uint256 nonce); + + event DepositInfoUpdate( + address srcToken, + uint256 feeAmount, + uint256 depositId, + uint256 eventNonce, + bool initiatewithdrawal, + address depositor + ); + + event FundsPaidWithMessage(bytes32 messageHash, address forwarder, uint256 nonce, bool execFlag, bytes execData); + + struct DestDetails { + uint32 domainId; + uint256 fee; + bool isSet; + } + + struct RelayData { + uint256 amount; + bytes32 srcChainId; + uint256 depositId; + address destToken; + address recipient; + } + + struct RelayDataMessage { + uint256 amount; + bytes32 srcChainId; + uint256 depositId; + address destToken; + address recipient; + bytes message; + } + + struct DepositData { + uint256 partnerId; + uint256 amount; + uint256 destAmount; + address srcToken; + address refundRecipient; + bytes32 destChainIdBytes; + } + + function iDepositUSDC( + uint256 partnerId, + bytes32 destChainIdBytes, + bytes32 recipient, + uint256 amount + ) + external + payable; + + function iDeposit( + DepositData memory depositData, + bytes memory destToken, + bytes memory recipient + ) + external + payable; + + function iDepositInfoUpdate( + address srcToken, + uint256 feeAmount, + uint256 depositId, + bool initiatewithdrawal + ) + external + payable; + + function iDepositMessage( + DepositData memory depositData, + bytes memory destToken, + bytes memory recipient, + bytes memory message + ) + external + payable; + + function iRelay(RelayData memory relayData) external payable; + + function depositNonce() external view returns (uint256); + + function iRelayMessage(RelayDataMessage memory relayData) external payable; +} diff --git a/src/libraries/Error.sol b/src/libraries/Error.sol new file mode 100644 index 0000000..bdd16cc --- /dev/null +++ b/src/libraries/Error.sol @@ -0,0 +1,143 @@ +// c=< +// | +// | ////\ 1@2 +// @@ | /___\** @@@2 @@@@@@@@@@@@@@@@@@@@@@ +// @@@ | |~L~ |* @@@@@@ @@@ @@@@@ @@@@ @@@ @@@@ @@@ @@@@@@@@ @@@@ @@@@ @@@ @@@@@@@@@ @@@@ @@@@ +// @@@@@ | \=_/8 @@@@1@@ @@@ @@@@@ @@@@ @@@@ @@@ @@@@@ @@@ @@@@@@@@@ @@@@ @@@@@ @@@@ @@@@@@@@@ @@@@ +// @@@@ +// @@@@@@| _ /| |\__ @@@@@@@@2 @@@ @@@@@ @@@@ @@@@ @@@ @@@@@@@ @@@ @@@@ @@@@ @@@@@@ @@@@ @@@ @@@@@@@ +// 1@@@@@@|\ \___/) @@1@@@@@2 ~~~ ~~~~~ @@@@ ~~@@ ~~~ ~~~~~~~~~~~ ~~~~ ~~~~ ~~~~~~~~~~~ ~@@ @@@@@ +// 2@@@@@ | \ \ / | @@@@@@2 @@@ @@@@@ @@@@ @@@@ @@@ @@@@@@@@@@@ @@@@@@@@@ @@@@ @@@@@@@@@@@ @@@@@@@@@ @@@@@ +// 2@@@@ |_ > <|__ @@1@12 @@@ @@@@@ @@@@ @@@@ @@@ @@@@ @@@@@@ @@@@ @@@@ @@@@ @@@@@@ @@@ @@@@@@@ +// @@@@ / _| / \/ \ @@1@ @@@ @@@ @@@@ @@@@ @@@ @@@@ @@@@@ @@@@ @@@@ @@@@ @@@@@ @@@@@@@@@ @@@@ +// @@@@ +// @@ / |^\/ | | @@1 @@@ @@@@ @@@@ @@@ @@@@ @@@ @@@@ @@@@ @@@ @@@@ @@@@@@@@@ @@@@ @@@@ +// / / ---- \ \\\= @@ @@@@@@@@@@@@@@@@@@@@@@ +// \___/ -------- ~~ @@@ +// @@ | | | | -- @@ +// ———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— + +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.21; + +library Error { + /*/////////////////////////////////////////////////////////////// + GENERIC + ///////////////////////////////////////////////////////////////*/ + + error AlreadyExists(); + + error DoesNotExist(); + + error Unauthorized(); + + error InvalidLength(); + + error NotOwner(); + + error InvalidCallerContext(); + + error InvalidChainId(); + + /*/////////////////////////////////////////////////////////////// + ADDRESS + ///////////////////////////////////////////////////////////////*/ + + error ImplementationMismatch(address implementation, address latestImplementation); + + error InvalidWithdrawalAddress(address to); + + error NullAddress(); + + error SameAddress(); + + error InvalidSolanaAddress(); + + error AddressAlreadySet(); + + error InsufficientAllowlistDelay(); + + /*/////////////////////////////////////////////////////////////// + AMOUNT / BALANCE + ///////////////////////////////////////////////////////////////*/ + + error InsufficientBalance(); + + error InsufficientWithdrawalAmount(uint256 amount); + + error InsufficientBalanceForFee(uint256 balance, uint256 fee); + + error InvalidNonce(bytes32 nonce); + + error ZeroValue(); + + error AmountDeltaZeroValue(); + + error DecimalsMoreThan18(uint256 decimals); + + error BridgeMaxAmountExceeded(); + + error ETHTransferFailed(); + + error OutOfBounds(); + + /*/////////////////////////////////////////////////////////////// + ACCOUNT + ///////////////////////////////////////////////////////////////*/ + + error CreateAccountDisabled(); + + error InvalidKeysForSalt(); + + error PredictAddressDisabled(); + + error FundsRecoveryActivationDeadlinePending(); + + error InvalidAppAccount(); + + error InvalidAppBeacon(); + + /*/////////////////////////////////////////////////////////////// + KEY MANAGEMENT + ///////////////////////////////////////////////////////////////*/ + + error InvalidRequest(); + + error InvalidKeySignature(address from); + + error KeyAlreadyInvalid(); + + error KeyAlreadyValid(); + + error KeyNotFound(); + + error CannotRemoveLastKey(); + + /*/////////////////////////////////////////////////////////////// + BRIDGING + ///////////////////////////////////////////////////////////////*/ + + error InvalidCCTPDomain(); + + error InvalidWormholeChainId(); + + /*/////////////////////////////////////////////////////////////// + WORMHOLE + ///////////////////////////////////////////////////////////////*/ + + error UnexpectedResultLength(); + + error InvalidBlockFinality(); + + /*/////////////////////////////////////////////////////////////// + GAS FEE REBATE + ///////////////////////////////////////////////////////////////*/ + + error InvalidDeductGasFunction(bytes4 sig); + + /*/////////////////////////////////////////////////////////////// + FEATURE FLAGS + ///////////////////////////////////////////////////////////////*/ + + error FundsRecoveryNotActive(); +} diff --git a/test/Foo.t.sol b/test/Foo.t.sol index 727337a..5969415 100644 --- a/test/Foo.t.sol +++ b/test/Foo.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity >=0.8.25 <0.9.0; +pragma solidity >=0.8.21; import { Test } from "forge-std/src/Test.sol"; import { console2 } from "forge-std/src/console2.sol"; @@ -25,7 +25,8 @@ contract FooTest is Test { function test_Example() external view { console2.log("Hello World"); uint256 x = 42; - assertEq(foo.id(x), x, "value mismatch"); + uint256 y = foo.id(x); + assert(y == x); } /// @dev Fuzz test that provides random values for an unsigned integer, but which rejects zero as an input. @@ -33,7 +34,7 @@ contract FooTest is Test { /// See https://twitter.com/PaulRBerg/status/1622558791685242880 function testFuzz_Example(uint256 x) external view { vm.assume(x != 0); // or x = bound(x, 1, 100) - assertEq(foo.id(x), x, "value mismatch"); + assert(foo.id(x) == x); } /// @dev Fork test that runs against an Ethereum Mainnet fork. For this to work, you need to set `API_KEY_ALCHEMY`