diff --git a/.env.example b/.env.example index 4083dfd8..d4dd0284 100644 --- a/.env.example +++ b/.env.example @@ -43,7 +43,7 @@ ARBITRUM_NOVA_RPC_URL=https://nova.arbitrum.io/rpc SEI_RPC_URL=https://evm-rpc.sei-apis.com INK_RPC_URL=https://rpc-gel.inkonchain.com SONIC_RPC_URL=https://rpc.soniclabs.com -MONAD_RPC_URL=https://rpc.monad.xyz +MONAD_RPC_URL=https://monad-mainnet.infura.io/v3/${RPC_API_KEY} MEGA_ETH_RPC_URL= RONIN_RPC_URL=https://api.roninchain.com/rpc TEMPO_RPC_URL=https://rpc.tempo.xyz diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 09fc09bc..9aeadf14 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -52,4 +52,5 @@ jobs: env: LINEA_RPC_URL: ${{ secrets.LINEA_RPC_URL }} ARBITRUM_RPC_URL: ${{ secrets.ARBITRUM_RPC_URL }} + MONAD_RPC_URL: ${{ secrets.MONAD_RPC_URL }} RPC_API_KEY: ${{ secrets.RPC_API_KEY }} diff --git a/audits/cyfrin/cyfrin-10-26.pdf b/audits/cyfrin/cyfrin-10-26.pdf new file mode 100644 index 00000000..f6354bfc Binary files /dev/null and b/audits/cyfrin/cyfrin-10-26.pdf differ diff --git a/broadcast/DeployComplianceVedaAdapter.s.sol/143/run-1791287105763.json b/broadcast/DeployComplianceVedaAdapter.s.sol/143/run-1791287105763.json new file mode 100644 index 00000000..b5d6f077 --- /dev/null +++ b/broadcast/DeployComplianceVedaAdapter.s.sol/143/run-1791287105763.json @@ -0,0 +1,85 @@ +{ + "transactions": [ + { + "hash": "0x4168f1715c823d520b58be489c88af2f95e2d3e6ed5202b351297e3eed214586", + "transactionType": "CREATE2", + "contractName": "ComplianceVedaAdapter", + "contractAddress": "0x0a9bb266e06e1f003a61ab02cada1ee147951eb1", + "function": null, + "arguments": [ + "0x74dbD45B505fB3b9F94C31E5f15c41b8E9883B04", + "0xdb9B1e94B5b69Df7e401DDbedE43491141047dB3", + "0x8b5bA7A24c30106F712217285Fc9702d6A0e0D1C", + "0xa23847d0B4842caA2bAdAD8F082dD55C68007612", + "0xacA92E438df0B2401fF60dA7E4337B687a2435DA" + ], + "transaction": { + "from": "0xb0403b32f54d0bd752113f4009e8b534c6669f44", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "value": "0x0", + "input": "0x4741544f520000000000000000000000000000000000000000000000000000006101006040523480156200001257600080fd5b5060405162001fec38038062001fec8339810160408190526200003591620004d4565b846001600160a01b0381166200006657604051631e4fbdf760e01b8152600060048201526024015b60405180910390fd5b620000718162000112565b506001600160a01b03841615806200009057506001600160a01b038316155b80620000a357506001600160a01b038216155b80620000b657506001600160a01b038116155b15620000d55760405163f6b2911f60e01b815260040160405180910390fd5b6001600160a01b0380851660805283811660a081905283821660c05290821660e0819052620001079160001962000130565b505050505062000599565b600180546001600160a01b03191690556200012d81620001fc565b50565b604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180516001600160e01b0390811663095ea7b360e01b179091526200018a90859083906200024c16565b620001f657604080516001600160a01b038516602482015260006044808301919091528251808303909101815260649091019091526020810180516001600160e01b0390811663095ea7b360e01b17909152620001ea918691620002fd16565b620001f68482620002fd565b50505050565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6000806000846001600160a01b0316846040516200026b919062000544565b6000604051808303816000865af19150503d8060008114620002aa576040519150601f19603f3d011682016040523d82523d6000602084013e620002af565b606091505b5091509150818015620002dd575080511580620002dd575080806020019051810190620002dd919062000575565b8015620002f457506000856001600160a01b03163b115b95945050505050565b6000620003146001600160a01b038416836200036c565b905080516000141580156200033c5750808060200190518101906200033a919062000575565b155b156200036757604051635274afe760e01b81526001600160a01b03841660048201526024016200005d565b505050565b60606200037c8383600062000383565b9392505050565b606081471015620003aa5760405163cd78605960e01b81523060048201526024016200005d565b600080856001600160a01b03168486604051620003c8919062000544565b60006040518083038185875af1925050503d806000811462000407576040519150601f19603f3d011682016040523d82523d6000602084013e6200040c565b606091505b5090925090506200041f86838362000429565b9695505050505050565b60608262000442576200043c826200048d565b6200037c565b81511580156200045a57506001600160a01b0384163b155b156200048557604051639996b31560e01b81526001600160a01b03851660048201526024016200005d565b50806200037c565b8051156200049e5780518082602001fd5b604051630a12f52160e11b815260040160405180910390fd5b80516001600160a01b0381168114620004cf57600080fd5b919050565b600080600080600060a08688031215620004ed57600080fd5b620004f886620004b7565b94506200050860208701620004b7565b93506200051860408701620004b7565b92506200052860608701620004b7565b91506200053860808701620004b7565b90509295509295909350565b6000825160005b818110156200056757602081860181015185830152016200054b565b506000920191825250919050565b6000602082840312156200058857600080fd5b815180151581146200037c57600080fd5b60805160a05160c05160e0516119c562000627600039600081816101b901528181610285015281816108870152818161093b01528181610c6d01528181610d760152610e1701526000818161011c015281816108c90152610d3c01526000818161023e015281816102a7015261077c015260008181610204015281816108070152610ccd01526119c56000f3fe608060405234801561001057600080fd5b50600436106100f55760003560e01c80638da5cb5b11610097578063e30c397811610066578063e30c3978146101ee578063ea4d3c9b146101ff578063f2fde38b14610226578063f3b977841461023957600080fd5b80638da5cb5b14610190578063af74d121146101a1578063c89039c5146101b4578063d83d314a146101db57600080fd5b8063715018a6116100d3578063715018a61461015a578063758d94581461016257806379ba5097146101755780638bd291191461017d57600080fd5b80632a490331146100fa5780633db1b8e51461010f57806357edab4e14610117575b600080fd5b61010d610108366004611237565b610260565b005b61010d610270565b61013e7f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b03909116815260200160405180910390f35b61010d6102d0565b61010d610170366004611298565b6102e2565b61010d610377565b61010d61018b3660046112da565b6103c0565b6000546001600160a01b031661013e565b61010d6101af366004611352565b6103d2565b61013e7f000000000000000000000000000000000000000000000000000000000000000081565b61010d6101e9366004611352565b610490565b6001546001600160a01b031661013e565b61013e7f000000000000000000000000000000000000000000000000000000000000000081565b61010d610234366004611394565b610538565b61013e7f000000000000000000000000000000000000000000000000000000000000000081565b61026b8383836105a9565b505050565b6102786109bb565b6102ce6001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000167f00000000000000000000000000000000000000000000000000000000000000006000196109e8565b565b6102d86109bb565b6102ce6000610aa6565b6102ea6109bb565b6001600160a01b03811661031157604051634e46966960e11b815260040160405180910390fd5b6103256001600160a01b0384168284610abf565b806001600160a01b0316836001600160a01b03167f0e191ee1e354192688af5ec7da0cc3d7f54ecb056e6418d42e73ddc4c37d36378460405161036a91815260200190565b60405180910390a3505050565b60015433906001600160a01b031681146103b45760405163118cdaa760e01b81526001600160a01b03821660048201526024015b60405180910390fd5b6103bd81610aa6565b50565b6103cc84848484610af0565b50505050565b8060008190036103f55760405163ca3487f760e01b815260040160405180910390fd5b60005b818110156104545736848483818110610413576104136113b1565b905060200281019061042591906113c7565b905061044b61043482806113e7565b60208401356104466040860186611431565b610af0565b506001016103f8565b5060405181815233907f25a16c13690e68b4c9d49e1fbab1e490d7a6710945c61a77c745d0512b501e36906020015b60405180910390a2505050565b8060008190036104b35760405163ca3487f760e01b815260040160405180910390fd5b60005b8181101561050557368484838181106104d1576104d16113b1565b90506020028101906104e39190611431565b90506104fc6104f282806113e7565b83602001356105a9565b506001016104b6565b5060405181815233907f52cc3cbb9ba447be2acf8c157cfb0f7b133158f4c4ad10a43f43d77c18c3716890602001610483565b6105406109bb565b600180546001600160a01b0383166001600160a01b031990911681179091556105716000546001600160a01b031690565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b8160028110156105cb57604051622d38d560e61b815260040160405180910390fd5b6000610639858560008181106105e3576105e36113b1565b90506020028101906105f59190611447565b6106039060608101906113e7565b6000818110610614576106146113b1565b905060200281019061062691906113c7565b61063490602081019061145d565b610e98565b90506000858561064a6001866114a4565b818110610659576106596113b1565b905060200281019061066b9190611447565b61067c906040810190602001611394565b60408051600180825281830190925291925060009190816020015b606081526020019060019003908161069757905050905086866040516020016106c192919061163e565b604051602081830303815290604052816000815181106106e3576106e36113b1565b602090810291909101015260408051600180825281830190925260009181602001602082028036833701905050905061071a610eda565b8160008151811061072d5761072d6113b1565b6020908102919091010152604080516001808252818301909252600091816020015b606081526020019060019003908161074f575050604051306024820152604481018790529091506107d2907f0000000000000000000000000000000000000000000000000000000000000000906000906064015b60408051601f198184030181529190526020810180516001600160e01b031663a9059cbb60e01b179052610eed565b816000815181106107e5576107e56113b1565b602090810291909101015260405163cef6d20960e01b81526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169063cef6d20990610840908690869086906004016117c5565b600060405180830381600087803b15801561085a57600080fd5b505af115801561086e573d6000803e3d6000fd5b50506040516316762eed60e01b81526001600160a01b037f00000000000000000000000000000000000000000000000000000000000000008116600483015260248201899052604482018b90528781166064830152600093507f00000000000000000000000000000000000000000000000000000000000000001691506316762eed906084016020604051808303816000875af1158015610913573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610937919061182f565b90507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316856001600160a01b03167fa7b6bdc54af4440592478ca9547f46cc8b433eaf224768065fc6b910aab5cc1f88846040516109a7929190918252602082015260400190565b60405180910390a350505050505050505050565b6000546001600160a01b031633146102ce5760405163118cdaa760e01b81523360048201526024016103ab565b604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180516001600160e01b031663095ea7b360e01b179052610a398482610f1c565b6103cc576040516001600160a01b03848116602483015260006044830152610a9c91869182169063095ea7b3906064015b604051602081830303815290604052915060e01b6020820180516001600160e01b038381831617835250505050610fc6565b6103cc8482610fc6565b600180546001600160a01b03191690556103bd81611029565b6040516001600160a01b0383811660248301526044820183905261026b91859182169063a9059cbb90606401610a6a565b826002811015610b1257604051622d38d560e61b815260040160405180910390fd5b6000610b2a868660008181106105e3576105e36113b1565b905060008686610b3b6001866114a4565b818110610b4a57610b4a6113b1565b9050602002810190610b5c9190611447565b610b6d906040810190602001611394565b60408051600180825281830190925291925060009190816020015b6060815260200190600190039081610b885790505090508787604051602001610bb292919061163e565b60405160208183030381529060405281600081518110610bd457610bd46113b1565b6020908102919091010152604080516001808252818301909252600091816020016020820280368337019050509050610c0b610eda565b81600081518110610c1e57610c1e6113b1565b6020908102919091010152604080516001808252818301909252600091816020015b6060815260200190600190039081610c4057505060405130602482015260448101879052909150610c98907f0000000000000000000000000000000000000000000000000000000000000000906000906064016107a3565b81600081518110610cab57610cab6113b1565b602090810291909101015260405163cef6d20960e01b81526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169063cef6d20990610d06908690869086906004016117c5565b600060405180830381600087803b158015610d2057600080fd5b505af1158015610d34573d6000803e3d6000fd5b5050505060007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316637bad376560405180606001604052807f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031681526020018981526020018c8152508760008c6040518563ffffffff1660e01b8152600401610dd09493929190611848565b6020604051808303816000875af1158015610def573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610e13919061182f565b90507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316856001600160a01b03167fe1e067eab571218f837c5015c854ebbbc631dc6ec63b28e9deb6718d78a858b78884604051610e83929190918252602082015260400190565b60405180910390a35050505050505050505050565b60006034821015610ebc576040516386a371ad60e01b815260040160405180910390fd5b610eca6034601484866118ad565b610ed3916118d7565b9392505050565b6000610ee881808080611079565b905090565b6060838383604051602001610f04939291906118f5565b60405160208183030381529060405290509392505050565b6000806000846001600160a01b031684604051610f399190611934565b6000604051808303816000865af19150503d8060008114610f76576040519150601f19603f3d011682016040523d82523d6000602084013e610f7b565b606091505b5091509150818015610fa5575080511580610fa5575080806020019051810190610fa59190611946565b8015610fbb57506000856001600160a01b03163b115b925050505b92915050565b6000610fdb6001600160a01b038416836110e4565b90508051600014158015611000575080806020019051810190610ffe9190611946565b155b1561026b57604051635274afe760e01b81526001600160a01b03841660048201526024016103ab565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b604080516001600160f81b03198087166020830152851660218201526000602282018190526001600160e01b03198516602683015269ffffffffffffffffffff198416602a83015291016040516020818303038152906040526110db90611968565b95945050505050565b6060610ed38383600084600080856001600160a01b0316848660405161110a9190611934565b60006040518083038185875af1925050503d8060008114611147576040519150601f19603f3d011682016040523d82523d6000602084013e61114c565b606091505b509150915061115c868383611166565b9695505050505050565b60608261117b57611176826111c2565b610ed3565b815115801561119257506001600160a01b0384163b155b156111bb57604051639996b31560e01b81526001600160a01b03851660048201526024016103ab565b5080610ed3565b8051156111d25780518082602001fd5b604051630a12f52160e11b815260040160405180910390fd5b60008083601f8401126111fd57600080fd5b50813567ffffffffffffffff81111561121557600080fd5b6020830191508360208260051b850101111561123057600080fd5b9250929050565b60008060006040848603121561124c57600080fd5b833567ffffffffffffffff81111561126357600080fd5b61126f868287016111eb565b909790965060209590950135949350505050565b6001600160a01b03811681146103bd57600080fd5b6000806000606084860312156112ad57600080fd5b83356112b881611283565b92506020840135915060408401356112cf81611283565b809150509250925092565b600080600080606085870312156112f057600080fd5b843567ffffffffffffffff8082111561130857600080fd5b611314888389016111eb565b909650945060208701359350604087013591508082111561133457600080fd5b5085016040818803121561134757600080fd5b939692955090935050565b6000806020838503121561136557600080fd5b823567ffffffffffffffff81111561137c57600080fd5b611388858286016111eb565b90969095509350505050565b6000602082840312156113a657600080fd5b8135610ed381611283565b634e487b7160e01b600052603260045260246000fd5b60008235605e198336030181126113dd57600080fd5b9190910192915050565b6000808335601e198436030181126113fe57600080fd5b83018035915067ffffffffffffffff82111561141957600080fd5b6020019150600581901b360382131561123057600080fd5b60008235603e198336030181126113dd57600080fd5b6000823560be198336030181126113dd57600080fd5b6000808335601e1984360301811261147457600080fd5b83018035915067ffffffffffffffff82111561148f57600080fd5b60200191503681900382131561123057600080fd5b81810381811115610fc057634e487b7160e01b600052601160045260246000fd5b6000808335601e198436030181126114dc57600080fd5b830160208101925035905067ffffffffffffffff8111156114fc57600080fd5b8060051b360382131561123057600080fd5b6000808335601e1984360301811261152557600080fd5b830160208101925035905067ffffffffffffffff81111561154557600080fd5b80360382131561123057600080fd5b81835281816020850137506000828201602090810191909152601f909101601f19169091010190565b60008383855260208086019550808560051b830101846000805b8881101561163057858403601f19018a52823536899003605e190181126115bc578283fd5b8801606081356115cb81611283565b6001600160a01b031686526115e28288018361150e565b82898901526115f48389018284611554565b9250505060406116068184018461150e565b93508783038289015261161a838583611554565b9d89019d97505050938601935050600101611597565b509198975050505050505050565b60208082528181018390526000906040808401600586901b850182018785805b8981101561172257888403603f190185528235368c900360be19018112611683578283fd5b8b0160c0813561169281611283565b6001600160a01b039081168752828a0135906116ad82611283565b16868a0152818801358887015260606116c8818401846114c5565b83838a01526116da848a01828461157d565b9350505050608080830135818801525060a06116f88184018461150e565b93508783038289015261170c838583611554565b988b01989750505093880193505060010161165e565b50919998505050505050505050565b60005b8381101561174c578181015183820152602001611734565b50506000910152565b600082825180855260208086019550808260051b84010181860160005b848110156117b857601f1980878503018a528251805180865261179a81888801898501611731565b9a86019a601f01909116939093018401925090830190600101611772565b5090979650505050505050565b6060815260006117d86060830186611755565b82810360208481019190915285518083528682019282019060005b8181101561180f578451835293830193918301916001016117f3565b505084810360408601526118238187611755565b98975050505050505050565b60006020828403121561184157600080fd5b5051919050565b600060018060a01b038087511683526020870151602084015260408701516040840152808616606084015280851660808401525060c060a0830152823560c0830152611897602084018461150e565b604060e085015261182361010085018284611554565b600080858511156118bd57600080fd5b838611156118ca57600080fd5b5050820193919092039150565b80356020831015610fc057600019602084900360031b1b1692915050565b6bffffffffffffffffffffffff198460601b16815282601482015260008251611925816034850160208701611731565b91909101603401949350505050565b600082516113dd818460208701611731565b60006020828403121561195857600080fd5b81518015158114610ed357600080fd5b80516020808301519190811015611989576000198160200360031b1b821691505b5091905056fea26469706673582212204d8faa1d87f717e2fe0223901e3bdd37399d2323d3ef249782acc670ce880a1464736f6c6343000817003300000000000000000000000074dbd45b505fb3b9f94c31e5f15c41b8e9883b04000000000000000000000000db9b1e94b5b69df7e401ddbede43491141047db30000000000000000000000008b5ba7a24c30106f712217285fc9702d6a0e0d1c000000000000000000000000a23847d0b4842caa2badad8f082dd55c68007612000000000000000000000000aca92e438df0b2401ff60da7e4337b687a2435da", + "nonce": "0x30", + "chainId": "0x8f" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x7f0384", + "logs": [ + { + "address": "0x0a9bb266e06e1f003a61ab02cada1ee147951eb1", + "topics": [ + "0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x00000000000000000000000074dbd45b505fb3b9f94c31e5f15c41b8e9883b04" + ], + "data": "0x", + "blockHash": "0xced0359b65f89b3f6126308f5708016024f1f12c10933238a7d6538801c4c864", + "blockNumber": "0x69e2d0e", + "blockTimestamp": "0x6ac4df40", + "transactionHash": "0x4168f1715c823d520b58be489c88af2f95e2d3e6ed5202b351297e3eed214586", + "transactionIndex": "0x7", + "logIndex": "0x3e", + "removed": false + }, + { + "address": "0xaca92e438df0b2401ff60da7e4337b687a2435da", + "topics": [ + "0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925", + "0x0000000000000000000000000a9bb266e06e1f003a61ab02cada1ee147951eb1", + "0x0000000000000000000000008b5ba7a24c30106f712217285fc9702d6a0e0d1c" + ], + "data": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "blockHash": "0xced0359b65f89b3f6126308f5708016024f1f12c10933238a7d6538801c4c864", + "blockNumber": "0x69e2d0e", + "blockTimestamp": "0x6ac4df40", + "transactionHash": "0x4168f1715c823d520b58be489c88af2f95e2d3e6ed5202b351297e3eed214586", + "transactionIndex": "0x7", + "logIndex": "0x3f", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000000000200000000080800000000000000101000000000000000000000000004000000000000000000000000000200000000000000000000000000000000000000001000000000000000000000000000000000000020008000000800000000800000000000000800000000000000000400000008020000000000000000000000000002000000000000000000800000000020000000000000000000000000000000000000000000000000000080000000000000000000000100000000000000000000000000000000000000000000020000010000020000000000000000000000000000000000000000000000000000000", + "transactionHash": "0x4168f1715c823d520b58be489c88af2f95e2d3e6ed5202b351297e3eed214586", + "transactionIndex": "0x7", + "blockHash": "0xced0359b65f89b3f6126308f5708016024f1f12c10933238a7d6538801c4c864", + "blockNumber": "0x69e2d0e", + "gasUsed": "0x206ed6", + "effectiveGasPrice": "0x17bfac7c00", + "from": "0xb0403b32f54d0bd752113f4009e8b534c6669f44", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null + } + ], + "libraries": [], + "pending": [], + "returns": {}, + "timestamp": 1791287105763, + "chain": 143, + "commit": "7b9fe8d" +} \ No newline at end of file diff --git a/broadcast/DeployComplianceVedaAdapter.s.sol/143/run-latest.json b/broadcast/DeployComplianceVedaAdapter.s.sol/143/run-latest.json new file mode 100644 index 00000000..b5d6f077 --- /dev/null +++ b/broadcast/DeployComplianceVedaAdapter.s.sol/143/run-latest.json @@ -0,0 +1,85 @@ +{ + "transactions": [ + { + "hash": "0x4168f1715c823d520b58be489c88af2f95e2d3e6ed5202b351297e3eed214586", + "transactionType": "CREATE2", + "contractName": "ComplianceVedaAdapter", + "contractAddress": "0x0a9bb266e06e1f003a61ab02cada1ee147951eb1", + "function": null, + "arguments": [ + "0x74dbD45B505fB3b9F94C31E5f15c41b8E9883B04", + "0xdb9B1e94B5b69Df7e401DDbedE43491141047dB3", + "0x8b5bA7A24c30106F712217285Fc9702d6A0e0D1C", + "0xa23847d0B4842caA2bAdAD8F082dD55C68007612", + "0xacA92E438df0B2401fF60dA7E4337B687a2435DA" + ], + "transaction": { + "from": "0xb0403b32f54d0bd752113f4009e8b534c6669f44", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "value": "0x0", + "input": "0x4741544f520000000000000000000000000000000000000000000000000000006101006040523480156200001257600080fd5b5060405162001fec38038062001fec8339810160408190526200003591620004d4565b846001600160a01b0381166200006657604051631e4fbdf760e01b8152600060048201526024015b60405180910390fd5b620000718162000112565b506001600160a01b03841615806200009057506001600160a01b038316155b80620000a357506001600160a01b038216155b80620000b657506001600160a01b038116155b15620000d55760405163f6b2911f60e01b815260040160405180910390fd5b6001600160a01b0380851660805283811660a081905283821660c05290821660e0819052620001079160001962000130565b505050505062000599565b600180546001600160a01b03191690556200012d81620001fc565b50565b604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180516001600160e01b0390811663095ea7b360e01b179091526200018a90859083906200024c16565b620001f657604080516001600160a01b038516602482015260006044808301919091528251808303909101815260649091019091526020810180516001600160e01b0390811663095ea7b360e01b17909152620001ea918691620002fd16565b620001f68482620002fd565b50505050565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6000806000846001600160a01b0316846040516200026b919062000544565b6000604051808303816000865af19150503d8060008114620002aa576040519150601f19603f3d011682016040523d82523d6000602084013e620002af565b606091505b5091509150818015620002dd575080511580620002dd575080806020019051810190620002dd919062000575565b8015620002f457506000856001600160a01b03163b115b95945050505050565b6000620003146001600160a01b038416836200036c565b905080516000141580156200033c5750808060200190518101906200033a919062000575565b155b156200036757604051635274afe760e01b81526001600160a01b03841660048201526024016200005d565b505050565b60606200037c8383600062000383565b9392505050565b606081471015620003aa5760405163cd78605960e01b81523060048201526024016200005d565b600080856001600160a01b03168486604051620003c8919062000544565b60006040518083038185875af1925050503d806000811462000407576040519150601f19603f3d011682016040523d82523d6000602084013e6200040c565b606091505b5090925090506200041f86838362000429565b9695505050505050565b60608262000442576200043c826200048d565b6200037c565b81511580156200045a57506001600160a01b0384163b155b156200048557604051639996b31560e01b81526001600160a01b03851660048201526024016200005d565b50806200037c565b8051156200049e5780518082602001fd5b604051630a12f52160e11b815260040160405180910390fd5b80516001600160a01b0381168114620004cf57600080fd5b919050565b600080600080600060a08688031215620004ed57600080fd5b620004f886620004b7565b94506200050860208701620004b7565b93506200051860408701620004b7565b92506200052860608701620004b7565b91506200053860808701620004b7565b90509295509295909350565b6000825160005b818110156200056757602081860181015185830152016200054b565b506000920191825250919050565b6000602082840312156200058857600080fd5b815180151581146200037c57600080fd5b60805160a05160c05160e0516119c562000627600039600081816101b901528181610285015281816108870152818161093b01528181610c6d01528181610d760152610e1701526000818161011c015281816108c90152610d3c01526000818161023e015281816102a7015261077c015260008181610204015281816108070152610ccd01526119c56000f3fe608060405234801561001057600080fd5b50600436106100f55760003560e01c80638da5cb5b11610097578063e30c397811610066578063e30c3978146101ee578063ea4d3c9b146101ff578063f2fde38b14610226578063f3b977841461023957600080fd5b80638da5cb5b14610190578063af74d121146101a1578063c89039c5146101b4578063d83d314a146101db57600080fd5b8063715018a6116100d3578063715018a61461015a578063758d94581461016257806379ba5097146101755780638bd291191461017d57600080fd5b80632a490331146100fa5780633db1b8e51461010f57806357edab4e14610117575b600080fd5b61010d610108366004611237565b610260565b005b61010d610270565b61013e7f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b03909116815260200160405180910390f35b61010d6102d0565b61010d610170366004611298565b6102e2565b61010d610377565b61010d61018b3660046112da565b6103c0565b6000546001600160a01b031661013e565b61010d6101af366004611352565b6103d2565b61013e7f000000000000000000000000000000000000000000000000000000000000000081565b61010d6101e9366004611352565b610490565b6001546001600160a01b031661013e565b61013e7f000000000000000000000000000000000000000000000000000000000000000081565b61010d610234366004611394565b610538565b61013e7f000000000000000000000000000000000000000000000000000000000000000081565b61026b8383836105a9565b505050565b6102786109bb565b6102ce6001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000167f00000000000000000000000000000000000000000000000000000000000000006000196109e8565b565b6102d86109bb565b6102ce6000610aa6565b6102ea6109bb565b6001600160a01b03811661031157604051634e46966960e11b815260040160405180910390fd5b6103256001600160a01b0384168284610abf565b806001600160a01b0316836001600160a01b03167f0e191ee1e354192688af5ec7da0cc3d7f54ecb056e6418d42e73ddc4c37d36378460405161036a91815260200190565b60405180910390a3505050565b60015433906001600160a01b031681146103b45760405163118cdaa760e01b81526001600160a01b03821660048201526024015b60405180910390fd5b6103bd81610aa6565b50565b6103cc84848484610af0565b50505050565b8060008190036103f55760405163ca3487f760e01b815260040160405180910390fd5b60005b818110156104545736848483818110610413576104136113b1565b905060200281019061042591906113c7565b905061044b61043482806113e7565b60208401356104466040860186611431565b610af0565b506001016103f8565b5060405181815233907f25a16c13690e68b4c9d49e1fbab1e490d7a6710945c61a77c745d0512b501e36906020015b60405180910390a2505050565b8060008190036104b35760405163ca3487f760e01b815260040160405180910390fd5b60005b8181101561050557368484838181106104d1576104d16113b1565b90506020028101906104e39190611431565b90506104fc6104f282806113e7565b83602001356105a9565b506001016104b6565b5060405181815233907f52cc3cbb9ba447be2acf8c157cfb0f7b133158f4c4ad10a43f43d77c18c3716890602001610483565b6105406109bb565b600180546001600160a01b0383166001600160a01b031990911681179091556105716000546001600160a01b031690565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b8160028110156105cb57604051622d38d560e61b815260040160405180910390fd5b6000610639858560008181106105e3576105e36113b1565b90506020028101906105f59190611447565b6106039060608101906113e7565b6000818110610614576106146113b1565b905060200281019061062691906113c7565b61063490602081019061145d565b610e98565b90506000858561064a6001866114a4565b818110610659576106596113b1565b905060200281019061066b9190611447565b61067c906040810190602001611394565b60408051600180825281830190925291925060009190816020015b606081526020019060019003908161069757905050905086866040516020016106c192919061163e565b604051602081830303815290604052816000815181106106e3576106e36113b1565b602090810291909101015260408051600180825281830190925260009181602001602082028036833701905050905061071a610eda565b8160008151811061072d5761072d6113b1565b6020908102919091010152604080516001808252818301909252600091816020015b606081526020019060019003908161074f575050604051306024820152604481018790529091506107d2907f0000000000000000000000000000000000000000000000000000000000000000906000906064015b60408051601f198184030181529190526020810180516001600160e01b031663a9059cbb60e01b179052610eed565b816000815181106107e5576107e56113b1565b602090810291909101015260405163cef6d20960e01b81526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169063cef6d20990610840908690869086906004016117c5565b600060405180830381600087803b15801561085a57600080fd5b505af115801561086e573d6000803e3d6000fd5b50506040516316762eed60e01b81526001600160a01b037f00000000000000000000000000000000000000000000000000000000000000008116600483015260248201899052604482018b90528781166064830152600093507f00000000000000000000000000000000000000000000000000000000000000001691506316762eed906084016020604051808303816000875af1158015610913573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610937919061182f565b90507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316856001600160a01b03167fa7b6bdc54af4440592478ca9547f46cc8b433eaf224768065fc6b910aab5cc1f88846040516109a7929190918252602082015260400190565b60405180910390a350505050505050505050565b6000546001600160a01b031633146102ce5760405163118cdaa760e01b81523360048201526024016103ab565b604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180516001600160e01b031663095ea7b360e01b179052610a398482610f1c565b6103cc576040516001600160a01b03848116602483015260006044830152610a9c91869182169063095ea7b3906064015b604051602081830303815290604052915060e01b6020820180516001600160e01b038381831617835250505050610fc6565b6103cc8482610fc6565b600180546001600160a01b03191690556103bd81611029565b6040516001600160a01b0383811660248301526044820183905261026b91859182169063a9059cbb90606401610a6a565b826002811015610b1257604051622d38d560e61b815260040160405180910390fd5b6000610b2a868660008181106105e3576105e36113b1565b905060008686610b3b6001866114a4565b818110610b4a57610b4a6113b1565b9050602002810190610b5c9190611447565b610b6d906040810190602001611394565b60408051600180825281830190925291925060009190816020015b6060815260200190600190039081610b885790505090508787604051602001610bb292919061163e565b60405160208183030381529060405281600081518110610bd457610bd46113b1565b6020908102919091010152604080516001808252818301909252600091816020016020820280368337019050509050610c0b610eda565b81600081518110610c1e57610c1e6113b1565b6020908102919091010152604080516001808252818301909252600091816020015b6060815260200190600190039081610c4057505060405130602482015260448101879052909150610c98907f0000000000000000000000000000000000000000000000000000000000000000906000906064016107a3565b81600081518110610cab57610cab6113b1565b602090810291909101015260405163cef6d20960e01b81526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169063cef6d20990610d06908690869086906004016117c5565b600060405180830381600087803b158015610d2057600080fd5b505af1158015610d34573d6000803e3d6000fd5b5050505060007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316637bad376560405180606001604052807f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031681526020018981526020018c8152508760008c6040518563ffffffff1660e01b8152600401610dd09493929190611848565b6020604051808303816000875af1158015610def573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610e13919061182f565b90507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316856001600160a01b03167fe1e067eab571218f837c5015c854ebbbc631dc6ec63b28e9deb6718d78a858b78884604051610e83929190918252602082015260400190565b60405180910390a35050505050505050505050565b60006034821015610ebc576040516386a371ad60e01b815260040160405180910390fd5b610eca6034601484866118ad565b610ed3916118d7565b9392505050565b6000610ee881808080611079565b905090565b6060838383604051602001610f04939291906118f5565b60405160208183030381529060405290509392505050565b6000806000846001600160a01b031684604051610f399190611934565b6000604051808303816000865af19150503d8060008114610f76576040519150601f19603f3d011682016040523d82523d6000602084013e610f7b565b606091505b5091509150818015610fa5575080511580610fa5575080806020019051810190610fa59190611946565b8015610fbb57506000856001600160a01b03163b115b925050505b92915050565b6000610fdb6001600160a01b038416836110e4565b90508051600014158015611000575080806020019051810190610ffe9190611946565b155b1561026b57604051635274afe760e01b81526001600160a01b03841660048201526024016103ab565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b604080516001600160f81b03198087166020830152851660218201526000602282018190526001600160e01b03198516602683015269ffffffffffffffffffff198416602a83015291016040516020818303038152906040526110db90611968565b95945050505050565b6060610ed38383600084600080856001600160a01b0316848660405161110a9190611934565b60006040518083038185875af1925050503d8060008114611147576040519150601f19603f3d011682016040523d82523d6000602084013e61114c565b606091505b509150915061115c868383611166565b9695505050505050565b60608261117b57611176826111c2565b610ed3565b815115801561119257506001600160a01b0384163b155b156111bb57604051639996b31560e01b81526001600160a01b03851660048201526024016103ab565b5080610ed3565b8051156111d25780518082602001fd5b604051630a12f52160e11b815260040160405180910390fd5b60008083601f8401126111fd57600080fd5b50813567ffffffffffffffff81111561121557600080fd5b6020830191508360208260051b850101111561123057600080fd5b9250929050565b60008060006040848603121561124c57600080fd5b833567ffffffffffffffff81111561126357600080fd5b61126f868287016111eb565b909790965060209590950135949350505050565b6001600160a01b03811681146103bd57600080fd5b6000806000606084860312156112ad57600080fd5b83356112b881611283565b92506020840135915060408401356112cf81611283565b809150509250925092565b600080600080606085870312156112f057600080fd5b843567ffffffffffffffff8082111561130857600080fd5b611314888389016111eb565b909650945060208701359350604087013591508082111561133457600080fd5b5085016040818803121561134757600080fd5b939692955090935050565b6000806020838503121561136557600080fd5b823567ffffffffffffffff81111561137c57600080fd5b611388858286016111eb565b90969095509350505050565b6000602082840312156113a657600080fd5b8135610ed381611283565b634e487b7160e01b600052603260045260246000fd5b60008235605e198336030181126113dd57600080fd5b9190910192915050565b6000808335601e198436030181126113fe57600080fd5b83018035915067ffffffffffffffff82111561141957600080fd5b6020019150600581901b360382131561123057600080fd5b60008235603e198336030181126113dd57600080fd5b6000823560be198336030181126113dd57600080fd5b6000808335601e1984360301811261147457600080fd5b83018035915067ffffffffffffffff82111561148f57600080fd5b60200191503681900382131561123057600080fd5b81810381811115610fc057634e487b7160e01b600052601160045260246000fd5b6000808335601e198436030181126114dc57600080fd5b830160208101925035905067ffffffffffffffff8111156114fc57600080fd5b8060051b360382131561123057600080fd5b6000808335601e1984360301811261152557600080fd5b830160208101925035905067ffffffffffffffff81111561154557600080fd5b80360382131561123057600080fd5b81835281816020850137506000828201602090810191909152601f909101601f19169091010190565b60008383855260208086019550808560051b830101846000805b8881101561163057858403601f19018a52823536899003605e190181126115bc578283fd5b8801606081356115cb81611283565b6001600160a01b031686526115e28288018361150e565b82898901526115f48389018284611554565b9250505060406116068184018461150e565b93508783038289015261161a838583611554565b9d89019d97505050938601935050600101611597565b509198975050505050505050565b60208082528181018390526000906040808401600586901b850182018785805b8981101561172257888403603f190185528235368c900360be19018112611683578283fd5b8b0160c0813561169281611283565b6001600160a01b039081168752828a0135906116ad82611283565b16868a0152818801358887015260606116c8818401846114c5565b83838a01526116da848a01828461157d565b9350505050608080830135818801525060a06116f88184018461150e565b93508783038289015261170c838583611554565b988b01989750505093880193505060010161165e565b50919998505050505050505050565b60005b8381101561174c578181015183820152602001611734565b50506000910152565b600082825180855260208086019550808260051b84010181860160005b848110156117b857601f1980878503018a528251805180865261179a81888801898501611731565b9a86019a601f01909116939093018401925090830190600101611772565b5090979650505050505050565b6060815260006117d86060830186611755565b82810360208481019190915285518083528682019282019060005b8181101561180f578451835293830193918301916001016117f3565b505084810360408601526118238187611755565b98975050505050505050565b60006020828403121561184157600080fd5b5051919050565b600060018060a01b038087511683526020870151602084015260408701516040840152808616606084015280851660808401525060c060a0830152823560c0830152611897602084018461150e565b604060e085015261182361010085018284611554565b600080858511156118bd57600080fd5b838611156118ca57600080fd5b5050820193919092039150565b80356020831015610fc057600019602084900360031b1b1692915050565b6bffffffffffffffffffffffff198460601b16815282601482015260008251611925816034850160208701611731565b91909101603401949350505050565b600082516113dd818460208701611731565b60006020828403121561195857600080fd5b81518015158114610ed357600080fd5b80516020808301519190811015611989576000198160200360031b1b821691505b5091905056fea26469706673582212204d8faa1d87f717e2fe0223901e3bdd37399d2323d3ef249782acc670ce880a1464736f6c6343000817003300000000000000000000000074dbd45b505fb3b9f94c31e5f15c41b8e9883b04000000000000000000000000db9b1e94b5b69df7e401ddbede43491141047db30000000000000000000000008b5ba7a24c30106f712217285fc9702d6a0e0d1c000000000000000000000000a23847d0b4842caa2badad8f082dd55c68007612000000000000000000000000aca92e438df0b2401ff60da7e4337b687a2435da", + "nonce": "0x30", + "chainId": "0x8f" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x7f0384", + "logs": [ + { + "address": "0x0a9bb266e06e1f003a61ab02cada1ee147951eb1", + "topics": [ + "0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x00000000000000000000000074dbd45b505fb3b9f94c31e5f15c41b8e9883b04" + ], + "data": "0x", + "blockHash": "0xced0359b65f89b3f6126308f5708016024f1f12c10933238a7d6538801c4c864", + "blockNumber": "0x69e2d0e", + "blockTimestamp": "0x6ac4df40", + "transactionHash": "0x4168f1715c823d520b58be489c88af2f95e2d3e6ed5202b351297e3eed214586", + "transactionIndex": "0x7", + "logIndex": "0x3e", + "removed": false + }, + { + "address": "0xaca92e438df0b2401ff60da7e4337b687a2435da", + "topics": [ + "0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925", + "0x0000000000000000000000000a9bb266e06e1f003a61ab02cada1ee147951eb1", + "0x0000000000000000000000008b5ba7a24c30106f712217285fc9702d6a0e0d1c" + ], + "data": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "blockHash": "0xced0359b65f89b3f6126308f5708016024f1f12c10933238a7d6538801c4c864", + "blockNumber": "0x69e2d0e", + "blockTimestamp": "0x6ac4df40", + "transactionHash": "0x4168f1715c823d520b58be489c88af2f95e2d3e6ed5202b351297e3eed214586", + "transactionIndex": "0x7", + "logIndex": "0x3f", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000000000200000000080800000000000000101000000000000000000000000004000000000000000000000000000200000000000000000000000000000000000000001000000000000000000000000000000000000020008000000800000000800000000000000800000000000000000400000008020000000000000000000000000002000000000000000000800000000020000000000000000000000000000000000000000000000000000080000000000000000000000100000000000000000000000000000000000000000000020000010000020000000000000000000000000000000000000000000000000000000", + "transactionHash": "0x4168f1715c823d520b58be489c88af2f95e2d3e6ed5202b351297e3eed214586", + "transactionIndex": "0x7", + "blockHash": "0xced0359b65f89b3f6126308f5708016024f1f12c10933238a7d6538801c4c864", + "blockNumber": "0x69e2d0e", + "gasUsed": "0x206ed6", + "effectiveGasPrice": "0x17bfac7c00", + "from": "0xb0403b32f54d0bd752113f4009e8b534c6669f44", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null + } + ], + "libraries": [], + "pending": [], + "returns": {}, + "timestamp": 1791287105763, + "chain": 143, + "commit": "7b9fe8d" +} \ No newline at end of file diff --git a/documents/Deployments.md b/documents/Deployments.md index 16b3dfc1..04594893 100644 --- a/documents/Deployments.md +++ b/documents/Deployments.md @@ -15,6 +15,11 @@ forge script script/DeployCaveatEnforcers.s.sol --rpc-url --priva **MegaETH:** append `--gas-limit 300000000 --with-gas-price 2100000 --priority-gas-price 100000 --skip-simulation` to the `forge script` command (mainnet and testnet). +**Monad (`DeployVedaAdapter` and `DeployComplianceVedaAdapter`):** mUSD uses post-London opcodes and this repo pins `evm_version = "london"`, so the constructor's `forceApprove` reverts in the local script run. `--skip-simulation` does not skip that local run. Pick one: + +1. Append `--evm-version shanghai`. Compile, broadcast, and verify with that same setting: a different compiler EVM version changes creation code and the CREATE2 address. +2. Stay on London and uncomment the `vm.mockCall` (and `vm.clearMockedCalls`) in the script's `run()`. The mock only affects the local simulation; the real constructor still runs on-chain. Leave those lines commented when using `--evm-version shanghai`. + ### Salt Value The deployment scripts use a predefined salt value, which is **"GATOR"**. This value is converted into a **`bytes32`** format. diff --git a/script/DeployComplianceVedaAdapter.s.sol b/script/DeployComplianceVedaAdapter.s.sol new file mode 100644 index 00000000..63f3426d --- /dev/null +++ b/script/DeployComplianceVedaAdapter.s.sol @@ -0,0 +1,65 @@ +// 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 { ComplianceVedaAdapter } from "../src/helpers/ComplianceVedaAdapter.sol"; + +/** + * @title DeployComplianceVedaAdapter + * @notice Deploys ComplianceVedaAdapter deterministically with CREATE2. + * @dev Run with: + * forge script script/DeployComplianceVedaAdapter.s.sol --rpc-url --private-key $PRIVATE_KEY --broadcast + * On Monad, mUSD uses post-London opcodes and this repo pins London, so the constructor's forceApprove + * reverts in the local script run. Pick one: + * 1. Append --evm-version shanghai. Compile, broadcast, and verify with that same setting: a different + * compiler EVM version changes creation code and the CREATE2 address. + * 2. Stay on London and uncomment the vm.mockCall (and vm.clearMockedCalls) in run(). The mock only + * affects the local simulation; the real constructor still runs on-chain. + */ +contract DeployComplianceVedaAdapter is Script { + bytes32 internal salt; + address internal vedaAdapterOwner; + address internal delegationManager; + address internal boringVault; + address internal vedaTeller; + address internal 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"); + + 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("Salt:"); + console2.logBytes32(salt); + } + + function run() public { + vm.startBroadcast(); + + // London only. Foundry's fork mode cannot interact with mUSD on Monad (NotActivated in revm). + // Uncomment so the local simulation passes. Leave commented when using --evm-version shanghai. + // The mock only affects the local simulation; the real constructor still runs on-chain. + // vm.mockCall(depositToken, abi.encodeWithSelector(bytes4(keccak256("approve(address,uint256)"))), abi.encode(true)); + + address deployed = address( + new ComplianceVedaAdapter{ salt: salt }(vedaAdapterOwner, delegationManager, boringVault, vedaTeller, depositToken) + ); + + vm.stopBroadcast(); + console2.log("ComplianceVedaAdapter: %s", deployed); + + // Uncomment together with vm.mockCall above when deploying on London. + // vm.clearMockedCalls(); + } +} diff --git a/script/DeployVedaAdapter.s.sol b/script/DeployVedaAdapter.s.sol index 52eece91..ff56d283 100644 --- a/script/DeployVedaAdapter.s.sol +++ b/script/DeployVedaAdapter.s.sol @@ -12,7 +12,12 @@ import { VedaAdapter } from "../src/helpers/VedaAdapter.sol"; * @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. + * On Monad, mUSD uses post-London opcodes and this repo pins London, so the constructor's forceApprove + * reverts in the local script run. Pick one: + * 1. Append --evm-version shanghai. Compile, broadcast, and verify with that same setting: a different + * compiler EVM version changes creation code and the CREATE2 address. + * 2. Stay on London and uncomment the vm.mockCall (and vm.clearMockedCalls) in run(). The mock only + * affects the local simulation; the real constructor still runs on-chain. */ contract DeployVedaAdapter is Script { bytes32 salt; @@ -45,9 +50,9 @@ contract DeployVedaAdapter is Script { 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. + // London only. Foundry's fork mode cannot interact with mUSD on Monad (NotActivated in revm). + // Uncomment so the local simulation passes. Leave commented when using --evm-version shanghai. + // The mock only affects the local simulation; the real constructor still runs on-chain. // vm.mockCall(depositToken, abi.encodeWithSelector(bytes4(keccak256("approve(address,uint256)"))), abi.encode(true)); vm.startBroadcast(); @@ -58,6 +63,7 @@ contract DeployVedaAdapter is Script { vm.stopBroadcast(); + // Uncomment together with vm.mockCall above when deploying on London. // vm.clearMockedCalls(); } } diff --git a/src/helpers/ComplianceVedaAdapter.sol b/src/helpers/ComplianceVedaAdapter.sol new file mode 100644 index 00000000..5e620e48 --- /dev/null +++ b/src/helpers/ComplianceVedaAdapter.sol @@ -0,0 +1,479 @@ +// 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 { IComplianceVedaTeller } from "./interfaces/IComplianceVedaTeller.sol"; + +/** + * @title ComplianceVedaAdapter + * @notice Adapter contract that enables compliance-gated Veda BoringVault deposit and withdrawal operations + * through MetaMask's delegation framework + * @dev This contract acts as an intermediary between users and a compliance-enabled Veda Teller + * (`TellerWithMultiAssetSupport` Base V0.3 / `TellerWithYieldStreaming`), enabling delegation-based + * token operations without requiring direct token approvals from the user to the vault. + * + * 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. Deposits use the struct-based + * `deposit(DepositParams, to, referral, ComplianceData)` selector. Withdrawals use + * `teller.withdraw()` on TellerWithYieldStreaming. + * - 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. + * + * Compliance: + * - An off-chain backend verifies that the share recipient (`to`) is an approved account and produces + * an EIP-191 (`personal_sign`) signature. The Teller recovers the signer and checks that the signer + * holds the configured `complianceSignerRole` on the Teller's `RolesAuthority`. + * - The digest is `keccak256(abi.encode(teller, chainId, msg.sender, to, asset, amount, deadline))`, + * then prefixed with `"\x19Ethereum Signed Message:\n32"`. There is no separate nonce: replay + * protection is the Teller's `usedComplianceSignatures` map of that digest. + * - Because this adapter is `msg.sender` on the Teller call, the signature MUST bind this adapter as + * the caller and the root delegator as `to` (share recipient). A signature issued for a direct user + * deposit cannot be reused through this adapter. + * - `TellerWithMultiAssetSupport__ComplianceCheckFailed()` means either that `deadline` has passed + * (`block.timestamp > deadline`) or, when `complianceWindow` is nonzero, that the acceptance window + * has not begun (`deadline > block.timestamp + complianceWindow`). A nonzero window accepts an + * approval only while `block.timestamp <= deadline <= block.timestamp + complianceWindow`, which is + * from `deadline - complianceWindow` through `deadline`, inclusive. The error alone does not show + * that the approval has expired. An early rejection leaves the approval unconsumed and rolls back + * the leaf's transfer-allowance consumption, so the original signed delegation and approval can + * still succeed once the window opens. The Teller's replay key includes `deadline`, so a replacement + * approval with a different deadline is a separate key and does not cancel the original. On retry, + * reuse the same amount-capped leaf, or revoke the superseded leaf before submitting a replacement. + * A fresh leaf together with a new approval can both execute and spend allowance twice. + * - Veda RolesAuthority prerequisites: + * - `allowlistedRouterRole` must permit this adapter to route both deposits and withdrawals to the + * root delegator, because the Teller sees `to != msg.sender` in both flows. + * - When `transferAllowedRole` is enabled, withdrawals must satisfy that role during the delegated + * vault-share transfer. Granting it to this adapter supports all users. + * - The Teller's deposit and withdraw selectors must be public or otherwise callable by this adapter. + * Withdrawals do not require a compliance signature, but these role checks still apply. + * + * 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 ComplianceVedaAdapter contract + * + * 2. The operator then redelegates to this ComplianceVedaAdapter contract. The leaf caveat is an + * `ERC20TransferAmountEnforcer` capped to the exact amount. That enforcer already requires + * `IERC20.transfer` (`0xa9059cbb`) and the token target — `depositToken` for deposits, + * `boringVault` for withdrawals — so a separate allowed-methods caveat is not used. A limited-calls + * enforcer can further restrict the redelegation to a single execution. + * + * 3. For deposits: the adapter redeems the delegation chain, transfers tokens from the user to itself, + * and calls `teller.deposit(...)` with the caller-supplied `ComplianceData` 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: + * - ComplianceVedaAdapter 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. The enforcer evaluates the + * preliminary token transfer redeemed through `DelegationManager`, not the adapter entry point or + * the subsequent Teller call. + * + * @notice Security consideration: Anyone can call `depositByDelegation` and `withdrawByDelegation` — there is no + * caller restriction. Security is enforced entirely through the delegation chain and, for deposits, the + * Teller's compliance check. 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. A valid compliance signature is additionally bound to + * this adapter, the recipient, the asset, the amount, and a deadline; it cannot be replayed after the Teller + * marks the digest as used. `TellerWithMultiAssetSupport__ComplianceCheckFailed()` before the acceptance + * window opens does not mark that digest used. + */ +contract ComplianceVedaAdapter is Ownable2Step { + using SafeERC20 for IERC20; + using ExecutionLib for bytes; + using ModeLib for ModeCode; + + /** + * @notice Parameters for a single deposit operation in a batch + * @dev `compliance` is forwarded to the Teller unchanged. Each stream needs its own signature because + * the digest includes amount and deadline, and used signatures cannot be replayed. + */ + struct DepositParams { + Delegation[] delegations; + uint256 minimumMint; + IComplianceVedaTeller.ComplianceData compliance; + } + + /** + * @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 compliance-gated Teller contract for deposit and withdrawal operations + */ + IComplianceVedaTeller 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 compliance-gated 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 = IComplianceVedaTeller(_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). + * `_compliance` is forwarded as-is. The Teller verifies the EIP-191 signature, signer role, and + * unused digest. When `complianceWindow` is nonzero it accepts the approval only while + * `block.timestamp <= deadline <= block.timestamp + complianceWindow`. + * `TellerWithMultiAssetSupport__ComplianceCheckFailed()` is returned both after `deadline` and + * before that window opens. An early rejection does not consume the approval or the leaf allowance, + * and a replacement approval uses a different `deadline`, so it does not invalidate the original. + * Retry with the same amount-capped leaf, or revoke the superseded leaf before using a replacement. + * The signed `msg.sender` field must be this adapter and `to` must be the root delegator. + * @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. Minted shares are the accountant's fair value reduced by + * `assetData(depositToken).sharePremium` when that premium is non-zero. The Teller owner can + * set the premium up to 1,000 bps (10%) through `updateAssetData`. Rate drift from yield + * streaming is negligible beside that haircut. A tolerance of 0.1-0.5% covers rate drift only + * while the premium is zero; otherwise size this bound for the configured premium. If this + * check causes a revert, no funds are lost — retry with a fresh quote. + * @param _compliance Backend-issued compliance approval (`deadline` + `signature`) for this deposit + * @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, + IComplianceVedaTeller.ComplianceData calldata _compliance + ) + external + { + _executeDepositByDelegation(_delegations, _minimumMint, _compliance); + } + + /** + * @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. Each stream must carry its own + * `ComplianceData`; a signature cannot be reused across streams. + * @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, params_.compliance); + 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). + * Withdrawals do not require a compliance signature. The Teller's routing and transfer role checks + * still apply; see the contract-level RolesAuthority prerequisites. + * @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. + * Recommended delegation: restrict this owner action by token, recipient, amount, and call count. + * @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. + * Recommended delegation: allow only this selector and limit it to one call. + */ + 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. + * Forwards `_compliance` to the Teller; this adapter is `msg.sender` and `to` is the root delegator. + * @param _delegations Delegation chain, sorted leaf to root + * @param _minimumMint Minimum vault shares expected (sanity-check bound) + * @param _compliance Backend-issued compliance approval for this deposit + */ + function _executeDepositByDelegation( + Delegation[] calldata _delegations, + uint256 _minimumMint, + IComplianceVedaTeller.ComplianceData calldata _compliance + ) + 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_); + + // Deposit into Teller: pulls depositToken from this adapter, mints shares to root delegator + uint256 shares_ = teller.deposit( + IComplianceVedaTeller.DepositParams({ + depositAsset: address(depositToken), depositAmount: amount_, minimumMint: _minimumMint + }), + rootDelegator_, + address(0), + _compliance + ); + + 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/IComplianceVedaTeller.sol b/src/helpers/interfaces/IComplianceVedaTeller.sol new file mode 100644 index 00000000..b247f1e0 --- /dev/null +++ b/src/helpers/interfaces/IComplianceVedaTeller.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: MIT AND Apache-2.0 +pragma solidity 0.8.23; + +/** + * @title IComplianceVedaTeller + * @notice Interface for compliance-gated Veda tellers using the Base V0.3 deposit API. + * @dev Asset fields use address instead of Solmate's ERC20 type because both have the same ABI encoding. + */ +interface IComplianceVedaTeller { + struct DepositParams { + address depositAsset; + uint256 depositAmount; + uint256 minimumMint; + } + + struct ComplianceData { + uint256 deadline; + bytes signature; + } + + /** + * @notice Deposits an asset and mints vault shares to `to`. + * @dev The compliance signature is bound to this teller, chain ID, caller, recipient, asset, amount, and deadline. + */ + function deposit( + DepositParams calldata params, + address to, + address referralAddress, + ComplianceData calldata compliance + ) + external + payable + returns (uint256 shares); + + /** + * @notice Burns shares from the caller and sends the selected asset to `to`. + * @dev Available on TellerWithYieldStreaming. + */ + function withdraw( + address withdrawAsset, + uint256 shareAmount, + uint256 minimumAssets, + address to + ) + external + returns (uint256 assetsOut); +} diff --git a/test/helpers/ComplianceVedaLending.t.sol b/test/helpers/ComplianceVedaLending.t.sol new file mode 100644 index 00000000..95f11c94 --- /dev/null +++ b/test/helpers/ComplianceVedaLending.t.sol @@ -0,0 +1,388 @@ +// SPDX-License-Identifier: MIT AND Apache-2.0 +pragma solidity 0.8.23; + +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +import { EncoderLib } from "../../src/libraries/EncoderLib.sol"; +import { IComplianceVedaTeller } from "../../src/helpers/interfaces/IComplianceVedaTeller.sol"; +import { ComplianceVedaAdapter } from "../../src/helpers/ComplianceVedaAdapter.sol"; +import { ERC20TransferAmountEnforcer } from "../../src/enforcers/ERC20TransferAmountEnforcer.sol"; +import { RedeemerEnforcer } from "../../src/enforcers/RedeemerEnforcer.sol"; +import { Delegation, Caveat } from "../../src/utils/Types.sol"; +import { BaseTest } from "../utils/BaseTest.t.sol"; +import { Implementation, SignatureType, TestUser } from "../utils/Types.t.sol"; + +interface IRolesAuthority { + function owner() external view returns (address); + function setUserRole(address user, uint8 role, bool enabled) external; + function setRoleCapability(uint8 role, address target, bytes4 functionSig, bool enabled) external; +} + +interface ITellerConfiguration { + function owner() external view returns (address); + function setTransferRestrictions(uint8 transferAllowedRole, uint8 allowlistedRouterRole) external; + function assetData(address asset) external view returns (bool allowDeposits, bool allowWithdraws, uint16 sharePremium); + function updateAssetData(address asset, bool allowDeposits, bool allowWithdraws, uint16 sharePremium) external; +} + +/// @dev Custom errors thrown by the deployed Veda `TellerWithMultiAssetSupport` (Base V0.3). Declared here so the +/// negative tests can assert the exact revert reason instead of accepting any revert. +interface ITellerErrors { + error TellerWithMultiAssetSupport__ComplianceCheckFailed(); + error TellerWithMultiAssetSupport__MinimumMintNotMet(); +} + +/// forge-config: default.evm_version = "cancun" +contract ComplianceVedaLendingTest is BaseTest { + IComplianceVedaTeller internal constant TELLER = IComplianceVedaTeller(0xB0025a2eBc0474d4F28E975F0D3E70471246ebae); + IERC20 internal constant BORING_VAULT = IERC20(0xBFeC8c2b1ccea3931a1363E4CaC27352c1C908B7); + IERC20 internal constant MUSD = IERC20(0xacA92E438df0B2401fF60dA7E4337B687a2435DA); + IRolesAuthority internal constant ROLES_AUTHORITY = IRolesAuthority(0x00f0CF4f540D1d47470f565Ecb11a75E073c2dd0); + + address internal constant MUSD_WHALE = 0x6cb5094cfe45Ee97938702637478fe7146A8FA0f; + uint8 internal constant COMPLIANCE_SIGNER_ROLE = 36; + uint8 internal constant TRANSFER_ALLOWED_ROLE = 37; + uint8 internal constant TEST_ROUTER_ROLE = 254; + + uint256 internal constant INITIAL_MUSD_BALANCE = 100_000e6; + uint256 internal constant DEPOSIT_AMOUNT = 1_000e6; + uint256 internal constant COMPLIANCE_SIGNER_KEY = 0xC011A11CE; + + ERC20TransferAmountEnforcer internal erc20TransferAmountEnforcer; + RedeemerEnforcer internal redeemerEnforcer; + ComplianceVedaAdapter internal adapter; + address internal adapterOwner; + address internal complianceSigner; + + function setUp() public override { + vm.createSelectFork(vm.envString("MONAD_RPC_URL")); + + IMPLEMENTATION = Implementation.Hybrid; + SIGNATURE_TYPE = SignatureType.RawP256; + super.setUp(); + + adapterOwner = makeAddr("ComplianceVedaAdapter Owner"); + complianceSigner = vm.addr(COMPLIANCE_SIGNER_KEY); + erc20TransferAmountEnforcer = new ERC20TransferAmountEnforcer(); + redeemerEnforcer = new RedeemerEnforcer(); + adapter = new ComplianceVedaAdapter( + adapterOwner, address(delegationManager), address(BORING_VAULT), address(TELLER), address(MUSD) + ); + + _configureForkRoles(); + + vm.prank(MUSD_WHALE); + require(MUSD.transfer(address(users.alice.deleGator), INITIAL_MUSD_BALANCE), "mUSD funding failed"); + + vm.label(address(adapter), "ComplianceVedaAdapter"); + vm.label(address(TELLER), "Veda Compliance Teller"); + vm.label(address(BORING_VAULT), "Premium mUSD Vault"); + vm.label(address(MUSD), "mUSD"); + vm.label(complianceSigner, "Compliance Signer"); + } + + function test_deposit_direct_withCompliance() public { + uint256 deadline_ = block.timestamp + 30 minutes; + bytes memory signature_ = _signCompliance( + address(users.alice.deleGator), address(users.alice.deleGator), DEPOSIT_AMOUNT, deadline_, COMPLIANCE_SIGNER_KEY + ); + + vm.startPrank(address(users.alice.deleGator)); + MUSD.approve(address(BORING_VAULT), DEPOSIT_AMOUNT); + uint256 shares_ = TELLER.deposit( + IComplianceVedaTeller.DepositParams(address(MUSD), DEPOSIT_AMOUNT, 0), + address(users.alice.deleGator), + address(0), + IComplianceVedaTeller.ComplianceData(deadline_, signature_) + ); + vm.stopPrank(); + + assertGt(shares_, 0); + assertEq(BORING_VAULT.balanceOf(address(users.alice.deleGator)), shares_); + } + + function test_deposit_viaAdapter_withCompliance() public { + uint256 shares_ = _depositViaAdapter(DEPOSIT_AMOUNT, 0, block.timestamp + 30 minutes); + + assertGt(shares_, 0); + assertEq(MUSD.balanceOf(address(adapter)), 0); + assertEq(BORING_VAULT.balanceOf(address(adapter)), 0); + } + + function test_withdraw_viaAdapter_afterComplianceDeposit() public { + _depositViaAdapter(DEPOSIT_AMOUNT, 0, block.timestamp + 30 minutes); + uint256 shares_ = BORING_VAULT.balanceOf(address(users.alice.deleGator)); + uint256 assetsBefore_ = MUSD.balanceOf(address(users.alice.deleGator)); + Delegation[] memory delegations_ = _createDelegationChain(address(BORING_VAULT), shares_, 1); + + vm.prank(address(users.bob.deleGator)); + adapter.withdrawByDelegation(delegations_, 0); + + assertEq(BORING_VAULT.balanceOf(address(users.alice.deleGator)), 0); + assertGt(MUSD.balanceOf(address(users.alice.deleGator)), assetsBefore_); + assertEq(BORING_VAULT.balanceOf(address(adapter)), 0); + } + + function test_deposit_batch_withUniqueComplianceApprovals() public { + uint256 firstAmount_ = DEPOSIT_AMOUNT; + uint256 secondAmount_ = DEPOSIT_AMOUNT / 2; + ComplianceVedaAdapter.DepositParams[] memory streams_ = new ComplianceVedaAdapter.DepositParams[](2); + streams_[0] = _buildDepositParams(firstAmount_, 10, block.timestamp + 20 minutes); + streams_[1] = _buildDepositParams(secondAmount_, 11, block.timestamp + 21 minutes); + + vm.prank(address(users.bob.deleGator)); + adapter.depositByDelegationBatch(streams_); + + assertGt(BORING_VAULT.balanceOf(address(users.alice.deleGator)), 0); + assertEq(MUSD.balanceOf(address(adapter)), 0); + } + + function test_withdraw_batch_afterComplianceDeposits() public { + _depositViaAdapter(DEPOSIT_AMOUNT, 12, block.timestamp + 20 minutes); + uint256 shares_ = BORING_VAULT.balanceOf(address(users.alice.deleGator)); + uint256 firstShareAmount_ = shares_ / 2; + + ComplianceVedaAdapter.WithdrawParams[] memory streams_ = new ComplianceVedaAdapter.WithdrawParams[](2); + streams_[0] = ComplianceVedaAdapter.WithdrawParams({ + delegations: _createDelegationChain(address(BORING_VAULT), firstShareAmount_, 13), minimumAssets: 0 + }); + streams_[1] = ComplianceVedaAdapter.WithdrawParams({ + delegations: _createDelegationChain(address(BORING_VAULT), shares_ - firstShareAmount_, 14), minimumAssets: 0 + }); + + vm.prank(address(users.bob.deleGator)); + adapter.withdrawByDelegationBatch(streams_); + + assertEq(BORING_VAULT.balanceOf(address(users.alice.deleGator)), 0); + assertEq(BORING_VAULT.balanceOf(address(adapter)), 0); + } + + function test_reverts_replayedComplianceSignatureWithFreshDelegation() public { + uint256 deadline_ = block.timestamp + 30 minutes; + _depositViaAdapter(DEPOSIT_AMOUNT, 20, deadline_); + + Delegation[] memory freshDelegations_ = _createDelegationChain(address(MUSD), DEPOSIT_AMOUNT, 21); + IComplianceVedaTeller.ComplianceData memory compliance_ = + _complianceData(address(users.alice.deleGator), DEPOSIT_AMOUNT, deadline_, COMPLIANCE_SIGNER_KEY); + + vm.prank(address(users.bob.deleGator)); + vm.expectRevert(ITellerErrors.TellerWithMultiAssetSupport__ComplianceCheckFailed.selector); + adapter.depositByDelegation(freshDelegations_, 0, compliance_); + } + + function test_reverts_expiredComplianceSignature() public { + uint256 deadline_ = block.timestamp - 1; + Delegation[] memory delegations_ = _createDelegationChain(address(MUSD), DEPOSIT_AMOUNT, 30); + IComplianceVedaTeller.ComplianceData memory compliance_ = + _complianceData(address(users.alice.deleGator), DEPOSIT_AMOUNT, deadline_, COMPLIANCE_SIGNER_KEY); + + vm.prank(address(users.bob.deleGator)); + vm.expectRevert(ITellerErrors.TellerWithMultiAssetSupport__ComplianceCheckFailed.selector); + adapter.depositByDelegation(delegations_, 0, compliance_); + } + + function test_reverts_wrongComplianceSigner() public { + uint256 deadline_ = block.timestamp + 30 minutes; + Delegation[] memory delegations_ = _createDelegationChain(address(MUSD), DEPOSIT_AMOUNT, 31); + IComplianceVedaTeller.ComplianceData memory compliance_ = + _complianceData(address(users.alice.deleGator), DEPOSIT_AMOUNT, deadline_, 0xBAD); + + vm.prank(address(users.bob.deleGator)); + vm.expectRevert(ITellerErrors.TellerWithMultiAssetSupport__ComplianceCheckFailed.selector); + adapter.depositByDelegation(delegations_, 0, compliance_); + } + + function test_reverts_signatureForWrongRecipient() public { + uint256 deadline_ = block.timestamp + 30 minutes; + Delegation[] memory delegations_ = _createDelegationChain(address(MUSD), DEPOSIT_AMOUNT, 32); + IComplianceVedaTeller.ComplianceData memory compliance_ = + _complianceData(address(users.bob.deleGator), DEPOSIT_AMOUNT, deadline_, COMPLIANCE_SIGNER_KEY); + + vm.prank(address(users.bob.deleGator)); + vm.expectRevert(ITellerErrors.TellerWithMultiAssetSupport__ComplianceCheckFailed.selector); + adapter.depositByDelegation(delegations_, 0, compliance_); + } + + /// @notice A non-zero `sharePremium` haircuts minted shares below the fair-value amount. + function test_deposit_sharePremiumHaircutsMint() public { + _setSharePremium(0); + uint256 fairShares_ = _depositViaAdapter(DEPOSIT_AMOUNT, 40, block.timestamp + 30 minutes); + + uint16 premiumBps_ = 1_000; + _setSharePremium(premiumBps_); + uint256 haircutShares_ = _depositViaAdapter(DEPOSIT_AMOUNT, 41, block.timestamp + 31 minutes); + + uint256 expected_ = fairShares_ * (10_000 - uint256(premiumBps_)) / 10_000; + assertLt(haircutShares_, fairShares_); + assertEq(haircutShares_, expected_); + } + + function test_reverts_minimumMintTooHigh() public { + uint256 deadline_ = block.timestamp + 30 minutes; + Delegation[] memory delegations_ = _createDelegationChain(address(MUSD), DEPOSIT_AMOUNT, 33); + IComplianceVedaTeller.ComplianceData memory compliance_ = + _complianceData(address(users.alice.deleGator), DEPOSIT_AMOUNT, deadline_, COMPLIANCE_SIGNER_KEY); + + vm.prank(address(users.bob.deleGator)); + vm.expectRevert(ITellerErrors.TellerWithMultiAssetSupport__MinimumMintNotMet.selector); + adapter.depositByDelegation(delegations_, type(uint256).max, compliance_); + } + + function test_reverts_emptyBatches() public { + ComplianceVedaAdapter.DepositParams[] memory deposits_ = new ComplianceVedaAdapter.DepositParams[](0); + ComplianceVedaAdapter.WithdrawParams[] memory withdrawals_ = new ComplianceVedaAdapter.WithdrawParams[](0); + + vm.expectRevert(ComplianceVedaAdapter.InvalidBatchLength.selector); + adapter.depositByDelegationBatch(deposits_); + vm.expectRevert(ComplianceVedaAdapter.InvalidBatchLength.selector); + adapter.withdrawByDelegationBatch(withdrawals_); + } + + function _setSharePremium(uint16 _sharePremium) internal { + (bool allowDeposits_, bool allowWithdraws_,) = ITellerConfiguration(address(TELLER)).assetData(address(MUSD)); + vm.prank(ITellerConfiguration(address(TELLER)).owner()); + ITellerConfiguration(address(TELLER)).updateAssetData(address(MUSD), allowDeposits_, allowWithdraws_, _sharePremium); + } + + function _configureForkRoles() internal { + address authorityOwner_ = ROLES_AUTHORITY.owner(); + address tellerOwner_ = ITellerConfiguration(address(TELLER)).owner(); + assertEq(authorityOwner_, tellerOwner_); + + vm.startPrank(authorityOwner_); + ROLES_AUTHORITY.setUserRole(complianceSigner, COMPLIANCE_SIGNER_ROLE, true); + ROLES_AUTHORITY.setUserRole(address(adapter), TRANSFER_ALLOWED_ROLE, true); + ROLES_AUTHORITY.setUserRole(address(adapter), TEST_ROUTER_ROLE, true); + ROLES_AUTHORITY.setRoleCapability(TEST_ROUTER_ROLE, address(TELLER), IComplianceVedaTeller.withdraw.selector, true); + ITellerConfiguration(address(TELLER)).setTransferRestrictions(TRANSFER_ALLOWED_ROLE, TEST_ROUTER_ROLE); + vm.stopPrank(); + } + + function _depositViaAdapter(uint256 _amount, uint256 _salt, uint256 _deadline) internal returns (uint256 shares_) { + uint256 sharesBefore_ = BORING_VAULT.balanceOf(address(users.alice.deleGator)); + Delegation[] memory delegations_ = _createDelegationChain(address(MUSD), _amount, _salt); + IComplianceVedaTeller.ComplianceData memory compliance_ = + _complianceData(address(users.alice.deleGator), _amount, _deadline, COMPLIANCE_SIGNER_KEY); + + vm.prank(address(users.bob.deleGator)); + adapter.depositByDelegation(delegations_, 0, compliance_); + shares_ = BORING_VAULT.balanceOf(address(users.alice.deleGator)) - sharesBefore_; + } + + function _buildDepositParams( + uint256 _amount, + uint256 _salt, + uint256 _deadline + ) + internal + view + returns (ComplianceVedaAdapter.DepositParams memory) + { + return ComplianceVedaAdapter.DepositParams({ + delegations: _createDelegationChain(address(MUSD), _amount, _salt), + minimumMint: 0, + compliance: _complianceData(address(users.alice.deleGator), _amount, _deadline, COMPLIANCE_SIGNER_KEY) + }); + } + + function _createDelegationChain( + address _token, + uint256 _amount, + uint256 _salt + ) + internal + view + returns (Delegation[] memory delegations_) + { + Delegation memory root_ = _createTransferDelegation(users.alice, _token, type(uint256).max, _salt); + Delegation memory leaf_ = _createAdapterRedelegation(EncoderLib._getDelegationHash(root_), _token, _amount, _salt); + + delegations_ = new Delegation[](2); + delegations_[0] = leaf_; + delegations_[1] = root_; + } + + function _createTransferDelegation( + TestUser memory _delegator, + 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(address(adapter)) }); + + Delegation memory delegation_ = Delegation({ + delegate: address(users.bob.deleGator), + delegator: address(_delegator.deleGator), + authority: ROOT_AUTHORITY, + caveats: caveats_, + salt: _salt, + signature: hex"" + }); + return signDelegation(_delegator, delegation_); + } + + function _createAdapterRedelegation( + 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(adapter), + delegator: address(users.bob.deleGator), + authority: _authority, + caveats: caveats_, + salt: _salt, + signature: hex"" + }); + return signDelegation(users.bob, delegation_); + } + + function _complianceData( + address _recipient, + uint256 _amount, + uint256 _deadline, + uint256 _signerKey + ) + internal + view + returns (IComplianceVedaTeller.ComplianceData memory) + { + return IComplianceVedaTeller.ComplianceData({ + deadline: _deadline, signature: _signCompliance(address(adapter), _recipient, _amount, _deadline, _signerKey) + }); + } + + function _signCompliance( + address _caller, + address _recipient, + uint256 _amount, + uint256 _deadline, + uint256 _signerKey + ) + internal + view + returns (bytes memory) + { + bytes32 messageHash_ = + keccak256(abi.encode(address(TELLER), block.chainid, _caller, _recipient, address(MUSD), _amount, _deadline)); + bytes32 ethSignedMessageHash_ = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", messageHash_)); + (uint8 v_, bytes32 r_, bytes32 s_) = vm.sign(_signerKey, ethSignedMessageHash_); + return abi.encodePacked(r_, s_, v_); + } +}