diff --git a/.env.example b/.env.example index ad649309..caeded5c 100644 --- a/.env.example +++ b/.env.example @@ -8,6 +8,10 @@ META_SWAP_ADAPTER_OWNER_ADDRESS= METASWAP_ADDRESS= SWAPS_API_SIGNER_ADDRESS= ARGS_EQUALITY_CHECK_ENFORCER_ADDRESS= +VEDA_ADAPTER_OWNER_ADDRESS= +VEDA_BORING_VAULT_ADDRESS= +VEDA_TELLER_ADDRESS= +VEDA_DEPOSIT_TOKEN_ADDRESS= # Required for verifying contracts ETHERSCAN_API_KEY= diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7ba85d68..09fc09bc 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -51,3 +51,5 @@ jobs: run: forge test -vvv env: LINEA_RPC_URL: ${{ secrets.LINEA_RPC_URL }} + ARBITRUM_RPC_URL: ${{ secrets.ARBITRUM_RPC_URL }} + RPC_API_KEY: ${{ secrets.RPC_API_KEY }} diff --git a/audits/cyfrin/cyfrin-4-26.pdf b/audits/cyfrin/cyfrin-4-26.pdf new file mode 100644 index 00000000..e1bbf512 Binary files /dev/null and b/audits/cyfrin/cyfrin-4-26.pdf differ diff --git a/broadcast/DeployVedaAdapter.s.sol/143/run-1780040823916.json b/broadcast/DeployVedaAdapter.s.sol/143/run-1780040823916.json new file mode 100644 index 00000000..9f0423bb --- /dev/null +++ b/broadcast/DeployVedaAdapter.s.sol/143/run-1780040823916.json @@ -0,0 +1,85 @@ +{ + "transactions": [ + { + "hash": "0x2c424a46501dbc942a4135799208c4e538b65f7330e6cdb943f2e065154ac8d8", + "transactionType": "CREATE2", + "contractName": "VedaAdapter", + "contractAddress": "0x8e6bae671d7406898d958b9799c37b96058aba01", + "function": null, + "arguments": [ + "0x74dbD45B505fB3b9F94C31E5f15c41b8E9883B04", + "0xdb9B1e94B5b69Df7e401DDbedE43491141047dB3", + "0x1C8a336051D2024E318A229d01F9F6CF96efD316", + "0xB30755C750E0A7E5BeD3dDAf0D9948Cf2b1CDc87", + "0xacA92E438df0B2401fF60dA7E4337B687a2435DA" + ], + "transaction": { + "from": "0xb0403b32f54d0bd752113f4009e8b534c6669f44", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "value": "0x0", + "input": "0x4741544f520000000000000000000000000000000000000000000000000000006101006040523480156200001257600080fd5b5060405162001edd38038062001edd8339810160408190526200003591620004d4565b846001600160a01b0381166200006657604051631e4fbdf760e01b8152600060048201526024015b60405180910390fd5b620000718162000112565b506001600160a01b03841615806200009057506001600160a01b038316155b80620000a357506001600160a01b038216155b80620000b657506001600160a01b038116155b15620000d55760405163f6b2911f60e01b815260040160405180910390fd5b6001600160a01b0380851660805283811660a081905283821660c05290821660e0819052620001079160001962000130565b505050505062000599565b600180546001600160a01b03191690556200012d81620001fc565b50565b604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180516001600160e01b0390811663095ea7b360e01b179091526200018a90859083906200024c16565b620001f657604080516001600160a01b038516602482015260006044808301919091528251808303909101815260649091019091526020810180516001600160e01b0390811663095ea7b360e01b17909152620001ea918691620002fd16565b620001f68482620002fd565b50505050565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6000806000846001600160a01b0316846040516200026b919062000544565b6000604051808303816000865af19150503d8060008114620002aa576040519150601f19603f3d011682016040523d82523d6000602084013e620002af565b606091505b5091509150818015620002dd575080511580620002dd575080806020019051810190620002dd919062000575565b8015620002f457506000856001600160a01b03163b115b95945050505050565b6000620003146001600160a01b038416836200036c565b905080516000141580156200033c5750808060200190518101906200033a919062000575565b155b156200036757604051635274afe760e01b81526001600160a01b03841660048201526024016200005d565b505050565b60606200037c8383600062000383565b9392505050565b606081471015620003aa5760405163cd78605960e01b81523060048201526024016200005d565b600080856001600160a01b03168486604051620003c8919062000544565b60006040518083038185875af1925050503d806000811462000407576040519150601f19603f3d011682016040523d82523d6000602084013e6200040c565b606091505b5090925090506200041f86838362000429565b9695505050505050565b60608262000442576200043c826200048d565b6200037c565b81511580156200045a57506001600160a01b0384163b155b156200048557604051639996b31560e01b81526001600160a01b03851660048201526024016200005d565b50806200037c565b8051156200049e5780518082602001fd5b604051630a12f52160e11b815260040160405180910390fd5b80516001600160a01b0381168114620004cf57600080fd5b919050565b600080600080600060a08688031215620004ed57600080fd5b620004f886620004b7565b94506200050860208701620004b7565b93506200051860408701620004b7565b92506200052860608701620004b7565b91506200053860808701620004b7565b90509295509295909350565b6000825160005b818110156200056757602081860181015185830152016200054b565b506000920191825250919050565b6000602082840312156200058857600080fd5b815180151581146200037c57600080fd5b60805160a05160c05160e0516118b662000627600039600081816101a601528181610285015281816108730152818161092701528181610c5f01528181610d3f0152610dfa01526000818161011c015281816108b50152610d8801526000818161023e015281816102a70152610768015260008181610204015281816107f30152610cbf01526118b66000f3fe608060405234801561001057600080fd5b50600436106100f55760003560e01c8063ae92a63a11610097578063e30c397811610066578063e30c3978146101ee578063ea4d3c9b146101ff578063f2fde38b14610226578063f3b977841461023957600080fd5b8063ae92a63a1461018e578063c89039c5146101a1578063d0f2a33e146101c8578063d83d314a146101db57600080fd5b8063715018a6116100d3578063715018a61461015a578063758d94581461016257806379ba5097146101755780638da5cb5b1461017d57600080fd5b80632a490331146100fa5780633db1b8e51461010f57806357edab4e14610117575b600080fd5b61010d610108366004611205565b610260565b005b61010d610270565b61013e7f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b03909116815260200160405180910390f35b61010d6102d0565b61010d610170366004611266565b6102e2565b61010d610377565b6000546001600160a01b031661013e565b61010d61019c366004611205565b6103c0565b61013e7f000000000000000000000000000000000000000000000000000000000000000081565b61010d6101d63660046112a8565b6103cb565b61010d6101e93660046112a8565b61047c565b6001546001600160a01b031661013e565b61013e7f000000000000000000000000000000000000000000000000000000000000000081565b61010d6102343660046112ea565b610524565b61013e7f000000000000000000000000000000000000000000000000000000000000000081565b61026b838383610595565b505050565b6102786109a7565b6102ce6001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000167f00000000000000000000000000000000000000000000000000000000000000006000196109d4565b565b6102d86109a7565b6102ce6000610a98565b6102ea6109a7565b6001600160a01b03811661031157604051634e46966960e11b815260040160405180910390fd5b6103256001600160a01b0384168284610ab1565b806001600160a01b0316836001600160a01b03167f0e191ee1e354192688af5ec7da0cc3d7f54ecb056e6418d42e73ddc4c37d36378460405161036a91815260200190565b60405180910390a3505050565b60015433906001600160a01b031681146103b45760405163118cdaa760e01b81526001600160a01b03821660048201526024015b60405180910390fd5b6103bd81610a98565b50565b61026b838383610ae2565b8060008190036103ee5760405163ca3487f760e01b815260040160405180910390fd5b60005b81811015610440573684848381811061040c5761040c611307565b905060200281019061041e919061131d565b905061043761042d828061133d565b8360200135610ae2565b506001016103f1565b5060405181815233907f25a16c13690e68b4c9d49e1fbab1e490d7a6710945c61a77c745d0512b501e36906020015b60405180910390a2505050565b80600081900361049f5760405163ca3487f760e01b815260040160405180910390fd5b60005b818110156104f157368484838181106104bd576104bd611307565b90506020028101906104cf919061131d565b90506104e86104de828061133d565b8360200135610595565b506001016104a2565b5060405181815233907f52cc3cbb9ba447be2acf8c157cfb0f7b133158f4c4ad10a43f43d77c18c371689060200161046f565b61052c6109a7565b600180546001600160a01b0383166001600160a01b0319909116811790915561055d6000546001600160a01b031690565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b8160028110156105b757604051622d38d560e61b815260040160405180910390fd5b6000610625858560008181106105cf576105cf611307565b90506020028101906105e19190611387565b6105ef90606081019061133d565b600081811061060057610600611307565b9050602002810190610612919061139d565b6106209060208101906113b3565b610e66565b9050600085856106366001866113fa565b81811061064557610645611307565b90506020028101906106579190611387565b6106689060408101906020016112ea565b60408051600180825281830190925291925060009190816020015b606081526020019060019003908161068357905050905086866040516020016106ad929190611594565b604051602081830303815290604052816000815181106106cf576106cf611307565b6020908102919091010152604080516001808252818301909252600091816020016020820280368337019050509050610706610ea8565b8160008151811061071957610719611307565b6020908102919091010152604080516001808252818301909252600091816020015b606081526020019060019003908161073b575050604051306024820152604481018790529091506107be907f0000000000000000000000000000000000000000000000000000000000000000906000906064015b60408051601f198184030181529190526020810180516001600160e01b031663a9059cbb60e01b179052610ebb565b816000815181106107d1576107d1611307565b602090810291909101015260405163cef6d20960e01b81526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169063cef6d2099061082c9086908690869060040161171b565b600060405180830381600087803b15801561084657600080fd5b505af115801561085a573d6000803e3d6000fd5b50506040516316762eed60e01b81526001600160a01b037f00000000000000000000000000000000000000000000000000000000000000008116600483015260248201899052604482018b90528781166064830152600093507f00000000000000000000000000000000000000000000000000000000000000001691506316762eed906084016020604051808303816000875af11580156108ff573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109239190611785565b90507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316856001600160a01b03167fa7b6bdc54af4440592478ca9547f46cc8b433eaf224768065fc6b910aab5cc1f8884604051610993929190918252602082015260400190565b60405180910390a350505050505050505050565b6000546001600160a01b031633146102ce5760405163118cdaa760e01b81523360048201526024016103ab565b604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180516001600160e01b031663095ea7b360e01b179052610a258482610eea565b610a92576040516001600160a01b03848116602483015260006044830152610a8891869182169063095ea7b3906064015b604051602081830303815290604052915060e01b6020820180516001600160e01b038381831617835250505050610f94565b610a928482610f94565b50505050565b600180546001600160a01b03191690556103bd81610ff7565b6040516001600160a01b0383811660248301526044820183905261026b91859182169063a9059cbb90606401610a56565b816002811015610b0457604051622d38d560e61b815260040160405180910390fd5b6000610b1c858560008181106105cf576105cf611307565b905060008585610b2d6001866113fa565b818110610b3c57610b3c611307565b9050602002810190610b4e9190611387565b610b5f9060408101906020016112ea565b60408051600180825281830190925291925060009190816020015b6060815260200190600190039081610b7a5790505090508686604051602001610ba4929190611594565b60405160208183030381529060405281600081518110610bc657610bc6611307565b6020908102919091010152604080516001808252818301909252600091816020016020820280368337019050509050610bfd610ea8565b81600081518110610c1057610c10611307565b6020908102919091010152604080516001808252818301909252600091816020015b6060815260200190600190039081610c3257505060405130602482015260448101879052909150610c8a907f00000000000000000000000000000000000000000000000000000000000000009060009060640161078f565b81600081518110610c9d57610c9d611307565b602090810291909101015260405163cef6d20960e01b81526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169063cef6d20990610cf89086908690869060040161171b565b600060405180830381600087803b158015610d1257600080fd5b505af1158015610d26573d6000803e3d6000fd5b5050604051632a22f31f60e01b81526001600160a01b037f00000000000000000000000000000000000000000000000000000000000000008116600483015260248201899052604482018b9052878116606483015260006084830181905293507f0000000000000000000000000000000000000000000000000000000000000000169150632a22f31f9060a4016020604051808303816000875af1158015610dd2573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610df69190611785565b90507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316856001600160a01b03167fe1e067eab571218f837c5015c854ebbbc631dc6ec63b28e9deb6718d78a858b78884604051610993929190918252602082015260400190565b60006034821015610e8a576040516386a371ad60e01b815260040160405180910390fd5b610e9860346014848661179e565b610ea1916117c8565b9392505050565b6000610eb681808080611047565b905090565b6060838383604051602001610ed2939291906117e6565b60405160208183030381529060405290509392505050565b6000806000846001600160a01b031684604051610f079190611825565b6000604051808303816000865af19150503d8060008114610f44576040519150601f19603f3d011682016040523d82523d6000602084013e610f49565b606091505b5091509150818015610f73575080511580610f73575080806020019051810190610f739190611837565b8015610f8957506000856001600160a01b03163b115b925050505b92915050565b6000610fa96001600160a01b038416836110b2565b90508051600014158015610fce575080806020019051810190610fcc9190611837565b155b1561026b57604051635274afe760e01b81526001600160a01b03841660048201526024016103ab565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b604080516001600160f81b03198087166020830152851660218201526000602282018190526001600160e01b03198516602683015269ffffffffffffffffffff198416602a83015291016040516020818303038152906040526110a990611859565b95945050505050565b6060610ea18383600084600080856001600160a01b031684866040516110d89190611825565b60006040518083038185875af1925050503d8060008114611115576040519150601f19603f3d011682016040523d82523d6000602084013e61111a565b606091505b509150915061112a868383611134565b9695505050505050565b6060826111495761114482611190565b610ea1565b815115801561116057506001600160a01b0384163b155b1561118957604051639996b31560e01b81526001600160a01b03851660048201526024016103ab565b5080610ea1565b8051156111a05780518082602001fd5b604051630a12f52160e11b815260040160405180910390fd5b60008083601f8401126111cb57600080fd5b50813567ffffffffffffffff8111156111e357600080fd5b6020830191508360208260051b85010111156111fe57600080fd5b9250929050565b60008060006040848603121561121a57600080fd5b833567ffffffffffffffff81111561123157600080fd5b61123d868287016111b9565b909790965060209590950135949350505050565b6001600160a01b03811681146103bd57600080fd5b60008060006060848603121561127b57600080fd5b833561128681611251565b925060208401359150604084013561129d81611251565b809150509250925092565b600080602083850312156112bb57600080fd5b823567ffffffffffffffff8111156112d257600080fd5b6112de858286016111b9565b90969095509350505050565b6000602082840312156112fc57600080fd5b8135610ea181611251565b634e487b7160e01b600052603260045260246000fd5b60008235603e1983360301811261133357600080fd5b9190910192915050565b6000808335601e1984360301811261135457600080fd5b83018035915067ffffffffffffffff82111561136f57600080fd5b6020019150600581901b36038213156111fe57600080fd5b6000823560be1983360301811261133357600080fd5b60008235605e1983360301811261133357600080fd5b6000808335601e198436030181126113ca57600080fd5b83018035915067ffffffffffffffff8211156113e557600080fd5b6020019150368190038213156111fe57600080fd5b81810381811115610f8e57634e487b7160e01b600052601160045260246000fd5b6000808335601e1984360301811261143257600080fd5b830160208101925035905067ffffffffffffffff81111561145257600080fd5b8060051b36038213156111fe57600080fd5b6000808335601e1984360301811261147b57600080fd5b830160208101925035905067ffffffffffffffff81111561149b57600080fd5b8036038213156111fe57600080fd5b81835281816020850137506000828201602090810191909152601f909101601f19169091010190565b60008383855260208086019550808560051b830101846000805b8881101561158657858403601f19018a52823536899003605e19018112611512578283fd5b88016060813561152181611251565b6001600160a01b0316865261153882880183611464565b828989015261154a83890182846114aa565b92505050604061155c81840184611464565b9350878303828901526115708385836114aa565b9d89019d975050509386019350506001016114ed565b509198975050505050505050565b60208082528181018390526000906040808401600586901b850182018785805b8981101561167857888403603f190185528235368c900360be190181126115d9578283fd5b8b0160c081356115e881611251565b6001600160a01b039081168752828a01359061160382611251565b16868a01528188013588870152606061161e8184018461141b565b83838a0152611630848a0182846114d3565b9350505050608080830135818801525060a061164e81840184611464565b9350878303828901526116628385836114aa565b988b0198975050509388019350506001016115b4565b50919998505050505050505050565b60005b838110156116a257818101518382015260200161168a565b50506000910152565b600082825180855260208086019550808260051b84010181860160005b8481101561170e57601f1980878503018a52825180518086526116f081888801898501611687565b9a86019a601f019091169390930184019250908301906001016116c8565b5090979650505050505050565b60608152600061172e60608301866116ab565b82810360208481019190915285518083528682019282019060005b8181101561176557845183529383019391830191600101611749565b5050848103604086015261177981876116ab565b98975050505050505050565b60006020828403121561179757600080fd5b5051919050565b600080858511156117ae57600080fd5b838611156117bb57600080fd5b5050820193919092039150565b80356020831015610f8e57600019602084900360031b1b1692915050565b6bffffffffffffffffffffffff198460601b16815282601482015260008251611816816034850160208701611687565b91909101603401949350505050565b60008251611333818460208701611687565b60006020828403121561184957600080fd5b81518015158114610ea157600080fd5b8051602080830151919081101561187a576000198160200360031b1b821691505b5091905056fea2646970667358221220bc6988899ae8b59e2fa90859a4720d5b1e7e30b643818cea7884ed48a160dc6864736f6c6343000817003300000000000000000000000074dbd45b505fb3b9f94c31e5f15c41b8e9883b04000000000000000000000000db9b1e94b5b69df7e401ddbede43491141047db30000000000000000000000001c8a336051d2024e318a229d01f9f6cf96efd316000000000000000000000000b30755c750e0a7e5bed3ddaf0d9948cf2b1cdc87000000000000000000000000aca92e438df0b2401ff60da7e4337b687a2435da", + "nonce": "0x2c", + "chainId": "0x8f" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x559917", + "logs": [ + { + "address": "0x8e6bae671d7406898d958b9799c37b96058aba01", + "topics": [ + "0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x00000000000000000000000074dbd45b505fb3b9f94c31e5f15c41b8e9883b04" + ], + "data": "0x", + "blockHash": "0x2719e2fc5536b0d86d32cc58d0b9535d85892ca7c9852276591c0b2e380a823b", + "blockNumber": "0x4a21778", + "blockTimestamp": "0x6a194477", + "transactionHash": "0x2c424a46501dbc942a4135799208c4e538b65f7330e6cdb943f2e065154ac8d8", + "transactionIndex": "0x5", + "logIndex": "0x37", + "removed": false + }, + { + "address": "0xaca92e438df0b2401ff60da7e4337b687a2435da", + "topics": [ + "0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925", + "0x0000000000000000000000008e6bae671d7406898d958b9799c37b96058aba01", + "0x0000000000000000000000001c8a336051d2024e318a229d01f9f6cf96efd316" + ], + "data": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "blockHash": "0x2719e2fc5536b0d86d32cc58d0b9535d85892ca7c9852276591c0b2e380a823b", + "blockNumber": "0x4a21778", + "blockTimestamp": "0x6a194477", + "transactionHash": "0x2c424a46501dbc942a4135799208c4e538b65f7330e6cdb943f2e065154ac8d8", + "transactionIndex": "0x5", + "logIndex": "0x38", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000800000000000000001000000000000000000000000004000000000000000000000000000200000000000000000000000000000000000000001000000000000000000000000000000000000020008000000000200000800000000000000800000000000000000400000000010000000000000400000000000000000000000000000000000000001020000000000000000000000000000000000000000000000004000080000000000000000004000000000000000000000000000000000000000000000000020000010000020000100000000000000000000000400000000000000000000000000", + "transactionHash": "0x2c424a46501dbc942a4135799208c4e538b65f7330e6cdb943f2e065154ac8d8", + "transactionIndex": "0x5", + "blockHash": "0x2719e2fc5536b0d86d32cc58d0b9535d85892ca7c9852276591c0b2e380a823b", + "blockNumber": "0x4a21778", + "gasUsed": "0x1f6b8c", + "effectiveGasPrice": "0x17bfac7c00", + "from": "0xb0403b32f54d0bd752113f4009e8b534c6669f44", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null + } + ], + "libraries": [], + "pending": [], + "returns": {}, + "timestamp": 1780040823916, + "chain": 143, + "commit": "d6fe82a" +} \ No newline at end of file diff --git a/broadcast/DeployVedaAdapter.s.sol/143/run-latest.json b/broadcast/DeployVedaAdapter.s.sol/143/run-latest.json new file mode 100644 index 00000000..9f0423bb --- /dev/null +++ b/broadcast/DeployVedaAdapter.s.sol/143/run-latest.json @@ -0,0 +1,85 @@ +{ + "transactions": [ + { + "hash": "0x2c424a46501dbc942a4135799208c4e538b65f7330e6cdb943f2e065154ac8d8", + "transactionType": "CREATE2", + "contractName": "VedaAdapter", + "contractAddress": "0x8e6bae671d7406898d958b9799c37b96058aba01", + "function": null, + "arguments": [ + "0x74dbD45B505fB3b9F94C31E5f15c41b8E9883B04", + "0xdb9B1e94B5b69Df7e401DDbedE43491141047dB3", + "0x1C8a336051D2024E318A229d01F9F6CF96efD316", + "0xB30755C750E0A7E5BeD3dDAf0D9948Cf2b1CDc87", + "0xacA92E438df0B2401fF60dA7E4337B687a2435DA" + ], + "transaction": { + "from": "0xb0403b32f54d0bd752113f4009e8b534c6669f44", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "value": "0x0", + "input": "0x4741544f520000000000000000000000000000000000000000000000000000006101006040523480156200001257600080fd5b5060405162001edd38038062001edd8339810160408190526200003591620004d4565b846001600160a01b0381166200006657604051631e4fbdf760e01b8152600060048201526024015b60405180910390fd5b620000718162000112565b506001600160a01b03841615806200009057506001600160a01b038316155b80620000a357506001600160a01b038216155b80620000b657506001600160a01b038116155b15620000d55760405163f6b2911f60e01b815260040160405180910390fd5b6001600160a01b0380851660805283811660a081905283821660c05290821660e0819052620001079160001962000130565b505050505062000599565b600180546001600160a01b03191690556200012d81620001fc565b50565b604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180516001600160e01b0390811663095ea7b360e01b179091526200018a90859083906200024c16565b620001f657604080516001600160a01b038516602482015260006044808301919091528251808303909101815260649091019091526020810180516001600160e01b0390811663095ea7b360e01b17909152620001ea918691620002fd16565b620001f68482620002fd565b50505050565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6000806000846001600160a01b0316846040516200026b919062000544565b6000604051808303816000865af19150503d8060008114620002aa576040519150601f19603f3d011682016040523d82523d6000602084013e620002af565b606091505b5091509150818015620002dd575080511580620002dd575080806020019051810190620002dd919062000575565b8015620002f457506000856001600160a01b03163b115b95945050505050565b6000620003146001600160a01b038416836200036c565b905080516000141580156200033c5750808060200190518101906200033a919062000575565b155b156200036757604051635274afe760e01b81526001600160a01b03841660048201526024016200005d565b505050565b60606200037c8383600062000383565b9392505050565b606081471015620003aa5760405163cd78605960e01b81523060048201526024016200005d565b600080856001600160a01b03168486604051620003c8919062000544565b60006040518083038185875af1925050503d806000811462000407576040519150601f19603f3d011682016040523d82523d6000602084013e6200040c565b606091505b5090925090506200041f86838362000429565b9695505050505050565b60608262000442576200043c826200048d565b6200037c565b81511580156200045a57506001600160a01b0384163b155b156200048557604051639996b31560e01b81526001600160a01b03851660048201526024016200005d565b50806200037c565b8051156200049e5780518082602001fd5b604051630a12f52160e11b815260040160405180910390fd5b80516001600160a01b0381168114620004cf57600080fd5b919050565b600080600080600060a08688031215620004ed57600080fd5b620004f886620004b7565b94506200050860208701620004b7565b93506200051860408701620004b7565b92506200052860608701620004b7565b91506200053860808701620004b7565b90509295509295909350565b6000825160005b818110156200056757602081860181015185830152016200054b565b506000920191825250919050565b6000602082840312156200058857600080fd5b815180151581146200037c57600080fd5b60805160a05160c05160e0516118b662000627600039600081816101a601528181610285015281816108730152818161092701528181610c5f01528181610d3f0152610dfa01526000818161011c015281816108b50152610d8801526000818161023e015281816102a70152610768015260008181610204015281816107f30152610cbf01526118b66000f3fe608060405234801561001057600080fd5b50600436106100f55760003560e01c8063ae92a63a11610097578063e30c397811610066578063e30c3978146101ee578063ea4d3c9b146101ff578063f2fde38b14610226578063f3b977841461023957600080fd5b8063ae92a63a1461018e578063c89039c5146101a1578063d0f2a33e146101c8578063d83d314a146101db57600080fd5b8063715018a6116100d3578063715018a61461015a578063758d94581461016257806379ba5097146101755780638da5cb5b1461017d57600080fd5b80632a490331146100fa5780633db1b8e51461010f57806357edab4e14610117575b600080fd5b61010d610108366004611205565b610260565b005b61010d610270565b61013e7f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b03909116815260200160405180910390f35b61010d6102d0565b61010d610170366004611266565b6102e2565b61010d610377565b6000546001600160a01b031661013e565b61010d61019c366004611205565b6103c0565b61013e7f000000000000000000000000000000000000000000000000000000000000000081565b61010d6101d63660046112a8565b6103cb565b61010d6101e93660046112a8565b61047c565b6001546001600160a01b031661013e565b61013e7f000000000000000000000000000000000000000000000000000000000000000081565b61010d6102343660046112ea565b610524565b61013e7f000000000000000000000000000000000000000000000000000000000000000081565b61026b838383610595565b505050565b6102786109a7565b6102ce6001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000167f00000000000000000000000000000000000000000000000000000000000000006000196109d4565b565b6102d86109a7565b6102ce6000610a98565b6102ea6109a7565b6001600160a01b03811661031157604051634e46966960e11b815260040160405180910390fd5b6103256001600160a01b0384168284610ab1565b806001600160a01b0316836001600160a01b03167f0e191ee1e354192688af5ec7da0cc3d7f54ecb056e6418d42e73ddc4c37d36378460405161036a91815260200190565b60405180910390a3505050565b60015433906001600160a01b031681146103b45760405163118cdaa760e01b81526001600160a01b03821660048201526024015b60405180910390fd5b6103bd81610a98565b50565b61026b838383610ae2565b8060008190036103ee5760405163ca3487f760e01b815260040160405180910390fd5b60005b81811015610440573684848381811061040c5761040c611307565b905060200281019061041e919061131d565b905061043761042d828061133d565b8360200135610ae2565b506001016103f1565b5060405181815233907f25a16c13690e68b4c9d49e1fbab1e490d7a6710945c61a77c745d0512b501e36906020015b60405180910390a2505050565b80600081900361049f5760405163ca3487f760e01b815260040160405180910390fd5b60005b818110156104f157368484838181106104bd576104bd611307565b90506020028101906104cf919061131d565b90506104e86104de828061133d565b8360200135610595565b506001016104a2565b5060405181815233907f52cc3cbb9ba447be2acf8c157cfb0f7b133158f4c4ad10a43f43d77c18c371689060200161046f565b61052c6109a7565b600180546001600160a01b0383166001600160a01b0319909116811790915561055d6000546001600160a01b031690565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b8160028110156105b757604051622d38d560e61b815260040160405180910390fd5b6000610625858560008181106105cf576105cf611307565b90506020028101906105e19190611387565b6105ef90606081019061133d565b600081811061060057610600611307565b9050602002810190610612919061139d565b6106209060208101906113b3565b610e66565b9050600085856106366001866113fa565b81811061064557610645611307565b90506020028101906106579190611387565b6106689060408101906020016112ea565b60408051600180825281830190925291925060009190816020015b606081526020019060019003908161068357905050905086866040516020016106ad929190611594565b604051602081830303815290604052816000815181106106cf576106cf611307565b6020908102919091010152604080516001808252818301909252600091816020016020820280368337019050509050610706610ea8565b8160008151811061071957610719611307565b6020908102919091010152604080516001808252818301909252600091816020015b606081526020019060019003908161073b575050604051306024820152604481018790529091506107be907f0000000000000000000000000000000000000000000000000000000000000000906000906064015b60408051601f198184030181529190526020810180516001600160e01b031663a9059cbb60e01b179052610ebb565b816000815181106107d1576107d1611307565b602090810291909101015260405163cef6d20960e01b81526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169063cef6d2099061082c9086908690869060040161171b565b600060405180830381600087803b15801561084657600080fd5b505af115801561085a573d6000803e3d6000fd5b50506040516316762eed60e01b81526001600160a01b037f00000000000000000000000000000000000000000000000000000000000000008116600483015260248201899052604482018b90528781166064830152600093507f00000000000000000000000000000000000000000000000000000000000000001691506316762eed906084016020604051808303816000875af11580156108ff573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109239190611785565b90507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316856001600160a01b03167fa7b6bdc54af4440592478ca9547f46cc8b433eaf224768065fc6b910aab5cc1f8884604051610993929190918252602082015260400190565b60405180910390a350505050505050505050565b6000546001600160a01b031633146102ce5760405163118cdaa760e01b81523360048201526024016103ab565b604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180516001600160e01b031663095ea7b360e01b179052610a258482610eea565b610a92576040516001600160a01b03848116602483015260006044830152610a8891869182169063095ea7b3906064015b604051602081830303815290604052915060e01b6020820180516001600160e01b038381831617835250505050610f94565b610a928482610f94565b50505050565b600180546001600160a01b03191690556103bd81610ff7565b6040516001600160a01b0383811660248301526044820183905261026b91859182169063a9059cbb90606401610a56565b816002811015610b0457604051622d38d560e61b815260040160405180910390fd5b6000610b1c858560008181106105cf576105cf611307565b905060008585610b2d6001866113fa565b818110610b3c57610b3c611307565b9050602002810190610b4e9190611387565b610b5f9060408101906020016112ea565b60408051600180825281830190925291925060009190816020015b6060815260200190600190039081610b7a5790505090508686604051602001610ba4929190611594565b60405160208183030381529060405281600081518110610bc657610bc6611307565b6020908102919091010152604080516001808252818301909252600091816020016020820280368337019050509050610bfd610ea8565b81600081518110610c1057610c10611307565b6020908102919091010152604080516001808252818301909252600091816020015b6060815260200190600190039081610c3257505060405130602482015260448101879052909150610c8a907f00000000000000000000000000000000000000000000000000000000000000009060009060640161078f565b81600081518110610c9d57610c9d611307565b602090810291909101015260405163cef6d20960e01b81526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169063cef6d20990610cf89086908690869060040161171b565b600060405180830381600087803b158015610d1257600080fd5b505af1158015610d26573d6000803e3d6000fd5b5050604051632a22f31f60e01b81526001600160a01b037f00000000000000000000000000000000000000000000000000000000000000008116600483015260248201899052604482018b9052878116606483015260006084830181905293507f0000000000000000000000000000000000000000000000000000000000000000169150632a22f31f9060a4016020604051808303816000875af1158015610dd2573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610df69190611785565b90507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316856001600160a01b03167fe1e067eab571218f837c5015c854ebbbc631dc6ec63b28e9deb6718d78a858b78884604051610993929190918252602082015260400190565b60006034821015610e8a576040516386a371ad60e01b815260040160405180910390fd5b610e9860346014848661179e565b610ea1916117c8565b9392505050565b6000610eb681808080611047565b905090565b6060838383604051602001610ed2939291906117e6565b60405160208183030381529060405290509392505050565b6000806000846001600160a01b031684604051610f079190611825565b6000604051808303816000865af19150503d8060008114610f44576040519150601f19603f3d011682016040523d82523d6000602084013e610f49565b606091505b5091509150818015610f73575080511580610f73575080806020019051810190610f739190611837565b8015610f8957506000856001600160a01b03163b115b925050505b92915050565b6000610fa96001600160a01b038416836110b2565b90508051600014158015610fce575080806020019051810190610fcc9190611837565b155b1561026b57604051635274afe760e01b81526001600160a01b03841660048201526024016103ab565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b604080516001600160f81b03198087166020830152851660218201526000602282018190526001600160e01b03198516602683015269ffffffffffffffffffff198416602a83015291016040516020818303038152906040526110a990611859565b95945050505050565b6060610ea18383600084600080856001600160a01b031684866040516110d89190611825565b60006040518083038185875af1925050503d8060008114611115576040519150601f19603f3d011682016040523d82523d6000602084013e61111a565b606091505b509150915061112a868383611134565b9695505050505050565b6060826111495761114482611190565b610ea1565b815115801561116057506001600160a01b0384163b155b1561118957604051639996b31560e01b81526001600160a01b03851660048201526024016103ab565b5080610ea1565b8051156111a05780518082602001fd5b604051630a12f52160e11b815260040160405180910390fd5b60008083601f8401126111cb57600080fd5b50813567ffffffffffffffff8111156111e357600080fd5b6020830191508360208260051b85010111156111fe57600080fd5b9250929050565b60008060006040848603121561121a57600080fd5b833567ffffffffffffffff81111561123157600080fd5b61123d868287016111b9565b909790965060209590950135949350505050565b6001600160a01b03811681146103bd57600080fd5b60008060006060848603121561127b57600080fd5b833561128681611251565b925060208401359150604084013561129d81611251565b809150509250925092565b600080602083850312156112bb57600080fd5b823567ffffffffffffffff8111156112d257600080fd5b6112de858286016111b9565b90969095509350505050565b6000602082840312156112fc57600080fd5b8135610ea181611251565b634e487b7160e01b600052603260045260246000fd5b60008235603e1983360301811261133357600080fd5b9190910192915050565b6000808335601e1984360301811261135457600080fd5b83018035915067ffffffffffffffff82111561136f57600080fd5b6020019150600581901b36038213156111fe57600080fd5b6000823560be1983360301811261133357600080fd5b60008235605e1983360301811261133357600080fd5b6000808335601e198436030181126113ca57600080fd5b83018035915067ffffffffffffffff8211156113e557600080fd5b6020019150368190038213156111fe57600080fd5b81810381811115610f8e57634e487b7160e01b600052601160045260246000fd5b6000808335601e1984360301811261143257600080fd5b830160208101925035905067ffffffffffffffff81111561145257600080fd5b8060051b36038213156111fe57600080fd5b6000808335601e1984360301811261147b57600080fd5b830160208101925035905067ffffffffffffffff81111561149b57600080fd5b8036038213156111fe57600080fd5b81835281816020850137506000828201602090810191909152601f909101601f19169091010190565b60008383855260208086019550808560051b830101846000805b8881101561158657858403601f19018a52823536899003605e19018112611512578283fd5b88016060813561152181611251565b6001600160a01b0316865261153882880183611464565b828989015261154a83890182846114aa565b92505050604061155c81840184611464565b9350878303828901526115708385836114aa565b9d89019d975050509386019350506001016114ed565b509198975050505050505050565b60208082528181018390526000906040808401600586901b850182018785805b8981101561167857888403603f190185528235368c900360be190181126115d9578283fd5b8b0160c081356115e881611251565b6001600160a01b039081168752828a01359061160382611251565b16868a01528188013588870152606061161e8184018461141b565b83838a0152611630848a0182846114d3565b9350505050608080830135818801525060a061164e81840184611464565b9350878303828901526116628385836114aa565b988b0198975050509388019350506001016115b4565b50919998505050505050505050565b60005b838110156116a257818101518382015260200161168a565b50506000910152565b600082825180855260208086019550808260051b84010181860160005b8481101561170e57601f1980878503018a52825180518086526116f081888801898501611687565b9a86019a601f019091169390930184019250908301906001016116c8565b5090979650505050505050565b60608152600061172e60608301866116ab565b82810360208481019190915285518083528682019282019060005b8181101561176557845183529383019391830191600101611749565b5050848103604086015261177981876116ab565b98975050505050505050565b60006020828403121561179757600080fd5b5051919050565b600080858511156117ae57600080fd5b838611156117bb57600080fd5b5050820193919092039150565b80356020831015610f8e57600019602084900360031b1b1692915050565b6bffffffffffffffffffffffff198460601b16815282601482015260008251611816816034850160208701611687565b91909101603401949350505050565b60008251611333818460208701611687565b60006020828403121561184957600080fd5b81518015158114610ea157600080fd5b8051602080830151919081101561187a576000198160200360031b1b821691505b5091905056fea2646970667358221220bc6988899ae8b59e2fa90859a4720d5b1e7e30b643818cea7884ed48a160dc6864736f6c6343000817003300000000000000000000000074dbd45b505fb3b9f94c31e5f15c41b8e9883b04000000000000000000000000db9b1e94b5b69df7e401ddbede43491141047db30000000000000000000000001c8a336051d2024e318a229d01f9f6cf96efd316000000000000000000000000b30755c750e0a7e5bed3ddaf0d9948cf2b1cdc87000000000000000000000000aca92e438df0b2401ff60da7e4337b687a2435da", + "nonce": "0x2c", + "chainId": "0x8f" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x559917", + "logs": [ + { + "address": "0x8e6bae671d7406898d958b9799c37b96058aba01", + "topics": [ + "0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x00000000000000000000000074dbd45b505fb3b9f94c31e5f15c41b8e9883b04" + ], + "data": "0x", + "blockHash": "0x2719e2fc5536b0d86d32cc58d0b9535d85892ca7c9852276591c0b2e380a823b", + "blockNumber": "0x4a21778", + "blockTimestamp": "0x6a194477", + "transactionHash": "0x2c424a46501dbc942a4135799208c4e538b65f7330e6cdb943f2e065154ac8d8", + "transactionIndex": "0x5", + "logIndex": "0x37", + "removed": false + }, + { + "address": "0xaca92e438df0b2401ff60da7e4337b687a2435da", + "topics": [ + "0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925", + "0x0000000000000000000000008e6bae671d7406898d958b9799c37b96058aba01", + "0x0000000000000000000000001c8a336051d2024e318a229d01f9f6cf96efd316" + ], + "data": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "blockHash": "0x2719e2fc5536b0d86d32cc58d0b9535d85892ca7c9852276591c0b2e380a823b", + "blockNumber": "0x4a21778", + "blockTimestamp": "0x6a194477", + "transactionHash": "0x2c424a46501dbc942a4135799208c4e538b65f7330e6cdb943f2e065154ac8d8", + "transactionIndex": "0x5", + "logIndex": "0x38", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000800000000000000001000000000000000000000000004000000000000000000000000000200000000000000000000000000000000000000001000000000000000000000000000000000000020008000000000200000800000000000000800000000000000000400000000010000000000000400000000000000000000000000000000000000001020000000000000000000000000000000000000000000000004000080000000000000000004000000000000000000000000000000000000000000000000020000010000020000100000000000000000000000400000000000000000000000000", + "transactionHash": "0x2c424a46501dbc942a4135799208c4e538b65f7330e6cdb943f2e065154ac8d8", + "transactionIndex": "0x5", + "blockHash": "0x2719e2fc5536b0d86d32cc58d0b9535d85892ca7c9852276591c0b2e380a823b", + "blockNumber": "0x4a21778", + "gasUsed": "0x1f6b8c", + "effectiveGasPrice": "0x17bfac7c00", + "from": "0xb0403b32f54d0bd752113f4009e8b534c6669f44", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null + } + ], + "libraries": [], + "pending": [], + "returns": {}, + "timestamp": 1780040823916, + "chain": 143, + "commit": "d6fe82a" +} \ No newline at end of file diff --git a/script/DeployVedaAdapter.s.sol b/script/DeployVedaAdapter.s.sol new file mode 100644 index 00000000..52eece91 --- /dev/null +++ b/script/DeployVedaAdapter.s.sol @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: MIT AND Apache-2.0 +pragma solidity 0.8.23; + +import "forge-std/Script.sol"; +import { console2 } from "forge-std/console2.sol"; + +import { VedaAdapter } from "../src/helpers/VedaAdapter.sol"; + +/** + * @title DeployVedaAdapter + * @notice Deploys the VedaAdapter contract. + * @dev Fill the required variables in the .env file + * @dev run the script with: + * forge script script/DeployVedaAdapter.s.sol --rpc-url --private-key $PRIVATE_KEY --broadcast + * For deploying on monad add --skip-simulation, because of how monad works the simulation fails because the token is not activated. + */ +contract DeployVedaAdapter is Script { + bytes32 salt; + address deployer; + address vedaAdapterOwner; + address delegationManager; + address boringVault; + address vedaTeller; + address depositToken; + + function setUp() public { + salt = bytes32(abi.encodePacked(vm.envString("SALT"))); + vedaAdapterOwner = vm.envAddress("VEDA_ADAPTER_OWNER_ADDRESS"); + delegationManager = vm.envAddress("DELEGATION_MANAGER_ADDRESS"); + boringVault = vm.envAddress("VEDA_BORING_VAULT_ADDRESS"); + vedaTeller = vm.envAddress("VEDA_TELLER_ADDRESS"); + depositToken = vm.envAddress("VEDA_DEPOSIT_TOKEN_ADDRESS"); + deployer = msg.sender; + console2.log("~~~"); + console2.log("Owner: %s", vedaAdapterOwner); + console2.log("DelegationManager: %s", delegationManager); + console2.log("BoringVault: %s", boringVault); + console2.log("VedaTeller: %s", vedaTeller); + console2.log("DepositToken: %s", depositToken); + console2.log("Deployer: %s", deployer); + console2.log("Salt:"); + console2.logBytes32(salt); + } + + function run() public { + console2.log("~~~"); + + // Foundry's fork mode cannot interact with mUSD on Monad (NotActivated in revm). + // Mock the approve call so simulation passes; the real broadcast executes the + // actual constructor on-chain where the token works correctly. + // vm.mockCall(depositToken, abi.encodeWithSelector(bytes4(keccak256("approve(address,uint256)"))), abi.encode(true)); + + vm.startBroadcast(); + + address vedaAdapter = + address(new VedaAdapter{ salt: salt }(vedaAdapterOwner, delegationManager, boringVault, vedaTeller, depositToken)); + console2.log("VedaAdapter: %s", vedaAdapter); + + vm.stopBroadcast(); + + // vm.clearMockedCalls(); + } +} diff --git a/src/helpers/VedaAdapter.sol b/src/helpers/VedaAdapter.sol new file mode 100644 index 00000000..5b5c00f4 --- /dev/null +++ b/src/helpers/VedaAdapter.sol @@ -0,0 +1,399 @@ +// SPDX-License-Identifier: MIT AND Apache-2.0 +pragma solidity 0.8.23; + +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import { Ownable2Step, Ownable } from "@openzeppelin/contracts/access/Ownable2Step.sol"; +import { ModeLib } from "@erc7579/lib/ModeLib.sol"; +import { ExecutionLib } from "@erc7579/lib/ExecutionLib.sol"; +import { Delegation, ModeCode } from "../utils/Types.sol"; +import { IDelegationManager } from "../interfaces/IDelegationManager.sol"; +import { IVedaTeller } from "./interfaces/IVedaTeller.sol"; + +/** + * @title VedaAdapter + * @notice Adapter contract that enables Veda BoringVault deposit and withdrawal operations through MetaMask's + * delegation framework + * @dev This contract acts as an intermediary between users and Veda's BoringVault, enabling delegation-based + * token operations without requiring direct token approvals. + * + * Architecture: + * - BoringVault: The ERC20 vault share token that also custodies assets. On deposit, the vault pulls + * tokens from the caller via `safeTransferFrom`, so this adapter must approve the BoringVault. + * - Teller: The contract that orchestrates deposits/withdrawals. The adapter calls `teller.deposit()` + * for deposits and `teller.withdraw()` for withdrawals (user-facing, no special + * role needed). + * - depositToken: The single ERC20 token used for both deposits and withdrawals. Fixed at construction; + * deposits transfer this token into the vault, and withdrawals redeem vault shares back to this token. + * + * Delegation Flow: + * 1. The user creates an initial delegation to an "operator" address (a DeleGator-upgraded account). + * This delegation includes: + * - A transfer enforcer to control which tokens/shares and amounts can be transferred + * - A redeemer enforcer that restricts redemption to only the VedaAdapter contract + * + * 2. The operator then redelegates to this VedaAdapter contract with additional constraints: + * - Allowed methods enforcer limiting which functions can be called + * - Limited calls enforcer restricting the delegation to a single execution + * + * 3. For deposits: the adapter redeems the delegation chain, transfers tokens from the user to itself, + * approves the BoringVault, and calls `teller.deposit()` to mint shares to the user. + * For withdrawals: the adapter redeems the delegation chain, transfers vault shares from the user + * to itself, and calls `teller.withdraw()` to burn shares and send `depositToken` assets to the user. + * + * Requirements: + * - VedaAdapter must approve the BoringVault to spend deposit tokens. The constructor sets + * this allowance to `type(uint256).max`, and the owner-only `ensureAllowance()` function + * can be used as a fail-safe to restore it to max if it were ever reduced. + * + * Leaf Caveat Format: + * - The first caveat of the leaf delegation (`_delegations[0].caveats[0]`) must follow the + * ERC20TransferAmountEnforcer terms format: abi.encodePacked(address token, uint256 amount) (52 bytes). + * The adapter parses only the amount from these terms; the token address encoded in bytes 0–19 is + * consumed by the enforcer itself and is not read by this adapter. + * + * @notice Security consideration: Anyone can call `depositByDelegation` and `withdrawByDelegation` — there is no + * caller restriction. Security is enforced entirely through the delegation chain. The redelegation from the + * operator to this adapter MUST include an `ERC20TransferAmountEnforcer` caveat capped to exactly the intended + * deposit or withdrawal amount, and it MUST be the first caveat (`caveats[0]`) of that redelegation — the + * adapter reads the amount directly from `_delegations[0].caveats[0].terms`. Once that amount is + * transferred the enforcer's running total is exhausted and any replay attempt will revert, making the + * delegation effectively single-use. A delegation without this enforcer as the first caveat (or with an amount + * larger than intended) could be exploited by any caller to transfer more tokens than authorised. + */ +contract VedaAdapter is Ownable2Step { + using SafeERC20 for IERC20; + using ExecutionLib for bytes; + using ModeLib for ModeCode; + + /** + * @notice Parameters for a single deposit operation in a batch + */ + struct DepositParams { + Delegation[] delegations; + uint256 minimumMint; + } + + /** + * @notice Parameters for a single withdrawal operation in a batch + */ + struct WithdrawParams { + Delegation[] delegations; + uint256 minimumAssets; + } + + ////////////////////////////// Events ////////////////////////////// + + /** + * @notice Emitted when a deposit operation is executed via delegation + * @param delegator Address of the token owner (delegator) + * @param token Address of the deposited token + * @param amount Amount of tokens deposited + * @param shares Amount of vault shares minted to the delegator + */ + event DepositExecuted(address indexed delegator, address indexed token, uint256 amount, uint256 shares); + + /** + * @notice Emitted when a withdrawal operation is executed via delegation + * @param delegator Address of the share owner (delegator) + * @param token Address of the underlying token withdrawn (always `depositToken`) + * @param shareAmount Amount of vault shares burned + * @param assetsOut Amount of underlying tokens sent to the delegator + */ + event WithdrawExecuted(address indexed delegator, address indexed token, uint256 shareAmount, uint256 assetsOut); + + /** + * @notice Emitted when a batch deposit is completed + * @param caller Address of the batch executor + * @param count Number of deposit streams executed + */ + event BatchDepositExecuted(address indexed caller, uint256 count); + + /** + * @notice Emitted when a batch withdrawal is completed + * @param caller Address of the batch executor + * @param count Number of withdrawal streams executed + */ + event BatchWithdrawExecuted(address indexed caller, uint256 count); + + /** + * @notice Emitted when stuck tokens are withdrawn by owner + * @param token Address of the token withdrawn + * @param recipient Address of the recipient + * @param amount Amount of tokens withdrawn + */ + event StuckTokensWithdrawn(IERC20 indexed token, address indexed recipient, uint256 amount); + + ////////////////////////////// Errors ////////////////////////////// + + /// @dev Thrown when a zero address is provided for required parameters + error InvalidZeroAddress(); + + /// @dev Thrown when a zero address is provided for the recipient + error InvalidRecipient(); + + /// @dev Thrown when the delegation chain has fewer than 2 delegations + error InvalidDelegationsLength(); + + /// @dev Thrown when the batch array is empty + error InvalidBatchLength(); + + /// @dev Thrown when the leaf caveat terms are shorter than 52 bytes (ERC20TransferAmountEnforcer format) + error InvalidTermsLength(); + + ////////////////////////////// State ////////////////////////////// + + /** + * @notice The DelegationManager contract used to redeem delegations + */ + IDelegationManager public immutable delegationManager; + + /** + * @notice The BoringVault contract (approval target for token transfers) + */ + address public immutable boringVault; + + /** + * @notice The Teller contract for deposit and withdrawal operations + */ + IVedaTeller public immutable teller; + + /** + * @notice The ERC20 token used for all deposits and withdrawals + * @dev Fixed at construction. Deposits transfer this token into the vault; withdrawals redeem vault + * shares back to this token. + */ + IERC20 public immutable depositToken; + + ////////////////////////////// Constructor ////////////////////////////// + + /** + * @notice Initializes the adapter with delegation manager, BoringVault, Teller, and deposit token addresses + * @param _owner Address of the contract owner + * @param _delegationManager Address of the delegation manager contract + * @param _boringVault Address of the BoringVault (token approval target) + * @param _teller Address of the Teller contract (deposit entry point) + * @param _depositToken Address of the ERC20 token used for all deposits and withdrawals + */ + constructor( + address _owner, + address _delegationManager, + address _boringVault, + address _teller, + address _depositToken + ) + Ownable(_owner) + { + if (_delegationManager == address(0) || _boringVault == address(0) || _teller == address(0) || _depositToken == address(0)) + { + revert InvalidZeroAddress(); + } + + delegationManager = IDelegationManager(_delegationManager); + boringVault = _boringVault; + teller = IVedaTeller(_teller); + depositToken = IERC20(_depositToken); + + // Approve BoringVault to pull tokens + depositToken.forceApprove(boringVault, type(uint256).max); + } + + ////////////////////////////// External Methods ////////////////////////////// + + /** + * @notice Deposits tokens into a Veda BoringVault using delegation-based token transfer + * @dev Redeems the delegation to transfer `depositToken` from the user to this adapter, then calls deposit + * on the Teller which mints vault shares directly to the original token owner. + * Requires at least 2 delegations forming a chain from user to operator to this adapter. + * The deposit amount is parsed from the first caveat of the leaf delegation + * (`_delegations[0].caveats[0].terms`), which must follow the ERC20TransferAmountEnforcer + * format: abi.encodePacked(address token, uint256 amount). + * @param _delegations Array of Delegation objects, sorted leaf to root + * @param _minimumMint Minimum vault shares the caller expects to receive, used as a sanity-check + * bound. The Veda vault conversion is always at fair value; rate drift from yield streaming + * is negligible. A tolerance of 0.1-0.5% is recommended. If this check causes a revert, + * no funds are lost — retry with a fresh quote. + * @notice Security consideration: Callable by anyone. The redelegation passed in MUST include an + * `ERC20TransferAmountEnforcer` as its first caveat (`caveats[0]`), capped to exactly the intended + * deposit amount, to prevent over-spending or replay. + */ + function depositByDelegation(Delegation[] calldata _delegations, uint256 _minimumMint) external { + _executeDepositByDelegation(_delegations, _minimumMint); + } + + /** + * @notice Deposits tokens using multiple delegation streams, executed sequentially + * @dev Each element is executed one after the other. The amount for each stream is parsed + * from the first caveat of each stream's leaf delegation. + * @param _depositStreams Array of deposit parameters + * @notice Security consideration: Callable by anyone. Each redelegation in the batch MUST include an + * `ERC20TransferAmountEnforcer` as its first caveat (`caveats[0]`), capped to exactly the intended + * deposit amount, to prevent over-spending or replay. + */ + function depositByDelegationBatch(DepositParams[] calldata _depositStreams) external { + uint256 streamsLength_ = _depositStreams.length; + if (streamsLength_ == 0) revert InvalidBatchLength(); + + for (uint256 i = 0; i < streamsLength_;) { + DepositParams calldata params_ = _depositStreams[i]; + _executeDepositByDelegation(params_.delegations, params_.minimumMint); + unchecked { + ++i; + } + } + + emit BatchDepositExecuted(msg.sender, streamsLength_); + } + + /** + * @notice Withdraws `depositToken` from a Veda BoringVault using delegation-based share transfer + * @dev Redeems the delegation to transfer vault shares to this adapter, then calls withdraw + * on the Teller which burns shares and sends `depositToken` assets directly to the original share owner. + * Requires at least 2 delegations forming a chain from user to operator to this adapter. + * The share amount is parsed from the first caveat of the leaf delegation + * (`_delegations[0].caveats[0].terms`), which must follow the ERC20TransferAmountEnforcer + * format: abi.encodePacked(address boringVault, uint256 shareAmount). + * @param _delegations Array of Delegation objects, sorted leaf to root + * @param _minimumAssets Minimum underlying assets the caller expects to receive, used as a + * sanity-check bound. The Veda vault conversion is always at fair value; rate drift from + * yield streaming is negligible. A tolerance of 0.1-0.5% is recommended. If this check + * causes a revert, no funds are lost — retry with a fresh quote. + * @notice Security consideration: Callable by anyone. The redelegation passed in MUST include an + * `ERC20TransferAmountEnforcer` as its first caveat (`caveats[0]`), capped to exactly the intended + * share amount, to prevent over-spending or replay. + */ + function withdrawByDelegation(Delegation[] calldata _delegations, uint256 _minimumAssets) external { + _executeWithdrawByDelegation(_delegations, _minimumAssets); + } + + /** + * @notice Withdraws `depositToken` using multiple delegation streams, executed sequentially + * @dev Each element is executed one after the other. The share amount for each stream is parsed + * from the first caveat of each stream's leaf delegation. + * @param _withdrawStreams Array of withdraw parameters + * @notice Security consideration: Callable by anyone. Each redelegation in the batch MUST include an + * `ERC20TransferAmountEnforcer` as its first caveat (`caveats[0]`), capped to exactly the intended + * share amount, to prevent over-spending or replay. + */ + function withdrawByDelegationBatch(WithdrawParams[] calldata _withdrawStreams) external { + uint256 streamsLength_ = _withdrawStreams.length; + if (streamsLength_ == 0) revert InvalidBatchLength(); + + for (uint256 i = 0; i < streamsLength_;) { + WithdrawParams calldata params_ = _withdrawStreams[i]; + _executeWithdrawByDelegation(params_.delegations, params_.minimumAssets); + unchecked { + ++i; + } + } + + emit BatchWithdrawExecuted(msg.sender, streamsLength_); + } + + /** + * @notice Emergency function to recover tokens accidentally sent to this contract + * @dev This contract should never hold ERC20 tokens as all token operations are handled + * through delegation-based transfers that move tokens directly between users and the BoringVault. + * This function is only for recovering tokens sent to this contract by mistake. + * @param _token The token to be recovered + * @param _amount The amount of tokens to recover + * @param _recipient The address to receive the recovered tokens + */ + function withdrawEmergency(IERC20 _token, uint256 _amount, address _recipient) external onlyOwner { + if (_recipient == address(0)) revert InvalidRecipient(); + + _token.safeTransfer(_recipient, _amount); + + emit StuckTokensWithdrawn(_token, _recipient, _amount); + } + + /** + * @notice Resets the deposit token allowance for the BoringVault to `type(uint256).max` + * @dev Fail-safe function. The constructor already sets the allowance to `type(uint256).max` + * the allowance should realistically never be exhausted. This function exists only as a + * defensive measure. The owner can restore it to max. + */ + function ensureAllowance() external onlyOwner { + depositToken.forceApprove(boringVault, type(uint256).max); + } + + ////////////////////////////// Private/Internal Methods ////////////////////////////// + + /** + * @notice Parses the transfer amount from ERC20TransferAmountEnforcer terms + * @dev Terms format: abi.encodePacked(address token, uint256 amount) = 52 bytes. + * The token address (bytes 0–19) is validated by the enforcer itself and is not read here. + * Only the amount (bytes 20–51) is returned. + * @param _terms The raw terms bytes from a caveat + * @return amount_ The uint256 amount encoded in bytes 20-51 + */ + function _parseERC20TransferTerms(bytes calldata _terms) private pure returns (uint256 amount_) { + if (_terms.length < 52) revert InvalidTermsLength(); + amount_ = uint256(bytes32(_terms[20:52])); + } + + /** + * @notice Internal implementation of deposit by delegation + * @dev Parses the deposit amount from the first caveat of the leaf delegation + * via `_parseERC20TransferTerms`. Uses `depositToken` as the transfer token. + * @param _delegations Delegation chain, sorted leaf to root + * @param _minimumMint Minimum vault shares expected (sanity-check bound) + */ + function _executeDepositByDelegation(Delegation[] calldata _delegations, uint256 _minimumMint) internal { + uint256 length_ = _delegations.length; + if (length_ < 2) revert InvalidDelegationsLength(); + + uint256 amount_ = _parseERC20TransferTerms(_delegations[0].caveats[0].terms); + address rootDelegator_ = _delegations[length_ - 1].delegator; + + // Redeem delegation: transfer tokens from user to this adapter + bytes[] memory permissionContexts_ = new bytes[](1); + permissionContexts_[0] = abi.encode(_delegations); + + ModeCode[] memory encodedModes_ = new ModeCode[](1); + encodedModes_[0] = ModeLib.encodeSimpleSingle(); + + bytes[] memory executionCallDatas_ = new bytes[](1); + executionCallDatas_[0] = + ExecutionLib.encodeSingle(address(depositToken), 0, abi.encodeCall(IERC20.transfer, (address(this), amount_))); + + delegationManager.redeemDelegations(permissionContexts_, encodedModes_, executionCallDatas_); + + uint256 shares_ = teller.deposit(address(depositToken), amount_, _minimumMint, rootDelegator_, address(0)); + + emit DepositExecuted(rootDelegator_, address(depositToken), amount_, shares_); + } + + /** + * @notice Internal implementation of withdraw by delegation + * @dev Parses the share amount from the first caveat of the leaf delegation + * via `_parseERC20TransferTerms`. Redeems vault shares and sends `depositToken` to the root delegator. + * @param _delegations Delegation chain, sorted leaf to root + * @param _minimumAssets Minimum underlying assets expected (sanity-check bound) + */ + function _executeWithdrawByDelegation(Delegation[] calldata _delegations, uint256 _minimumAssets) internal { + uint256 length_ = _delegations.length; + if (length_ < 2) revert InvalidDelegationsLength(); + + uint256 shareAmount_ = _parseERC20TransferTerms(_delegations[0].caveats[0].terms); + address rootDelegator_ = _delegations[length_ - 1].delegator; + + // Redeem delegation: transfer vault shares from user to this adapter + bytes[] memory permissionContexts_ = new bytes[](1); + permissionContexts_[0] = abi.encode(_delegations); + + ModeCode[] memory encodedModes_ = new ModeCode[](1); + encodedModes_[0] = ModeLib.encodeSimpleSingle(); + + bytes[] memory executionCallDatas_ = new bytes[](1); + executionCallDatas_[0] = + ExecutionLib.encodeSingle(boringVault, 0, abi.encodeCall(IERC20.transfer, (address(this), shareAmount_))); + + delegationManager.redeemDelegations(permissionContexts_, encodedModes_, executionCallDatas_); + + // Withdraw from Teller: burns shares from this adapter, sends depositToken to root delegator + uint256 assetsOut_ = teller.withdraw(address(depositToken), shareAmount_, _minimumAssets, rootDelegator_); + + emit WithdrawExecuted(rootDelegator_, address(depositToken), shareAmount_, assetsOut_); + } +} diff --git a/src/helpers/interfaces/IVedaTeller.sol b/src/helpers/interfaces/IVedaTeller.sol new file mode 100644 index 00000000..ab9a1d8d --- /dev/null +++ b/src/helpers/interfaces/IVedaTeller.sol @@ -0,0 +1,74 @@ +// Based on: +// https://github.com/Se7en-Seas/boring-vault/blob/main/src/base/Roles/TellerWithMultiAssetSupport.sol +// https://github.com/Veda-Labs/boring-vault/blob/dev/oct-2025/src/base/Roles/TellerWithYieldStreaming.sol + +// SPDX-License-Identifier: MIT AND Apache-2.0 +pragma solidity 0.8.23; + +/** + * @title IVedaTeller + * @notice Interface for the user-facing functions of Veda's TellerWithMultiAssetSupport. + * @dev Uses `address` for asset parameters to avoid importing Solmate's ERC20. + * The Teller is the entry/exit point for the BoringVault. All functions use `requiresAuth`, + * so callers must be authorized on the Teller's Authority. + */ +interface IVedaTeller { + /** + * @notice Allows users to deposit into the BoringVault, if the contract is not paused. + * @dev Shares are minted to `msg.sender`. A share lock period may apply. + * @param depositAsset The ERC20 token to deposit + * @param depositAmount The amount to deposit + * @param minimumMint The minimum shares the user expects to receive + * @param referralAddress Address used for referral tracking + * @return shares The number of vault shares minted + */ + function deposit( + address depositAsset, + uint256 depositAmount, + uint256 minimumMint, + address referralAddress + ) + external + payable + returns (uint256 shares); + + /** + * @notice Allows an authorized caller to deposit into the BoringVault for another address, if this contract is not paused. + * @dev Intended for router-like integrations; this selector should remain role-gated. + * @param depositAsset The ERC20 token to deposit + * @param depositAmount The amount to deposit + * @param minimumMint The minimum shares the user expects to receive + * @param to The address that will receive the minted vault shares + * @param referralAddress Address used for referral tracking + * @return shares The number of vault shares minted + */ + function deposit( + address depositAsset, + uint256 depositAmount, + uint256 minimumMint, + address to, + address referralAddress + ) + external + payable + returns (uint256 shares); + + /** + * @notice Allows users to withdraw from the BoringVault. + * @dev Available on TellerWithYieldStreaming. Burns shares from `msg.sender` and sends + * underlying assets to `to`. Updates vested yield before withdrawal. + * @param withdrawAsset The ERC20 token to receive + * @param shareAmount The amount of vault shares to burn + * @param minimumAssets The minimum underlying assets expected + * @param to The address that will receive the underlying assets + * @return assetsOut The amount of underlying assets sent + */ + function withdraw( + address withdrawAsset, + uint256 shareAmount, + uint256 minimumAssets, + address to + ) + external + returns (uint256 assetsOut); +} diff --git a/test/helpers/VedaLending.t.sol b/test/helpers/VedaLending.t.sol new file mode 100644 index 00000000..f7046584 --- /dev/null +++ b/test/helpers/VedaLending.t.sol @@ -0,0 +1,1054 @@ +// SPDX-License-Identifier: MIT AND Apache-2.0 +pragma solidity 0.8.23; + +import { Test } from "forge-std/Test.sol"; +import { ModeLib } from "@erc7579/lib/ModeLib.sol"; +import { ExecutionLib } from "@erc7579/lib/ExecutionLib.sol"; +import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { EncoderLib } from "../../src/libraries/EncoderLib.sol"; +import { IVedaTeller } from "../../src/helpers/interfaces/IVedaTeller.sol"; +import { BaseTest } from "../utils/BaseTest.t.sol"; +import { Implementation, SignatureType, TestUser } from "../utils/Types.t.sol"; +import { Execution, Delegation, Caveat, ModeCode, CallType, ExecType } from "../../src/utils/Types.sol"; +import { CALLTYPE_BATCH, EXECTYPE_TRY, MODE_DEFAULT } from "../../src/utils/Constants.sol"; +import { ModePayload } from "@erc7579/lib/ModeLib.sol"; +import { AllowedTargetsEnforcer } from "../../src/enforcers/AllowedTargetsEnforcer.sol"; +import { AllowedMethodsEnforcer } from "../../src/enforcers/AllowedMethodsEnforcer.sol"; +import { AllowedCalldataEnforcer } from "../../src/enforcers/AllowedCalldataEnforcer.sol"; +import { RedeemerEnforcer } from "../../src/enforcers/RedeemerEnforcer.sol"; +import { ValueLteEnforcer } from "../../src/enforcers/ValueLteEnforcer.sol"; +import { LimitedCallsEnforcer } from "../../src/enforcers/LimitedCallsEnforcer.sol"; +import { LogicalOrWrapperEnforcer } from "../../src/enforcers/LogicalOrWrapperEnforcer.sol"; +import { ERC20TransferAmountEnforcer } from "../../src/enforcers/ERC20TransferAmountEnforcer.sol"; +import { IDelegationManager } from "../../src/interfaces/IDelegationManager.sol"; +import { VedaAdapter } from "../../src/helpers/VedaAdapter.sol"; +import { BasicERC20 } from "../utils/BasicERC20.t.sol"; + +// @dev Do not remove this comment below +/// forge-config: default.evm_version = "shanghai" + +/** + * @title VedaLending Test + * @notice Tests delegation-based lending on Veda BoringVault. + * @dev Uses a forked Arbitrum mainnet environment to test real contract interactions. + * + * Veda BoringVault implements the ERC-4626 standard for tokenized vaults: + * - Users deposit assets (e.g., USDC) and receive vault shares representing proportional ownership + * - Shares are NOT 1:1 with assets - the conversion rate depends on vault's total assets and total supply + * - The vault contract itself is the ERC-20 share token (no separate token contract) + * - Veda uses multiple contracts to manage the flow of funds: + * - We implement Teller for deposits and withdrawals + * - We implement BoringVault for the approval and custody of assets + * - More docs here: https://docs.veda.tech/architecture-and-flow-of-funds + * + * - Security considerations: + * - We need a redelegation with specific amount to the adapter to prevent over withdrawal or deposit. This would not effect the + * user, but could drain the transaction creator wallet. + */ +contract VedaLendingTest is BaseTest { + using ModeLib for ModeCode; + + // Restricted vault - cannot set on behalfOf + IVedaTeller public constant VEDA_TELLER = IVedaTeller(0x86821F179eaD9F0b3C79b2f8deF0227eEBFDc9f9); + IERC20 public constant BORING_VAULT = IERC20(0xB5F07d769dD60fE54c97dd53101181073DDf21b2); + + IERC20 public constant USDC = IERC20(0xaf88d065e77c8cC2239327C5EDb3A432268e5831); + address public constant USDC_WHALE = 0xC6962004f452bE9203591991D15f6b388e09E8D0; + address public owner; + + // Enforcers for delegation restrictions + AllowedTargetsEnforcer public allowedTargetsEnforcer; + AllowedMethodsEnforcer public allowedMethodsEnforcer; + AllowedCalldataEnforcer public allowedCalldataEnforcer; + ValueLteEnforcer public valueLteEnforcer; + LogicalOrWrapperEnforcer public logicalOrWrapperEnforcer; + ERC20TransferAmountEnforcer public erc20TransferAmountEnforcer; + RedeemerEnforcer public redeemerEnforcer; + LimitedCallsEnforcer public limitedCallsEnforcer; + VedaAdapter public vedaAdapter; + + uint256 public constant INITIAL_USD_BALANCE = 10000000000; // 10k USDC + uint256 public constant DEPOSIT_AMOUNT = 1000000000; // 1k USDC + uint256 public constant SHARE_LOCK_SECONDS = 61; // Warp past the 60s share lock period applied by deposit() + + ////////////////////// Setup ////////////////////// + + function setUp() public override { + // Create fork from mainnet at specific block + vm.createSelectFork(vm.envString("ARBITRUM_RPC_URL")); + + // Set implementation type + IMPLEMENTATION = Implementation.Hybrid; + SIGNATURE_TYPE = SignatureType.RawP256; + + // Call parent setup to initialize delegation framework + super.setUp(); + + owner = makeAddr("VedaAdapter Owner"); + + // Deploy enforcers + allowedTargetsEnforcer = new AllowedTargetsEnforcer(); + allowedMethodsEnforcer = new AllowedMethodsEnforcer(); + allowedCalldataEnforcer = new AllowedCalldataEnforcer(); + valueLteEnforcer = new ValueLteEnforcer(); + erc20TransferAmountEnforcer = new ERC20TransferAmountEnforcer(); + redeemerEnforcer = new RedeemerEnforcer(); + limitedCallsEnforcer = new LimitedCallsEnforcer(); + logicalOrWrapperEnforcer = new LogicalOrWrapperEnforcer(delegationManager); + vedaAdapter = new VedaAdapter(owner, address(delegationManager), address(BORING_VAULT), address(VEDA_TELLER), address(USDC)); + + vm.label(address(allowedTargetsEnforcer), "AllowedTargetsEnforcer"); + vm.label(address(allowedMethodsEnforcer), "AllowedMethodsEnforcer"); + vm.label(address(allowedCalldataEnforcer), "AllowedCalldataEnforcer"); + vm.label(address(valueLteEnforcer), "ValueLteEnforcer"); + vm.label(address(logicalOrWrapperEnforcer), "LogicalOrWrapperEnforcer"); + vm.label(address(erc20TransferAmountEnforcer), "ERC20TransferAmountEnforcer"); + vm.label(address(vedaAdapter), "VedaAdapter"); + vm.label(address(BORING_VAULT), "Veda BoringVault"); + vm.label(address(VEDA_TELLER), "Veda Teller"); + vm.label(address(USDC), "USDC"); + vm.label(USDC_WHALE, "USDC Whale"); + + vm.deal(address(users.alice.deleGator), 1 ether); + vm.deal(address(users.bob.deleGator), 1 ether); + + vm.prank(USDC_WHALE); + USDC.transfer(address(users.alice.deleGator), INITIAL_USD_BALANCE); // 10k USDC + } + + // ================================================================================== + // Section 1: Direct Protocol Tests (Fork Sanity) + // Validates the forked mainnet environment works before testing adapter logic. + // ================================================================================== + + function test_deposit_direct_usdc() public { + uint256 aliceUSDCInitialBalance_ = USDC.balanceOf(address(users.alice.deleGator)); + assertEq(aliceUSDCInitialBalance_, INITIAL_USD_BALANCE); + + uint256 aliceSharesBefore_ = BORING_VAULT.balanceOf(address(users.alice.deleGator)); + + vm.prank(address(users.alice.deleGator)); + USDC.approve(address(BORING_VAULT), DEPOSIT_AMOUNT); + vm.prank(address(users.alice.deleGator)); + uint256 sharesMinted_ = VEDA_TELLER.deposit(address(USDC), DEPOSIT_AMOUNT, 0, address(0)); + + uint256 aliceUSDCBalance_ = USDC.balanceOf(address(users.alice.deleGator)); + assertEq(aliceUSDCBalance_, INITIAL_USD_BALANCE - DEPOSIT_AMOUNT); + + uint256 aliceSharesAfter_ = BORING_VAULT.balanceOf(address(users.alice.deleGator)); + assertEq(aliceSharesAfter_ - aliceSharesBefore_, sharesMinted_); + } + + function test_withdraw_direct_usdc() public { + _setupLendingState(); + vm.warp(block.timestamp + SHARE_LOCK_SECONDS); + + uint256 aliceUSDCAfterDeposit_ = USDC.balanceOf(address(users.alice.deleGator)); + assertEq(aliceUSDCAfterDeposit_, INITIAL_USD_BALANCE - DEPOSIT_AMOUNT); + + uint256 aliceShares_ = BORING_VAULT.balanceOf(address(users.alice.deleGator)); + assertGt(aliceShares_, 0, "Alice should have vault shares after deposit"); + + // Withdraw all shares back to USDC + vm.prank(address(users.alice.deleGator)); + uint256 assetsOut_ = VEDA_TELLER.withdraw(address(USDC), aliceShares_, 0, address(users.alice.deleGator)); + + assertGt(assetsOut_, 0, "Should receive assets back"); + + uint256 aliceSharesAfter_ = BORING_VAULT.balanceOf(address(users.alice.deleGator)); + assertEq(aliceSharesAfter_, 0, "All shares should be burned"); + + uint256 aliceUSDCFinal_ = USDC.balanceOf(address(users.alice.deleGator)); + assertApproxEqAbs(aliceUSDCFinal_, INITIAL_USD_BALANCE, DEPOSIT_AMOUNT / 100, "USDC balance should be close to initial"); + } + + // ================================================================================== + // Section 2: Adapter Happy-Path Tests (Core Functionality) + // Validates the standard deposit/withdraw flow via the adapter using delegations. + // ================================================================================== + + function test_deposit_viaAdapterDelegation_usdc() public { + uint256 aliceUSDCInitialBalance_ = USDC.balanceOf(address(users.alice.deleGator)); + assertEq(aliceUSDCInitialBalance_, INITIAL_USD_BALANCE); + uint256 aliceSharesInitial_ = BORING_VAULT.balanceOf(address(users.alice.deleGator)); + assertEq(aliceSharesInitial_, 0); + + // Alice delegates USDC transfer rights to Bob, redeemable only by the adapter + Delegation memory delegation_ = + _createTransferDelegation(address(users.bob.deleGator), address(vedaAdapter), address(USDC), type(uint256).max); + + // Bob redelegates to the VedaAdapter with a transfer amount cap + Delegation memory redelegation_ = + _createAdapterRedelegation(EncoderLib._getDelegationHash(delegation_), address(USDC), DEPOSIT_AMOUNT); + + Delegation[] memory delegations_ = new Delegation[](2); + delegations_[0] = redelegation_; + delegations_[1] = delegation_; + + vm.prank(address(users.bob.deleGator)); + vedaAdapter.depositByDelegation(delegations_, 0); + + uint256 aliceUSDCFinal_ = USDC.balanceOf(address(users.alice.deleGator)); + assertEq(aliceUSDCFinal_, INITIAL_USD_BALANCE - DEPOSIT_AMOUNT, "USDC balance should decrease"); + + uint256 aliceSharesFinal_ = BORING_VAULT.balanceOf(address(users.alice.deleGator)); + assertGt(aliceSharesFinal_, 0, "Shares should be minted to Alice"); + } + + function test_withdraw_viaAdapterDelegation_usdc() public { + _setupLendingState(); + vm.warp(block.timestamp + SHARE_LOCK_SECONDS); + + uint256 aliceShares_ = BORING_VAULT.balanceOf(address(users.alice.deleGator)); + assertGt(aliceShares_, 0, "Alice should have vault shares"); + uint256 aliceUSDCBefore_ = USDC.balanceOf(address(users.alice.deleGator)); + + // Alice delegates BoringVault share transfer rights to Bob, redeemable only by the adapter + Delegation memory delegation_ = _createTransferDelegation( + address(users.bob.deleGator), address(vedaAdapter), address(BORING_VAULT), type(uint256).max + ); + + // Bob redelegates to the VedaAdapter with a share transfer amount cap + Delegation memory redelegation_ = + _createAdapterRedelegation(EncoderLib._getDelegationHash(delegation_), address(BORING_VAULT), aliceShares_); + + Delegation[] memory delegations_ = new Delegation[](2); + delegations_[0] = redelegation_; + delegations_[1] = delegation_; + + vm.prank(address(users.bob.deleGator)); + vedaAdapter.withdrawByDelegation(delegations_, 0); + + uint256 aliceSharesAfter_ = BORING_VAULT.balanceOf(address(users.alice.deleGator)); + assertEq(aliceSharesAfter_, 0, "All shares should be burned"); + + uint256 aliceUSDCAfter_ = USDC.balanceOf(address(users.alice.deleGator)); + assertGt(aliceUSDCAfter_, aliceUSDCBefore_, "Alice should receive USDC back"); + assertApproxEqAbs(aliceUSDCAfter_, INITIAL_USD_BALANCE, DEPOSIT_AMOUNT / 100, "USDC balance should be close to initial"); + } + + // ================================================================================== + // Section 3: Constructor Validation Tests + // Ensures the adapter rejects invalid constructor parameters. + // ================================================================================== + + /// @notice Constructor must revert when delegationManager is zero address + function test_constructor_revertsOnZeroDelegationManager() public { + vm.expectRevert(VedaAdapter.InvalidZeroAddress.selector); + new VedaAdapter(owner, address(0), address(BORING_VAULT), address(VEDA_TELLER), address(USDC)); + } + + /// @notice Constructor must revert when boringVault is zero address + function test_constructor_revertsOnZeroBoringVault() public { + vm.expectRevert(VedaAdapter.InvalidZeroAddress.selector); + new VedaAdapter(owner, address(delegationManager), address(0), address(VEDA_TELLER), address(USDC)); + } + + /// @notice Constructor must revert when teller is zero address + function test_constructor_revertsOnZeroTeller() public { + vm.expectRevert(VedaAdapter.InvalidZeroAddress.selector); + new VedaAdapter(owner, address(delegationManager), address(BORING_VAULT), address(0), address(USDC)); + } + + /// @notice Constructor must revert when depositToken is zero address + function test_constructor_revertsOnZeroDepositToken() public { + vm.expectRevert(VedaAdapter.InvalidZeroAddress.selector); + new VedaAdapter(owner, address(delegationManager), address(BORING_VAULT), address(VEDA_TELLER), address(0)); + } + + /// @notice Constructor must revert when owner is zero address (OZ Ownable) + function test_constructor_revertsOnZeroOwner() public { + vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableInvalidOwner.selector, address(0))); + new VedaAdapter(address(0), address(delegationManager), address(BORING_VAULT), address(VEDA_TELLER), address(USDC)); + } + + /// @notice Constructor must store immutable state correctly with valid inputs + function test_constructor_successWithValidAddresses() public { + VedaAdapter newAdapter_ = + new VedaAdapter(owner, address(delegationManager), address(BORING_VAULT), address(VEDA_TELLER), address(USDC)); + + assertEq(address(newAdapter_.delegationManager()), address(delegationManager)); + assertEq(newAdapter_.boringVault(), address(BORING_VAULT)); + assertEq(address(newAdapter_.teller()), address(VEDA_TELLER)); + assertEq(address(newAdapter_.depositToken()), address(USDC)); + assertEq(newAdapter_.owner(), owner); + } + + // ================================================================================== + // Section 4: Deposit Input Validation / Revert Tests + // Ensures depositByDelegation rejects invalid inputs before any state changes. + // ================================================================================== + + /// @notice depositByDelegation must revert with 0 delegations + function test_depositByDelegation_revertsOnEmptyDelegations() public { + Delegation[] memory delegations_ = new Delegation[](0); + + vm.expectRevert(VedaAdapter.InvalidDelegationsLength.selector); + vm.prank(address(users.bob.deleGator)); + vedaAdapter.depositByDelegation(delegations_, 0); + } + + /// @notice depositByDelegation must revert with only 1 delegation (requires >= 2 for redelegation pattern) + function test_depositByDelegation_revertsOnSingleDelegation() public { + Delegation memory delegation_ = + _createTransferDelegation(address(users.bob.deleGator), address(vedaAdapter), address(USDC), DEPOSIT_AMOUNT); + Delegation[] memory delegations_ = new Delegation[](1); + delegations_[0] = delegation_; + + vm.expectRevert(VedaAdapter.InvalidDelegationsLength.selector); + vm.prank(address(users.bob.deleGator)); + vedaAdapter.depositByDelegation(delegations_, 0); + } + + // ================================================================================== + // Section 5: Withdraw Input Validation / Revert Tests + // Ensures withdrawByDelegation rejects invalid inputs before any state changes. + // ================================================================================== + + /// @notice withdrawByDelegation must revert with 0 delegations + function test_withdrawByDelegation_revertsOnEmptyDelegations() public { + _setupLendingState(); + + Delegation[] memory delegations_ = new Delegation[](0); + + vm.expectRevert(VedaAdapter.InvalidDelegationsLength.selector); + vm.prank(address(users.bob.deleGator)); + vedaAdapter.withdrawByDelegation(delegations_, 0); + } + + /// @notice withdrawByDelegation must revert with only 1 delegation (requires >= 2 for redelegation pattern) + function test_withdrawByDelegation_revertsOnSingleDelegation() public { + _setupLendingState(); + + Delegation memory delegation_ = + _createTransferDelegation(address(users.bob.deleGator), address(vedaAdapter), address(BORING_VAULT), DEPOSIT_AMOUNT); + Delegation[] memory delegations_ = new Delegation[](1); + delegations_[0] = delegation_; + + vm.expectRevert(VedaAdapter.InvalidDelegationsLength.selector); + vm.prank(address(users.bob.deleGator)); + vedaAdapter.withdrawByDelegation(delegations_, 0); + } + + // ================================================================================== + // Section 6: Event Emission Tests + // Validates that adapter emits correct events with expected indexed parameters. + // ================================================================================== + + /// @notice depositByDelegation must emit DepositExecuted with correct parameters + function test_depositByDelegation_emitsDepositExecutedEvent() public { + Delegation memory delegation_ = + _createTransferDelegation(address(users.bob.deleGator), address(vedaAdapter), address(USDC), type(uint256).max); + Delegation memory redelegation_ = + _createAdapterRedelegation(EncoderLib._getDelegationHash(delegation_), address(USDC), DEPOSIT_AMOUNT); + + Delegation[] memory delegations_ = new Delegation[](2); + delegations_[0] = redelegation_; + delegations_[1] = delegation_; + + // Expect event: check indexed delegator and token. Amount and shares are checked via topic3. + vm.expectEmit(true, true, false, false, address(vedaAdapter)); + emit VedaAdapter.DepositExecuted(address(users.alice.deleGator), address(USDC), DEPOSIT_AMOUNT, 0); + + vm.prank(address(users.bob.deleGator)); + vedaAdapter.depositByDelegation(delegations_, 0); + } + + /// @notice withdrawByDelegation must emit WithdrawExecuted with correct parameters + function test_withdrawByDelegation_emitsWithdrawExecutedEvent() public { + _setupLendingState(); + vm.warp(block.timestamp + SHARE_LOCK_SECONDS); + + uint256 aliceShares_ = BORING_VAULT.balanceOf(address(users.alice.deleGator)); + + Delegation memory delegation_ = _createTransferDelegation( + address(users.bob.deleGator), address(vedaAdapter), address(BORING_VAULT), type(uint256).max + ); + Delegation memory redelegation_ = + _createAdapterRedelegation(EncoderLib._getDelegationHash(delegation_), address(BORING_VAULT), aliceShares_); + + Delegation[] memory delegations_ = new Delegation[](2); + delegations_[0] = redelegation_; + delegations_[1] = delegation_; + + // Expect event: check indexed delegator and token. shareAmount and assetsOut are checked via topic3. + vm.expectEmit(true, true, false, false, address(vedaAdapter)); + emit VedaAdapter.WithdrawExecuted(address(users.alice.deleGator), address(USDC), aliceShares_, 0); + + vm.prank(address(users.bob.deleGator)); + vedaAdapter.withdrawByDelegation(delegations_, 0); + } + + // ================================================================================== + // Section 7: Batch Operation Tests + // Validates depositByDelegationBatch and withdrawByDelegationBatch. + // ================================================================================== + + /// @notice depositByDelegationBatch must revert on empty array + function test_depositByDelegationBatch_revertsOnEmptyArray() public { + VedaAdapter.DepositParams[] memory streams_ = new VedaAdapter.DepositParams[](0); + + vm.expectRevert(VedaAdapter.InvalidBatchLength.selector); + vm.prank(address(users.bob.deleGator)); + vedaAdapter.depositByDelegationBatch(streams_); + } + + /// @notice withdrawByDelegationBatch must revert on empty array + function test_withdrawByDelegationBatch_revertsOnEmptyArray() public { + VedaAdapter.WithdrawParams[] memory streams_ = new VedaAdapter.WithdrawParams[](0); + + vm.expectRevert(VedaAdapter.InvalidBatchLength.selector); + vm.prank(address(users.bob.deleGator)); + vedaAdapter.withdrawByDelegationBatch(streams_); + } + + /// @notice Batch deposit with 2 independent delegation chains in a single transaction + function test_depositByDelegationBatch_twoDelegationChains() public { + uint256 amount1_ = 300 * 1e6; // 300 USDC + uint256 amount2_ = 400 * 1e6; // 400 USDC + + // Chain 1: Alice -> Bob -> VedaAdapter (salt 0) + Delegation memory delegation1_ = _createTransferDelegationWithSalt( + address(users.bob.deleGator), address(vedaAdapter), address(USDC), type(uint256).max, 0 + ); + Delegation memory redelegation1_ = + _createAdapterRedelegationWithSalt(EncoderLib._getDelegationHash(delegation1_), address(USDC), amount1_, 0); + Delegation[] memory delegations1_ = new Delegation[](2); + delegations1_[0] = redelegation1_; + delegations1_[1] = delegation1_; + + // Chain 2: Alice -> Bob -> VedaAdapter (salt 1) + Delegation memory delegation2_ = _createTransferDelegationWithSalt( + address(users.bob.deleGator), address(vedaAdapter), address(USDC), type(uint256).max, 1 + ); + Delegation memory redelegation2_ = + _createAdapterRedelegationWithSalt(EncoderLib._getDelegationHash(delegation2_), address(USDC), amount2_, 1); + Delegation[] memory delegations2_ = new Delegation[](2); + delegations2_[0] = redelegation2_; + delegations2_[1] = delegation2_; + + VedaAdapter.DepositParams[] memory streams_ = new VedaAdapter.DepositParams[](2); + streams_[0] = VedaAdapter.DepositParams({ delegations: delegations1_, minimumMint: 0 }); + streams_[1] = VedaAdapter.DepositParams({ delegations: delegations2_, minimumMint: 0 }); + + vm.expectEmit(true, true, true, true, address(vedaAdapter)); + emit VedaAdapter.BatchDepositExecuted(address(users.bob.deleGator), 2); + + vm.prank(address(users.bob.deleGator)); + vedaAdapter.depositByDelegationBatch(streams_); + + uint256 aliceUSDCFinal_ = USDC.balanceOf(address(users.alice.deleGator)); + assertEq(aliceUSDCFinal_, INITIAL_USD_BALANCE - amount1_ - amount2_, "USDC should decrease by total batch amount"); + + uint256 aliceShares_ = BORING_VAULT.balanceOf(address(users.alice.deleGator)); + assertGt(aliceShares_, 0, "Alice should receive vault shares from batch deposit"); + } + + /// @notice Batch withdraw with 2 independent delegation chains in a single transaction + function test_withdrawByDelegationBatch_twoDelegationChains() public { + // Setup: Deposit via adapter to create shares, then warp past the 60s share lock period + _depositViaAdapter(DEPOSIT_AMOUNT, 10); + vm.warp(block.timestamp + SHARE_LOCK_SECONDS); + + uint256 totalShares_ = BORING_VAULT.balanceOf(address(users.alice.deleGator)); + assertGt(totalShares_, 0, "Alice should have shares after deposit"); + + uint256 sharesPart1_ = totalShares_ / 2; + uint256 sharesPart2_ = totalShares_ - sharesPart1_; + + VedaAdapter.WithdrawParams[] memory wdStreams_ = new VedaAdapter.WithdrawParams[](2); + wdStreams_[0] = _buildWithdrawParams(sharesPart1_, 20); + wdStreams_[1] = _buildWithdrawParams(sharesPart2_, 21); + + vm.expectEmit(true, true, true, true, address(vedaAdapter)); + emit VedaAdapter.BatchWithdrawExecuted(address(users.bob.deleGator), 2); + + vm.prank(address(users.bob.deleGator)); + vedaAdapter.withdrawByDelegationBatch(wdStreams_); + + assertEq(BORING_VAULT.balanceOf(address(users.alice.deleGator)), 0, "All shares should be redeemed after batch withdraw"); + assertApproxEqAbs( + USDC.balanceOf(address(users.alice.deleGator)), + INITIAL_USD_BALANCE, + DEPOSIT_AMOUNT / 100, + "USDC should be approximately restored" + ); + } + + // ================================================================================== + // Section 8: Emergency Withdraw Tests + // Validates the owner-only withdrawEmergency function for recovering stuck tokens. + // ================================================================================== + + /// @notice Only the contract owner can call withdrawEmergency + function test_withdrawEmergency_revertsOnNonOwner() public { + BasicERC20 testToken_ = new BasicERC20(owner, "TestToken", "TST", 0); + vm.prank(owner); + testToken_.mint(address(vedaAdapter), 100 ether); + + vm.prank(address(users.alice.deleGator)); + vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, address(users.alice.deleGator))); + vedaAdapter.withdrawEmergency(testToken_, 50 ether, address(users.alice.deleGator)); + + assertEq(testToken_.balanceOf(address(vedaAdapter)), 100 ether, "Balance should be unchanged"); + } + + /// @notice Owner can recover stuck tokens; emits StuckTokensWithdrawn event + function test_withdrawEmergency_recoverTokens() public { + BasicERC20 testToken_ = new BasicERC20(owner, "TestToken", "TST", 0); + vm.prank(owner); + testToken_.mint(address(vedaAdapter), 100 ether); + + vm.expectEmit(true, true, true, true, address(vedaAdapter)); + emit VedaAdapter.StuckTokensWithdrawn(testToken_, address(users.alice.deleGator), 50 ether); + + vm.prank(owner); + vedaAdapter.withdrawEmergency(testToken_, 50 ether, address(users.alice.deleGator)); + + assertEq(testToken_.balanceOf(address(vedaAdapter)), 50 ether, "Adapter should retain remaining tokens"); + assertEq(testToken_.balanceOf(address(users.alice.deleGator)), 50 ether, "Recipient should receive tokens"); + } + + /// @notice withdrawEmergency must revert when recipient is zero address + function test_withdrawEmergency_revertsOnZeroRecipient() public { + BasicERC20 testToken_ = new BasicERC20(owner, "TestToken", "TST", 0); + vm.prank(owner); + testToken_.mint(address(vedaAdapter), 100 ether); + + vm.expectRevert(VedaAdapter.InvalidRecipient.selector); + vm.prank(owner); + vedaAdapter.withdrawEmergency(testToken_, 50 ether, address(0)); + } + + // ================================================================================== + // Section 9: Edge Cases and Security Validation + // Tests for subtle behaviors, allowance management, chain integrity, and token mismatch. + // ================================================================================== + + /// @notice After a deposit, the adapter must not retain any deposited tokens + function test_adapterDoesNotRetainTokensAfterDeposit() public { + Delegation memory delegation_ = + _createTransferDelegation(address(users.bob.deleGator), address(vedaAdapter), address(USDC), type(uint256).max); + Delegation memory redelegation_ = + _createAdapterRedelegation(EncoderLib._getDelegationHash(delegation_), address(USDC), DEPOSIT_AMOUNT); + + Delegation[] memory delegations_ = new Delegation[](2); + delegations_[0] = redelegation_; + delegations_[1] = delegation_; + + vm.prank(address(users.bob.deleGator)); + vedaAdapter.depositByDelegation(delegations_, 0); + + assertEq(USDC.balanceOf(address(vedaAdapter)), 0, "Adapter must not retain any USDC after deposit"); + } + + /// @notice After a withdraw, the adapter must not retain any vault shares + function test_adapterDoesNotRetainSharesAfterWithdraw() public { + _setupLendingState(); + vm.warp(block.timestamp + SHARE_LOCK_SECONDS); + + uint256 aliceShares_ = BORING_VAULT.balanceOf(address(users.alice.deleGator)); + + Delegation memory delegation_ = _createTransferDelegation( + address(users.bob.deleGator), address(vedaAdapter), address(BORING_VAULT), type(uint256).max + ); + Delegation memory redelegation_ = + _createAdapterRedelegation(EncoderLib._getDelegationHash(delegation_), address(BORING_VAULT), aliceShares_); + + Delegation[] memory delegations_ = new Delegation[](2); + delegations_[0] = redelegation_; + delegations_[1] = delegation_; + + vm.prank(address(users.bob.deleGator)); + vedaAdapter.withdrawByDelegation(delegations_, 0); + + assertEq(BORING_VAULT.balanceOf(address(vedaAdapter)), 0, "Adapter must not retain any vault shares after withdraw"); + } + + /// @notice The adapter approves the BoringVault for `type(uint256).max` in the constructor, + /// so deposits draw from a pre-existing unlimited allowance that never needs topping up + /// under normal operation. + function test_allowanceSetToMaxInConstructor() public { + assertEq( + USDC.allowance(address(vedaAdapter), address(BORING_VAULT)), + type(uint256).max, + "Constructor should set allowance to max" + ); + } + + /// @notice After a deposit, the allowance is simply `max - depositAmount` because the BoringVault + /// pulled tokens via `safeTransferFrom`. The allowance is effectively still unbounded and + /// does not require re-approval. + function test_allowanceRemainsUnlimitedAfterDeposit() public { + Delegation memory delegation_ = + _createTransferDelegation(address(users.bob.deleGator), address(vedaAdapter), address(USDC), type(uint256).max); + Delegation memory redelegation_ = + _createAdapterRedelegation(EncoderLib._getDelegationHash(delegation_), address(USDC), DEPOSIT_AMOUNT); + + Delegation[] memory delegations_ = new Delegation[](2); + delegations_[0] = redelegation_; + delegations_[1] = delegation_; + + vm.prank(address(users.bob.deleGator)); + vedaAdapter.depositByDelegation(delegations_, 0); + + assertEq( + USDC.allowance(address(vedaAdapter), address(BORING_VAULT)), + type(uint256).max - DEPOSIT_AMOUNT, + "Allowance should be unlimited minus the deposited amount" + ); + } + + /// @notice `ensureAllowance` is a fail-safe that lets the owner restore the BoringVault + /// allowance to `type(uint256).max` if it were ever reduced. + function test_ensureAllowanceRestoresMaxAllowance() public { + // Simulate the allowance being reduced by forcing an approval from the adapter via the owner. + // We can't directly call forceApprove on the adapter, so we verify the fail-safe restores + // allowance after a deposit consumes part of it. + Delegation memory delegation_ = + _createTransferDelegation(address(users.bob.deleGator), address(vedaAdapter), address(USDC), type(uint256).max); + Delegation memory redelegation_ = + _createAdapterRedelegation(EncoderLib._getDelegationHash(delegation_), address(USDC), DEPOSIT_AMOUNT); + + Delegation[] memory delegations_ = new Delegation[](2); + delegations_[0] = redelegation_; + delegations_[1] = delegation_; + + vm.prank(address(users.bob.deleGator)); + vedaAdapter.depositByDelegation(delegations_, 0); + + assertLt( + USDC.allowance(address(vedaAdapter), address(BORING_VAULT)), + type(uint256).max, + "Allowance should be below max after a deposit" + ); + + vm.prank(owner); + vedaAdapter.ensureAllowance(); + + assertEq( + USDC.allowance(address(vedaAdapter), address(BORING_VAULT)), + type(uint256).max, + "ensureAllowance should restore allowance to max" + ); + } + + /// @notice `ensureAllowance` is owner-gated and reverts when called by a non-owner. + function test_ensureAllowanceRevertsForNonOwner() public { + vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, address(users.bob.addr))); + vm.prank(address(users.bob.addr)); + vedaAdapter.ensureAllowance(); + } + + /// @notice A 3-level delegation chain (Alice -> Carol -> Bob -> Adapter) must correctly resolve + /// rootDelegator as Alice, ensuring shares are minted to the actual token owner. + function test_depositByDelegation_withThreeLevelDelegationChain() public { + vm.deal(address(users.carol.deleGator), 1 ether); + + // Root delegation: Alice -> Carol (with transfer enforcer + redeemer enforcer) + Delegation memory rootDelegation_ = + _createTransferDelegation(address(users.carol.deleGator), address(vedaAdapter), address(USDC), type(uint256).max); + + // Middle delegation: Carol -> Bob (no additional caveats, just extends the chain) + Delegation memory middleDelegation_ = Delegation({ + delegate: address(users.bob.deleGator), + delegator: address(users.carol.deleGator), + authority: EncoderLib._getDelegationHash(rootDelegation_), + caveats: new Caveat[](0), + salt: 0, + signature: hex"" + }); + middleDelegation_ = signDelegation(users.carol, middleDelegation_); + + // Leaf delegation: Bob -> VedaAdapter (with transfer amount cap) + Delegation memory adapterDelegation_ = + _createAdapterRedelegation(EncoderLib._getDelegationHash(middleDelegation_), address(USDC), DEPOSIT_AMOUNT); + + // Chain order: [leaf, middle, root] + Delegation[] memory delegations_ = new Delegation[](3); + delegations_[0] = adapterDelegation_; + delegations_[1] = middleDelegation_; + delegations_[2] = rootDelegation_; + + vm.prank(address(users.bob.deleGator)); + vedaAdapter.depositByDelegation(delegations_, 0); + + // rootDelegator_ = delegations[2].delegator = Alice + uint256 aliceShares_ = BORING_VAULT.balanceOf(address(users.alice.deleGator)); + assertGt(aliceShares_, 0, "Shares must be minted to Alice (root delegator), not Carol or Bob"); + + assertEq(BORING_VAULT.balanceOf(address(users.carol.deleGator)), 0, "Carol must not receive shares"); + assertEq(BORING_VAULT.balanceOf(address(users.bob.deleGator)), 0, "Bob must not receive shares"); + } + + // ================================================================================== + // Section 10: Terms Validation Tests + // Ensures the adapter rejects malformed caveat terms before executing. + // ================================================================================== + + /// @notice depositByDelegation must revert when leaf caveat terms are shorter than 52 bytes + function test_depositByDelegation_revertsOnShortTerms() public { + Delegation memory delegation_ = + _createTransferDelegation(address(users.bob.deleGator), address(vedaAdapter), address(USDC), type(uint256).max); + + Caveat[] memory shortCaveats_ = new Caveat[](1); + shortCaveats_[0] = Caveat({ + args: hex"", + enforcer: address(erc20TransferAmountEnforcer), + terms: abi.encodePacked(address(USDC)) // 20 bytes, too short + }); + + Delegation memory redelegation_ = Delegation({ + delegate: address(vedaAdapter), + delegator: address(users.bob.deleGator), + authority: EncoderLib._getDelegationHash(delegation_), + caveats: shortCaveats_, + salt: 0, + signature: hex"" + }); + redelegation_ = signDelegation(users.bob, redelegation_); + + Delegation[] memory delegations_ = new Delegation[](2); + delegations_[0] = redelegation_; + delegations_[1] = delegation_; + + vm.expectRevert(VedaAdapter.InvalidTermsLength.selector); + vm.prank(address(users.bob.deleGator)); + vedaAdapter.depositByDelegation(delegations_, 0); + } + + /// @notice withdrawByDelegation must revert when leaf caveat terms are shorter than 52 bytes + function test_withdrawByDelegation_revertsOnShortTerms() public { + _setupLendingState(); + + Delegation memory delegation_ = _createTransferDelegation( + address(users.bob.deleGator), address(vedaAdapter), address(BORING_VAULT), type(uint256).max + ); + + Caveat[] memory shortCaveats_ = new Caveat[](1); + shortCaveats_[0] = Caveat({ + args: hex"", + enforcer: address(erc20TransferAmountEnforcer), + terms: hex"aabbccdd" // 4 bytes, too short + }); + + Delegation memory redelegation_ = Delegation({ + delegate: address(vedaAdapter), + delegator: address(users.bob.deleGator), + authority: EncoderLib._getDelegationHash(delegation_), + caveats: shortCaveats_, + salt: 0, + signature: hex"" + }); + redelegation_ = signDelegation(users.bob, redelegation_); + + Delegation[] memory delegations_ = new Delegation[](2); + delegations_[0] = redelegation_; + delegations_[1] = delegation_; + + vm.expectRevert(VedaAdapter.InvalidTermsLength.selector); + vm.prank(address(users.bob.deleGator)); + vedaAdapter.withdrawByDelegation(delegations_, 0); + } + + // ================================================================================== + // Section 11: Replay / Double-Spend Prevention Tests + // Validates that the ERC20TransferAmountEnforcer prevents reuse of the same delegation. + // ================================================================================== + + /// @notice Calling depositByDelegation twice with the same delegation chain must revert on the second call + function test_depositByDelegation_revertsOnReplay() public { + Delegation memory delegation_ = + _createTransferDelegation(address(users.bob.deleGator), address(vedaAdapter), address(USDC), type(uint256).max); + Delegation memory redelegation_ = + _createAdapterRedelegation(EncoderLib._getDelegationHash(delegation_), address(USDC), DEPOSIT_AMOUNT); + + Delegation[] memory delegations_ = new Delegation[](2); + delegations_[0] = redelegation_; + delegations_[1] = delegation_; + + vm.prank(address(users.bob.deleGator)); + vedaAdapter.depositByDelegation(delegations_, 0); + + vm.prank(address(users.bob.deleGator)); + vm.expectRevert(); + vedaAdapter.depositByDelegation(delegations_, 0); + } + + // ================================================================================== + // Section 12: Slippage Protection Tests + // Validates that minimumMint / minimumAssets bounds cause reverts when not met. + // ================================================================================== + + /// @notice depositByDelegation must revert when minimumMint exceeds the actual shares minted + function test_depositByDelegation_revertsOnSlippage() public { + Delegation memory delegation_ = + _createTransferDelegation(address(users.bob.deleGator), address(vedaAdapter), address(USDC), type(uint256).max); + Delegation memory redelegation_ = + _createAdapterRedelegation(EncoderLib._getDelegationHash(delegation_), address(USDC), DEPOSIT_AMOUNT); + + Delegation[] memory delegations_ = new Delegation[](2); + delegations_[0] = redelegation_; + delegations_[1] = delegation_; + + vm.prank(address(users.bob.deleGator)); + vm.expectRevert(); + vedaAdapter.depositByDelegation(delegations_, type(uint256).max); + } + + /// @notice withdrawByDelegation must revert when minimumAssets exceeds the actual assets received + function test_withdrawByDelegation_revertsOnSlippage() public { + _setupLendingState(); + vm.warp(block.timestamp + SHARE_LOCK_SECONDS); + + uint256 aliceShares_ = BORING_VAULT.balanceOf(address(users.alice.deleGator)); + + Delegation memory delegation_ = _createTransferDelegation( + address(users.bob.deleGator), address(vedaAdapter), address(BORING_VAULT), type(uint256).max + ); + Delegation memory redelegation_ = + _createAdapterRedelegation(EncoderLib._getDelegationHash(delegation_), address(BORING_VAULT), aliceShares_); + + Delegation[] memory delegations_ = new Delegation[](2); + delegations_[0] = redelegation_; + delegations_[1] = delegation_; + + vm.prank(address(users.bob.deleGator)); + vm.expectRevert(); + vedaAdapter.withdrawByDelegation(delegations_, type(uint256).max); + } + + // ================================================================================== + // Section 13: Alternative Delegator Tests + // Validates the adapter works correctly when Carol (not Alice) is the root delegator. + // ================================================================================== + + /// @notice Deposit via adapter where Carol is the root delegator instead of Alice + function test_depositByDelegation_carolAsRootDelegator() public { + vm.deal(address(users.carol.deleGator), 1 ether); + vm.prank(USDC_WHALE); + USDC.transfer(address(users.carol.deleGator), INITIAL_USD_BALANCE); + + uint256 carolUSDCBefore_ = USDC.balanceOf(address(users.carol.deleGator)); + + // Carol delegates USDC transfer rights to Bob, redeemable only by the adapter + Delegation memory delegation_ = _createTransferDelegationFull( + users.carol, address(users.bob.deleGator), address(vedaAdapter), address(USDC), type(uint256).max, 0 + ); + + // Bob redelegates to the VedaAdapter with a transfer amount cap + Delegation memory redelegation_ = + _createAdapterRedelegation(EncoderLib._getDelegationHash(delegation_), address(USDC), DEPOSIT_AMOUNT); + + Delegation[] memory delegations_ = new Delegation[](2); + delegations_[0] = redelegation_; + delegations_[1] = delegation_; + + vm.prank(address(users.bob.deleGator)); + vedaAdapter.depositByDelegation(delegations_, 0); + + uint256 carolUSDCAfter_ = USDC.balanceOf(address(users.carol.deleGator)); + assertEq(carolUSDCAfter_, carolUSDCBefore_ - DEPOSIT_AMOUNT, "Carol's USDC should decrease"); + + uint256 carolShares_ = BORING_VAULT.balanceOf(address(users.carol.deleGator)); + assertGt(carolShares_, 0, "Shares should be minted to Carol (root delegator)"); + + assertEq(BORING_VAULT.balanceOf(address(users.bob.deleGator)), 0, "Bob must not receive shares"); + assertEq(BORING_VAULT.balanceOf(address(users.alice.deleGator)), 0, "Alice must not receive shares"); + } + + /// @notice Withdraw via adapter where Carol is the root delegator instead of Alice + function test_withdrawByDelegation_carolAsRootDelegator() public { + vm.deal(address(users.carol.deleGator), 1 ether); + vm.prank(USDC_WHALE); + USDC.transfer(address(users.carol.deleGator), INITIAL_USD_BALANCE); + + // Carol deposits directly to get shares + vm.prank(address(users.carol.deleGator)); + USDC.approve(address(BORING_VAULT), DEPOSIT_AMOUNT); + vm.prank(address(users.carol.deleGator)); + VEDA_TELLER.deposit(address(USDC), DEPOSIT_AMOUNT, 0, address(0)); + vm.warp(block.timestamp + SHARE_LOCK_SECONDS); + + uint256 carolShares_ = BORING_VAULT.balanceOf(address(users.carol.deleGator)); + assertGt(carolShares_, 0, "Carol should have vault shares"); + uint256 carolUSDCBefore_ = USDC.balanceOf(address(users.carol.deleGator)); + + // Carol delegates share transfer rights to Bob, redeemable only by the adapter + Delegation memory delegation_ = _createTransferDelegationFull( + users.carol, address(users.bob.deleGator), address(vedaAdapter), address(BORING_VAULT), type(uint256).max, 0 + ); + + // Bob redelegates to the VedaAdapter with a share transfer amount cap + Delegation memory redelegation_ = + _createAdapterRedelegation(EncoderLib._getDelegationHash(delegation_), address(BORING_VAULT), carolShares_); + + Delegation[] memory delegations_ = new Delegation[](2); + delegations_[0] = redelegation_; + delegations_[1] = delegation_; + + vm.prank(address(users.bob.deleGator)); + vedaAdapter.withdrawByDelegation(delegations_, 0); + + assertEq(BORING_VAULT.balanceOf(address(users.carol.deleGator)), 0, "All shares should be burned"); + uint256 carolUSDCAfter_ = USDC.balanceOf(address(users.carol.deleGator)); + assertGt(carolUSDCAfter_, carolUSDCBefore_, "Carol should receive USDC back"); + } + + // ================================================================================== + // Helper Functions + // ================================================================================== + + /// @notice Sets up initial lending state (Alice deposits USDC to get vault shares) + function _setupLendingState() internal { + vm.prank(address(users.alice.deleGator)); + USDC.approve(address(BORING_VAULT), DEPOSIT_AMOUNT); + vm.prank(address(users.alice.deleGator)); + VEDA_TELLER.deposit(address(USDC), DEPOSIT_AMOUNT, 0, address(0)); + } + + /// @notice Deposits USDC via adapter delegation (helper to reduce stack depth in batch tests) + function _depositViaAdapter(uint256 _amount, uint256 _salt) internal { + Delegation memory delegation_ = _createTransferDelegationWithSalt( + address(users.bob.deleGator), address(vedaAdapter), address(USDC), type(uint256).max, _salt + ); + Delegation memory redelegation_ = + _createAdapterRedelegationWithSalt(EncoderLib._getDelegationHash(delegation_), address(USDC), _amount, _salt); + Delegation[] memory delegations_ = new Delegation[](2); + delegations_[0] = redelegation_; + delegations_[1] = delegation_; + + vm.prank(address(users.bob.deleGator)); + vedaAdapter.depositByDelegation(delegations_, 0); + } + + /// @notice Builds a WithdrawParams struct for batch withdraw (helper to reduce stack depth) + function _buildWithdrawParams(uint256 _shareAmount, uint256 _salt) internal view returns (VedaAdapter.WithdrawParams memory) { + Delegation memory wd_ = _createTransferDelegationWithSalt( + address(users.bob.deleGator), address(vedaAdapter), address(BORING_VAULT), type(uint256).max, _salt + ); + Delegation memory rewd_ = + _createAdapterRedelegationWithSalt(EncoderLib._getDelegationHash(wd_), address(BORING_VAULT), _shareAmount, _salt); + Delegation[] memory wdDelegations_ = new Delegation[](2); + wdDelegations_[0] = rewd_; + wdDelegations_[1] = wd_; + + return VedaAdapter.WithdrawParams({ delegations: wdDelegations_, minimumAssets: 0 }); + } + + /// @notice Creates a transfer delegation with ERC20TransferAmountEnforcer and RedeemerEnforcer + function _createTransferDelegation( + address _delegate, + address _redeemer, + address _token, + uint256 _amount + ) + internal + view + returns (Delegation memory) + { + return _createTransferDelegationFull(users.alice, _delegate, _redeemer, _token, _amount, 0); + } + + /// @notice Creates a transfer delegation with a custom salt for unique delegation hashes in batch operations + function _createTransferDelegationWithSalt( + address _delegate, + address _redeemer, + address _token, + uint256 _amount, + uint256 _salt + ) + internal + view + returns (Delegation memory) + { + return _createTransferDelegationFull(users.alice, _delegate, _redeemer, _token, _amount, _salt); + } + + /// @notice Creates a transfer delegation signed by an arbitrary delegator + function _createTransferDelegationFull( + TestUser memory _delegator, + address _delegate, + address _redeemer, + address _token, + uint256 _amount, + uint256 _salt + ) + internal + view + returns (Delegation memory) + { + Caveat[] memory caveats_ = new Caveat[](2); + caveats_[0] = + Caveat({ args: hex"", enforcer: address(erc20TransferAmountEnforcer), terms: abi.encodePacked(_token, _amount) }); + + caveats_[1] = Caveat({ args: hex"", enforcer: address(redeemerEnforcer), terms: abi.encodePacked(_redeemer) }); + + Delegation memory delegation_ = Delegation({ + delegate: _delegate, + delegator: address(_delegator.deleGator), + authority: ROOT_AUTHORITY, + caveats: caveats_, + salt: _salt, + signature: hex"" + }); + + return signDelegation(_delegator, delegation_); + } + + /// @notice Creates an adapter redelegation with ERC20TransferAmountEnforcer + function _createAdapterRedelegation( + bytes32 _authority, + address _token, + uint256 _amount + ) + internal + view + returns (Delegation memory) + { + return _createAdapterRedelegationFull(users.bob, _authority, _token, _amount, 0); + } + + /// @notice Creates an adapter redelegation with a custom salt for unique delegation hashes in batch operations + function _createAdapterRedelegationWithSalt( + bytes32 _authority, + address _token, + uint256 _amount, + uint256 _salt + ) + internal + view + returns (Delegation memory) + { + return _createAdapterRedelegationFull(users.bob, _authority, _token, _amount, _salt); + } + + /// @notice Creates an adapter redelegation signed by an arbitrary operator + function _createAdapterRedelegationFull( + TestUser memory _operator, + bytes32 _authority, + address _token, + uint256 _amount, + uint256 _salt + ) + internal + view + returns (Delegation memory) + { + Caveat[] memory caveats_ = new Caveat[](1); + caveats_[0] = + Caveat({ args: hex"", enforcer: address(erc20TransferAmountEnforcer), terms: abi.encodePacked(_token, _amount) }); + + Delegation memory delegation_ = Delegation({ + delegate: address(vedaAdapter), + delegator: address(_operator.deleGator), + authority: _authority, + caveats: caveats_, + salt: _salt, + signature: hex"" + }); + + return signDelegation(_operator, delegation_); + } +}