Address: 0x0000000000CC58810c33F3a0D78aA1Ed80FaDcD8
Balance (XRP): 0 XRP
Bytecode: 0x6080604052600436101561001257600080fd5b60003560e01c806313792a4a1461007757806323b3713e14610072578063313dade71461006d57806342de1418146100685780638c946df4146100635763f916f3b21461005e57600080fd5b6106a6565b610662565b6105b6565b6103ef565b610194565b3461014a5760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261014a5760043567ffffffffffffffff811161014a576101207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc823603011261014a5760243567ffffffffffffffff811161014a573660238201121561014a57806004013567ffffffffffffffff811161014a57366024828401011161014a576101469260246101369301906004016109ba565b6040519081529081906020820190565b0390f35b600080fd5b73ffffffffffffffffffffffffffffffffffffffff81160361014a57565b6044359061017a8261014f565b565b6064359061017a8261014f565b359061017a8261014f565b3461014a5760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261014a576004356101cf8161014f565b73ffffffffffffffffffffffffffffffffffffffff602435911660005260006020526040600020906000526020526020604060002054604051908152f35b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6040810190811067ffffffffffffffff82111761025857604052565b61020d565b60a0810190811067ffffffffffffffff82111761025857604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff82111761025857604052565b6040519061017a604083610279565b6040519061017a60e083610279565b67ffffffffffffffff81116102585760051b60200190565b8015150361014a57565b359061017a826102f0565b908160e091031261014a5790565b81601f8201121561014a5780359061032a826102d8565b926103386040519485610279565b82845260208085019360061b8301019181831161014a57602001925b828410610362575050505090565b60408483031261014a576020604091825161037c8161023c565b863581528287013583820152815201930192610354565b906020808351928381520192019060005b8181106103b15750505090565b82518051855260209081015181860152604090940193909201916001016103a4565b6040906103ec9392151581528160208201520190610393565b90565b3461014a5760a07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261014a5760043567ffffffffffffffff811161014a5760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc823603011261014a57604051906104698261023c565b80600401356104778161014f565b825260248101359067ffffffffffffffff821161014a57013660238201121561014a5760048101356104a8816102d8565b916104b66040519384610279565b8183526020600460a08286019402830101019036821161014a57602401915b81831061055057505050602082015260243567ffffffffffffffff811161014a57610504903690600401610305565b9061050d61016d565b61051561017c565b906084359367ffffffffffffffff851161014a5761053a610540953690600401610313565b93611075565b90610146604051928392836103d3565b60a08336031261014a57604051906105678261025d565b8335610572816102f0565b8252602084013590600482101561014a57826020928360a09501526040860135604082015260608601356060820152608086013560808201528152019201916104d5565b3461014a5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261014a5760043567ffffffffffffffff811161014a573660238201121561014a57806004013567ffffffffffffffff811161014a573660248260061b8401011161014a57602461063192016113be565b005b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b3461014a5760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261014a57602069fffffffffffffffffffe604051908152f35b3461014a5760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261014a57602060405173eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee8152f35b3560ff8116810361014a5790565b9035907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe18136030182121561014a570180359067ffffffffffffffff821161014a57602001918160051b3603831361014a57565b604051906060820182811067ffffffffffffffff821117610258576040526000604083828152606060208201520152565b90610790826102d8565b61079d6040519182610279565b8281527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe06107cb82946102d8565b019060005b8281106107dc57505050565b6020906107e7610755565b828285010152016107d0565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b901561085b578035907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff218136030182121561014a570190565b6107f3565b919081101561085b5760051b810135907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff218136030182121561014a570190565b356103ec816102f0565b356103ec8161014f565b805182101561085b5760209160051b010190565b604051906108d58261023c565b60006020838281520152565b604051906108f0602083610279565b600080835282815b82811061090457505050565b60209061090f6108c8565b828285010152016108f8565b90610925826102d8565b6109326040519182610279565b8281527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe061096082946102d8565b019060005b82811061097157505050565b60209061097c6108c8565b82828501015201610965565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81146109b55760010190565b610633565b919060ff6109c7846106f3565b16610ee257606083013569fffffffffffffffffffe8111610eb4575060408301916109f28385610701565b905015610e8a57610a06918495939561168f565b91610a1b610a148583610701565b9050610786565b9060009460808501955b610a2f8284610701565b9050811015610dd157610a4c81610a468486610701565b90610860565b610a58608082016108a0565b610da75733610a82610a69836108aa565b73ffffffffffffffffffffffffffffffffffffffff1690565b14610d7d57600260c082013514610d5357610a9e8289516108b4565b51805115610ae85760019291816060610ad16020610ae295015173ffffffffffffffffffffffffffffffffffffffff1690565b9101519060408b0151923390611fbf565b01610a25565b91959392969050610af7610755565b96602083019460005b8751811015610d3257610b34610a69610b19838b6108b4565b515173ffffffffffffffffffffffffffffffffffffffff1690565b15610bfb57610b46610b19828a6108b4565b73ffffffffffffffffffffffffffffffffffffffff610b7c610a698a5173ffffffffffffffffffffffffffffffffffffffff1690565b911614610b8b57600101610b00565b9097929850610be4610bf59295968a600196610bdb60406060610bcf610bb28f8a906108b4565b51965b5173ffffffffffffffffffffffffffffffffffffffff1690565b94015192015160ff1690565b9133888c611a40565b610bee82896108b4565b52866108b4565b50610ae2565b90979298610be4908a600196610bdb60406060610bcf8d610c53610bf59b9e9f610c39905173ffffffffffffffffffffffffffffffffffffffff1690565b73ffffffffffffffffffffffffffffffffffffffff168952565b610c5b6108e1565b6020890152610d29610c81825173ffffffffffffffffffffffffffffffffffffffff1690565b855173ffffffffffffffffffffffffffffffffffffffff9091166020820190815273eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee604083015290610cf281606081015b037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101835282610279565b5190203373ffffffffffffffffffffffffffffffffffffffff16600052600060205260406000209060005260205260406000205490565b84890152610bb5565b90979298610be4908a600196610bdb60406060610bcf610bf5999c9d610bb5565b7fc8103a190000000000000000000000000000000000000000000000000000000060005260046000fd5b7f540733ea0000000000000000000000000000000000000000000000000000000060005260046000fd5b7faa25f2ad0000000000000000000000000000000000000000000000000000000060005260046000fd5b50919093929450610de28151610786565b9260009260005b8351811015610e64576020610dfe82866108b4565b5101515115801590610e4d575b610e18575b600101610de9565b93610e45600191610e2987876108b4565b51610e34828a6108b4565b52610e3f81896108b4565b50610988565b949050610e10565b506040610e5a82866108b4565b5101511515610e0b565b50610e819250610e7b9195610e8695948552610701565b90610822565b612255565b5190565b7f542f0af50000000000000000000000000000000000000000000000000000000060005260046000fd5b7fdce5eceb0000000000000000000000000000000000000000000000000000000060005260045260245b6000fd5b7f720521200000000000000000000000000000000000000000000000000000000060005260046000fd5b90600182018092116109b557565b919082018092116109b557565b9035907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe18136030182121561014a570180359067ffffffffffffffff821161014a5760200191813603831361014a57565b60041115610f8257565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b9392919073ffffffffffffffffffffffffffffffffffffffff16845260606020850152602060a085019173ffffffffffffffffffffffffffffffffffffffff81511660608701520151604060808601528051809252602060c0860191019160005b81811061102457505060409150930152565b909183518051151582526020810151906004821015610f825782608060a092602094856001970152604081015160408401526060810151606084015201516080820152019401910192919092611012565b949091929394611099815173ffffffffffffffffffffffffffffffffffffffff1690565b73ffffffffffffffffffffffffffffffffffffffff6110ba610a69866108aa565b9116036113a3578551926110dd6110d8602084019586515190610f1a565b61091b565b9460005b885181101561111457806110f76001928b6108b4565b51611102828a6108b4565b5261110d81896108b4565b50016110e1565b50909294919395875160005b885180518210156113925781611135916108b4565b516111596111466040890189610f27565b5060608301519081013591602090910190565b506080820151169061116b8151151590565b61123e575b60208101805161117f81610f78565b61118881610f78565b6111ae575060400151036111a0576001905b01611120565b506000989750505050505050565b600381516111bb81610f78565b6111c481610f78565b036111db575060400151106111a05760019061119a565b600181516111e881610f78565b6111f181610f78565b03611208575060400151146111a05760019061119a565b6002905161121581610f78565b61121e81610f78565b1461122e575b505060019061119a565b60400151116111a0573880611224565b9691989795929093998a610cc66112618c6040519283918c602084019687610fb1565b519020966000936112706108c8565b9c60005b875181101561137a5761128781896108b4565b51511561132d578a611299828a6108b4565b5151146112a857600101611274565b6020949e50879a9198939c9d96506112c6906112db939c96986108b4565b519284840151915b82156112e6575b50610f1a565b918291015290611170565b6113259192506113168b73ffffffffffffffffffffffffffffffffffffffff166000526000602052604060002090565b90600052602052604060002090565b5490386112d5565b6020949e506112db929b95509761137489899c99959e9f98939a61136e906113536102ba565b978c895260008a8a01528861136883836108b4565b526108b4565b50610f0c565b976112ce565b5096946020939d929b9c956112db929b95979a6112ce565b505083525060019650909450505050565b509250505060009190565b919081101561085b5760061b0190565b60005b8281106113cd57505050565b60206113da8285856113ae565b013561141f6113ea8386866113ae565b353373ffffffffffffffffffffffffffffffffffffffff16600052600060205260406000209060005260205260406000205490565b1161149957807f42063b8d32ac0a88f94053143bb30a789b3d5dfabb27d6c8a54e23d4fef30851606061145560019487876113ae565b3560206114638589896113ae565b0135336000526000602052604060002082600052602052806040600020556040519133835260208301526040820152a1016113c1565b7e2a700a0000000000000000000000000000000000000000000000000000000060005260046000fd5b604051906114cf8261025d565b6060608083600081526000602082015282604082015282808201520152565b9093929384831161014a57841161014a578101920390565b604051906115138261023c565b6000602083606081520152565b6040519060c0820182811067ffffffffffffffff8211176102585760405281600081526000602082015260006040820152600060608201526060608082015260a0611569611506565b910152565b90611578826102d8565b6115856040519182610279565b8281527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe06115b382946102d8565b019060005b8281106115c457505050565b6020906115cf611520565b828285010152016115b8565b6040513d6000823e3d90fd5b604051906080820182811067ffffffffffffffff82111761025857604052816000815260006020820152600060408201526060611569611520565b9061162c826102d8565b6116396040519182610279565b8281527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe061166782946102d8565b019060005b82811061167857505050565b6020906116836115e7565b8282850101520161166c565b9162ffffff9261169d6114c2565b50600091833560e81c60039516906116d46116cc6116c66116be858a610f1a565b89858a6114ee565b90612596565b939097610f1a565b602087019473ffffffffffffffffffffffffffffffffffffffff61170c875173ffffffffffffffffffffffffffffffffffffffff1690565b16156119ea5750600181019392919086013560f81c9061172b8261156e565b9560005b8884821061191e575050505015159081611915575b506118eb576117566040820182610701565b93905061176284611622565b94608087019586526000935b85851061177f575050505050505090565b61179b61178a6115e7565b918381013560f81c91600190910190565b608082161515835295908251156118cb57607f1684518110156118a15760006117da61180f986117cd602094896108b4565b5160608701525b86612b37565b9a9092916117e8878c612cbe565b92604051948594859094939260ff6060936080840197845216602083015260408201520152565b838052039060015afa1561189c5761184a610a69600051610bb56020860191829073ffffffffffffffffffffffffffffffffffffffff169052565b1561186b57611863816001938a519061136883836108b4565b50019361176e565b7fc1e84ed600000000000000000000000000000000000000000000000000000000600052610ede6024906000600452565b6115db565b7fbd8ba84d0000000000000000000000000000000000000000000000000000000060005260046000fd5b60006117da61180f986118e6602094604088019060ff169052565b6117d4565b7feb6252040000000000000000000000000000000000000000000000000000000060005260046000fd5b90501538611744565b906020600061193d6119539a8761194696611937611520565b50612a81565b8d959195612b37565b9c9092916117e888612b7a565b838052039060015afa1561189c5760005173ffffffffffffffffffffffffffffffffffffffff61199a610a69865173ffffffffffffffffffffffffffffffffffffffff1690565b9116036119c0576001916119ae828b6108b4565b526119b9818a6108b4565b500161172f565b7f9e5c658b0000000000000000000000000000000000000000000000000000000060005260046000fd5b807f9e5c658b0000000000000000000000000000000000000000000000000000000060049252fd5b60405190611a1f8261025d565b60606080836000815260006020820152600060408201526000838201520152565b939594909192611a4e610755565b50611a57611a12565b9660005b8151811015611d2257611a71610b1982846108b4565b73ffffffffffffffffffffffffffffffffffffffff808516911614611a9857600101611a5b565b90611aa992939495969798506108b4565b51945b73ffffffffffffffffffffffffffffffffffffffff611adf875173ffffffffffffffffffffffffffffffffffffffff1690565b1615611cde57602086015180151580611cd4575b611ca75750606086015167ffffffffffffffff1680151580611c94575b611c5d575083610a46826040611b27940190610701565b92611b34608085016108a0565b610da757611b41846108aa565b73ffffffffffffffffffffffffffffffffffffffff163014611c4857506080850160ff815151931692831015611c1e57611b8f92611b7f91516108b4565b5190602087019484865193611075565b9015611bf45760209252013580611bdd575b5060408083015191015110611bb35790565b7faa7feadc0000000000000000000000000000000000000000000000000000000060005260046000fd5b611bec60408401918251610f1a565b905238611ba1565b7f868a64de0000000000000000000000000000000000000000000000000000000060005260046000fd5b7f3f904dc00000000000000000000000000000000000000000000000000000000060005260046000fd5b9350505091506114995760200135611bb35790565b7fb1bbfdd50000000000000000000000000000000000000000000000000000000060005267ffffffffffffffff1660045260246000fd5b5067ffffffffffffffff81164211611b10565b7f331003b30000000000000000000000000000000000000000000000000000000060005260045260246000fd5b5046811415611af3565b7fc1e84ed60000000000000000000000000000000000000000000000000000000060005273ffffffffffffffffffffffffffffffffffffffff821660045260246000fd5b505090919293959495611aac565b9081602091031261014a575190565b60005b838110611d525750506000910152565b8181015183820152602001611d42565b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f602093611d9e81518092818752878088019101611d3f565b0116010190565b601f82602094937fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0938186528686013760008582860101520116010190565b9073ffffffffffffffffffffffffffffffffffffffff8235611e058161014f565b1681526020820135602082015260408201357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe18336030181121561014a57820160208135910167ffffffffffffffff821161014a57813603811361014a57611e7d60c09291839260e0604087015260e0860191611da5565b9360608101356060850152611ea0611e97608083016102fa565b15156080860152565b611eb8611eaf60a083016102fa565b151560a0860152565b013591015290565b73ffffffffffffffffffffffffffffffffffffffff6103ec94921681526060602082015273ffffffffffffffffffffffffffffffffffffffff83511660608201527fffffffff000000000000000000000000000000000000000000000000000000006020840151166080820152604083015160a0820152606083015160c082015260a0611f5d608085015160c060e0850152610120840190611d62565b930151927fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa082820301610100830152602067ffffffffffffffff81611fab8751604086526040860190611d62565b960151169101526040818403910152611de4565b92939091611fe4610a69865173ffffffffffffffffffffffffffffffffffffffff1690565b73ffffffffffffffffffffffffffffffffffffffff82160361219c5761200c608085016108a0565b610da75761201a8282612e23565b61215957506120319061202c846108aa565b612e23565b61210b576020820135611bb357602082612053610a69610a69612089966108aa565b60405180809681947f9d043a66000000000000000000000000000000000000000000000000000000008352898860048501611ec0565b03915afa91821561189c576000926120d8575b506120a79192612eee565b036120ae57565b7f0881ad5d0000000000000000000000000000000000000000000000000000000060005260046000fd5b6120a792506120fe9060203d602011612104575b6120f68183610279565b810190611d30565b9161209c565b503d6120ec565b610ede612117836108aa565b7fd33f19e70000000000000000000000000000000000000000000000000000000060005273ffffffffffffffffffffffffffffffffffffffff16600452602490565b7fd33f19e70000000000000000000000000000000000000000000000000000000060005273ffffffffffffffffffffffffffffffffffffffff1660045260246000fd5b7fc1e84ed60000000000000000000000000000000000000000000000000000000060005273ffffffffffffffffffffffffffffffffffffffff1660045260246000fd5b9060206103ec928181520190610393565b92919267ffffffffffffffff82116102585760405191612238601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01660200184610279565b82948184528183011161014a578281602093846000960137010152565b8151909190156124b257612268826108aa565b73ffffffffffffffffffffffffffffffffffffffff1630148015906124a3575b8015612491575b61149957600092835b82518510156122ed576122bb9060206122b187866108b4565b5101515190610f1a565b9360406122c882856108b4565b5101516122d9575b60010193612298565b936122e5600191610988565b9490506122d0565b6122f99193945061091b565b60009081935b83518510156124265760005b602061231787876108b4565b51015151811015612364578061235d61234060019360206123388b8b6108b4565b5101516108b4565b519561234b81610988565b9661235682886108b4565b52856108b4565b500161230b565b509493600190604061237682876108b4565b510151612386575b0193946122ff565b612420612396610b1983886108b4565b6040516123e281610cc6602082019485602073eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee9193929373ffffffffffffffffffffffffffffffffffffffff60408201951681520152565b5190209460406123f284896108b4565b5101516123fd6102ba565b968752602087015261240e81610988565b9561241982876108b4565b52846108b4565b5061237e565b949350612483925061247c915060405161246e81610cc660208201947f42de1418000000000000000000000000000000000000000000000000000000008652602483016121df565b519020926040810190610f27565b36916121f0565b602081519101200361149957565b5061249e60a083016108a0565b61228f565b50600160c08301351415612288565b506124bc906108aa565b73ffffffffffffffffffffffffffffffffffffffff16301461149957565b906124e4826102d8565b6124f16040519182610279565b8281527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe061251f82946102d8565b019060005b82811061253057505050565b60209061253b611a12565b82828501015201612524565b90612551826102d8565b61255e6040519182610279565b8281527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe061258c82946102d8565b0190602036910137565b9161259f6114c2565b600093849081906125b2605e87046124da565b91606085019283525b8681106125cb5750505152929150565b8181013560fc81901c97916001019060f81c8189156129d25750600189146129a057600289146127fb57600389146126ca575060048814612635577f0ad9979000000000000000000000000000000000000000000000000000000000600052600488905260246000fd5b90919293949596506020860173ffffffffffffffffffffffffffffffffffffffff612674825173ffffffffffffffffffffffffffffffffffffffff1690565b166119c0578382013560601c815260149091019061269a9061269590610bb5565b61311f565b865180156126c457906126b591600052602052604060002090565b86525b959493929190956125bb565b506126b5565b989091929394959697506118eb57600f60019816600f81146127ea575b81906126f281612547565b604089019081526000805b83821061274557505050506127178261271d9285876114ee565b906130ab565b8651801561273f579061273891600052602052604060002090565b86526126b8565b50612738565b61277c958881013560601c9060140196906127618486516108b4565b9073ffffffffffffffffffffffffffffffffffffffff169052565b73ffffffffffffffffffffffffffffffffffffffff806127a0610bb58587516108b4565b92169116106127c0578b6127b8610bb58385516108b4565b9101906126fd565b7f83f928070000000000000000000000000000000000000000000000000000000060005260046000fd5b50600281019083013560f01c6126e7565b61285e96985061285661286792600f6116c6939c96979a95989c169087929190928160031b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6001821b019185013590610100031c16920190565b979088610f1a565b809787876114ee565b93909593612989575b6020860173ffffffffffffffffffffffffffffffffffffffff6128a7825173ffffffffffffffffffffffffffffffffffffffff1690565b1661291f575b5060005b606087015180518210156128ee57906128e76128cf826001946108b4565b519b8a516128dc82610988565b9d61136883836108b4565b50016128b1565b50509290969593919794865180151560001461291857612738915190600052602052604060002090565b5051612738565b60208901612944610a69825173ffffffffffffffffffffffffffffffffffffffff1690565b6119c057612969612983925173ffffffffffffffffffffffffffffffffffffffff1690565b73ffffffffffffffffffffffffffffffffffffffff169052565b386128ad565b916118eb5760019160408601516040890152612870565b50855196975094959394929391929091602082019184013590801561273f579061273891600052602052604060002090565b929394959697985050506129e4611a12565b8184018035606090811c8352601482013560208401526034820135604084015260549091013560c01c9082015294612a20605c83018486612fa0565b8093916080890152612a339185876114ee565b612a3c91613056565b8751612a759291908015612a7b5790612a5d91600052602052604060002090565b88528551612a6a82610988565b9761136883836108b4565b506126b8565b50612a5d565b612b2c91939293612b2662ffffff612b016018612a9c611520565b987fffffffff000000000000000000000000000000000000000000000000000000006014888301803560601c8d5201351660208b01520160208682013591019060408a015260208682013591019060608a01528560039092919283013560e81c920190565b911690612b1c61247c612b148484610f1a565b8387896114ee565b6080890152610f1a565b91613183565b929060a08201529190565b8101916040602084359401359201601b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff84169360ff1c0160ff81116109b55791565b73ffffffffffffffffffffffffffffffffffffffff81511690612c9d7fffffffff00000000000000000000000000000000000000000000000000000000602083015116610cc67fffffff0000000000000000000000000000000000000000000000000000000000604085015194606081015190608081015191612c8c6008602360a062ffffff87511695015180517fffffffffffffffff00000000000000000000000000000000000000000000000060208251930151916040519a8b9460e81b166020850152612c538151809260208888019101611d3f565b83019160c01b168382015203017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe8810187520185610279565b604051978896602088019a8b6131ce565b51902090565b9080601f8301121561014a578160206103ec933591016121f0565b6020810135612ccc816102f0565b15612db3576000915b6060820135612cf182610a466080860135956040810190610701565b60e08136031261014a57612d036102c9565b92612d0d82610189565b84526020820135602085015260408201359167ffffffffffffffff831161014a57612c9d9460c0612d8592612d48610cc69636908301612ca3565b604084015260608101356060840152612d63608082016102fa565b6080840152612d7460a082016102fa565b60a0840152013560c0820152613286565b90604051958694602086019889939160a0959391855260208501526040840152606083015260808201520190565b4691612cd5565b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82019182136001166109b557565b90600182019160006001841291129080158216911516176109b557565b919091600083820193841291129080158216911516176109b557565b600082517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81019081136001166109b557905b81811315612e675750505050600090565b80820360008212838212811690848313901516176109b557612e8d906002900582612e07565b90612e9b610bb583876108b4565b73ffffffffffffffffffffffffffffffffffffffff8581169116808203612ec85750505050505050600190565b1015612edd5750612ed890612dea565b612e56565b9150612ee890612dba565b90612e56565b9060405160208101907f616363657074496d706c69636974526571756573740000000000000000000000825260158152612f29603582610279565b5190209160406060820151910151907fffffffffffffffffffffffffffffffffffffffff000000000000000000000000604051936020850195865260601b1660408401526054830152607482015260748152612c9d609482610279565b60405190612f938261023c565b6060602083600081520152565b60018301939281013560f81c612fb5816102d8565b92612fc36040519485610279565b8184527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0612ff0836102d8565b0160005b8181106130375750506000925b82841061301057505050509190565b9091929561302160019184846133d8565b979061302d82886108b4565b5201929190613001565b60209061304695939495612f86565b8282890101520193929193612ff4565b90612c9d60016020926040519381859282840197600089526021850137820101828101600081525003017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101835282610279565b90612c9d600160209260405193818592828401977f030000000000000000000000000000000000000000000000000000000000000089526021850137820101828101600081525003017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101835282610279565b6040517fffffffffffffffffffffffffffffffffffffffff00000000000000000000000060208201927f0400000000000000000000000000000000000000000000000000000000000000845260601b16602182015260158152612c9d603582610279565b929161318d611506565b9060038101908581013560e81c019460038601918281116109b55761247c6003936131bb92600b97856114ee565b83528501013560c01c6020820152920190565b9390927fffffffff00000000000000000000000000000000000000000000000000000000605b989796937fffffffffffffffffffffffffffffffffffffffff0000000000000000000000007fffffff00000000000000000000000000000000000000000000000000000000009660601b1687521660148601526018850152603884015260e81b16605882015261326d8251809360208785019101611d3f565b016132818251809360208685019101611d3f565b010190565b73ffffffffffffffffffffffffffffffffffffffff81511690602081015190604081015160208151910120906060810151608082015115159060c060a08401511515930151936040519560208701977f0603985259a953da1f65a522f589c17bd1d0117ec1d3abb7c0788aef251ef437895260408801526060870152608086015260a085015260c084015260e08301526101008201526101008152612c9d61012082610279565b90613337826102d8565b6133446040519182610279565b8281527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe061337282946102d8565b019060005b82811061338357505050565b6020906040516133928161025d565b6000815260008382015260006040820152600060608201526000608082015282828501015201613377565b60ff166004811015610f825790565b6004821015610f825752565b60ff9392915061342d61341d6134006133ef612f86565b948481013560601c91601490910190565b73ffffffffffffffffffffffffffffffffffffffff909116855290565b8281013560f81c91600190910190565b94166134388161332d565b602084019081526000925b82841061345257505050509190565b9091929561350b6134ed6134cf6134a56134c28b60206134bb6134b36134ae8c61348a60019c8e60019092919283013560f81c920190565b98909161349d888f8516151592516108b4565b519015159052565b60011c607f1690565b6133bd565b928b516108b4565b51016133cc565b8581013591602090910190565b9060406134dd8c89516108b4565b5101528481013591602090910190565b9060606134fb8b88516108b4565b5101528381013591602090910190565b9790608061351a8387516108b4565b5101520192919061344356fea2646970667358221220f8f235e0fd626f6b9c1b58a4c9cb03206a71f69c588f120d6f7dff677aa2561364736f6c634300081c0033
SessionErrors.sol
// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.27; /// @title SessionErrors /// @author Michael Standen /// @notice Errors for the session manager library SessionErrors { /// @notice Invalid session signer error InvalidSessionSigner(address invalidSigner); /// @notice Invalid chainId error InvalidChainId(uint256 invalidChainId); /// @notice Invalid self call error InvalidSelfCall(); /// @notice Invalid delegate call error InvalidDelegateCall(); /// @notice Invalid call behavior error InvalidBehavior(); /// @notice Invalid value error InvalidValue(); /// @notice Invalid node type in session configuration error InvalidNodeType(uint256 flag); /// @notice Error thrown when the payload kind is invalid error InvalidPayloadKind(); /// @notice Error thrown when the calls length is invalid error InvalidCallsLength(); /// @notice Error thrown when the payload space is invalid error InvalidSpace(uint256 space); // ---- Explicit session errors ---- /// @notice Missing permission for explicit session error MissingPermission(); /// @notice Invalid permission for explicit session error InvalidPermission(); /// @notice Session expired error SessionExpired(uint256 deadline); /// @notice Invalid limit usage increment error InvalidLimitUsageIncrement(); // ---- Implicit session errors ---- /// @notice Blacklisted address error BlacklistedAddress(address target); /// @notice Invalid implicit result error InvalidImplicitResult(); /// @notice Invalid identity signer error InvalidIdentitySigner(); /// @notice Invalid blacklist error InvalidBlacklist(); /// @notice Invalid attestation error InvalidAttestation(); /// @notice The blacklist was not sorted error InvalidBlacklistUnsorted(); }
SessionManager.sol
// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.27; import { Payload } from "../../modules/Payload.sol"; import { ISapient } from "../../modules/interfaces/ISapient.sol"; import { LibBytes } from "../../utils/LibBytes.sol"; import { SessionErrors } from "./SessionErrors.sol"; import { SessionSig } from "./SessionSig.sol"; import { ExplicitSessionManager, IExplicitSessionManager, SessionPermissions, SessionUsageLimits } from "./explicit/ExplicitSessionManager.sol"; import { Permission, UsageLimit } from "./explicit/Permission.sol"; import { ImplicitSessionManager } from "./implicit/ImplicitSessionManager.sol"; using LibBytes for bytes; /// @title SessionManager /// @author Michael Standen, Agustin Aguilar /// @notice Manager for smart sessions contract SessionManager is ISapient, ImplicitSessionManager, ExplicitSessionManager { /// @notice Maximum nonce space allowed for sessions use. /// @dev This excludes half the possible bits (uint160 vs uint80) uint256 public constant MAX_SPACE = type(uint80).max - 1; /// @inheritdoc ISapient function recoverSapientSignature( Payload.Decoded calldata payload, bytes calldata encodedSignature ) external view returns (bytes32) { // Validate outer Payload if (payload.kind != Payload.KIND_TRANSACTIONS) { revert SessionErrors.InvalidPayloadKind(); } if (payload.space > MAX_SPACE) { revert SessionErrors.InvalidSpace(payload.space); } if (payload.calls.length == 0) { revert SessionErrors.InvalidCallsLength(); } // Decode signature SessionSig.DecodedSignature memory sig = SessionSig.recoverSignature(payload, encodedSignature); address wallet = msg.sender; // Initialize session usage limits for explicit session SessionUsageLimits[] memory sessionUsageLimits = new SessionUsageLimits[](payload.calls.length); for (uint256 i = 0; i < payload.calls.length; i++) { Payload.Call calldata call = payload.calls[i]; // Ban delegate calls if (call.delegateCall) { revert SessionErrors.InvalidDelegateCall(); } // Ban self calls to the wallet if (call.to == wallet) { revert SessionErrors.InvalidSelfCall(); } // Check if this call could cause usage limits to be skipped if (call.behaviorOnError == Payload.BEHAVIOR_ABORT_ON_ERROR) { revert SessionErrors.InvalidBehavior(); } // Validate call signature SessionSig.CallSignature memory callSignature = sig.callSignatures[i]; if (callSignature.isImplicit) { // Validate implicit calls _validateImplicitCall( call, wallet, callSignature.sessionSigner, callSignature.attestation, sig.implicitBlacklist ); } else { // Find the session usage limits for the current call SessionUsageLimits memory limits; uint256 limitsIdx; for (limitsIdx = 0; limitsIdx < sessionUsageLimits.length; limitsIdx++) { if (sessionUsageLimits[limitsIdx].signer == address(0)) { // Initialize new session usage limits limits.signer = callSignature.sessionSigner; limits.limits = new UsageLimit[](0); bytes32 usageHash = keccak256(abi.encode(callSignature.sessionSigner, VALUE_TRACKING_ADDRESS)); limits.totalValueUsed = getLimitUsage(wallet, usageHash); break; } if (sessionUsageLimits[limitsIdx].signer == callSignature.sessionSigner) { limits = sessionUsageLimits[limitsIdx]; break; } } // Validate explicit calls. Obtain usage limits for increment validation. (limits) = _validateExplicitCall( payload, i, wallet, callSignature.sessionSigner, sig.sessionPermissions, callSignature.sessionPermission, limits ); sessionUsageLimits[limitsIdx] = limits; } } { // Reduce the size of the sessionUsageLimits array SessionUsageLimits[] memory actualSessionUsageLimits = new SessionUsageLimits[](sessionUsageLimits.length); uint256 actualSize; for (uint256 i = 0; i < sessionUsageLimits.length; i++) { if (sessionUsageLimits[i].limits.length > 0 || sessionUsageLimits[i].totalValueUsed > 0) { actualSessionUsageLimits[actualSize] = sessionUsageLimits[i]; actualSize++; } } assembly { mstore(actualSessionUsageLimits, actualSize) } // Bulk validate the updated usage limits Payload.Call calldata firstCall = payload.calls[0]; _validateLimitUsageIncrement(firstCall, actualSessionUsageLimits); } // Return the image hash return sig.imageHash; } }
SessionSig.sol
// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.27; import { Payload } from "../../modules/Payload.sol"; import { LibBytes } from "../../utils/LibBytes.sol"; import { LibOptim } from "../../utils/LibOptim.sol"; import { SessionErrors } from "./SessionErrors.sol"; import { SessionPermissions } from "./explicit/IExplicitSessionManager.sol"; import { LibPermission, Permission } from "./explicit/Permission.sol"; import { Attestation, LibAttestation } from "./implicit/Attestation.sol"; using LibBytes for bytes; using LibAttestation for Attestation; /// @title SessionSig /// @author Michael Standen, Agustin Aguilar /// @notice Library for session signatures library SessionSig { uint256 internal constant FLAG_PERMISSIONS = 0; uint256 internal constant FLAG_NODE = 1; uint256 internal constant FLAG_BRANCH = 2; uint256 internal constant FLAG_BLACKLIST = 3; uint256 internal constant FLAG_IDENTITY_SIGNER = 4; uint256 internal constant MIN_ENCODED_PERMISSION_SIZE = 94; /// @notice Call signature for a specific session /// @param isImplicit If the call is implicit /// @param sessionSigner Address of the session signer /// @param sessionPermission Session permission for explicit calls /// @param attestation Attestation for implicit calls struct CallSignature { bool isImplicit; address sessionSigner; uint8 sessionPermission; Attestation attestation; } /// @notice Decoded signature for a specific session /// @param imageHash Derived configuration image hash /// @param identitySigner Identity signer address /// @param implicitBlacklist Implicit blacklist addresses /// @param sessionPermissions Session permissions for each explicit signer /// @param callSignatures Call signatures for each call in the payload struct DecodedSignature { bytes32 imageHash; address identitySigner; address[] implicitBlacklist; SessionPermissions[] sessionPermissions; CallSignature[] callSignatures; } /// @notice Recovers the decoded signature from the encodedSignature bytes. /// @dev The encoded layout is conceptually separated into three parts: /// 1) Session Configuration /// 2) A reusable list of Attestations + their identity signatures (if any implicit calls exist) /// 3) Call Signatures (one per call in the payload) /// /// High-level layout: /// - session_configuration: [uint24 size, <Session Configuration encoded>] /// - attestation_list: [uint8 attestationCount, (Attestation + identitySig) * attestationCount] /// (new section to allow reusing the same Attestation across multiple calls) /// - call_signatures: [<CallSignature encoded>] - Size is payload.calls.length /// - call_signature: [uint8 call_flags, <session_signature>] /// - call_flags: [bool is_implicit (MSB), 7 bits encoded] /// - if call_flags.is_implicit.MSB == 1: /// - attestation_index: [uint8 index into the attestation list (7 bits of the call_flags)] /// - session_signature: [r, s, v (compact)] /// - if call_flags.is_implicit.MSB == 0: /// - session_permission: [uint8 (7 bits of the call_flags)] /// - session_signature: [r, s, v (compact)] function recoverSignature( Payload.Decoded calldata payload, bytes calldata encodedSignature ) internal view returns (DecodedSignature memory sig) { uint256 pointer = 0; bool hasBlacklistInConfig; // ----- Session Configuration ----- { // First read the length of the session configuration bytes (uint24) uint256 dataSize; (dataSize, pointer) = encodedSignature.readUint24(pointer); // Recover the session configuration (sig, hasBlacklistInConfig) = recoverConfiguration(encodedSignature[pointer:pointer + dataSize]); pointer += dataSize; // Identity signer must be set if (sig.identitySigner == address(0)) { revert SessionErrors.InvalidIdentitySigner(); } } // ----- Attestations for implicit calls ----- Attestation[] memory attestationList; { uint8 attestationCount; (attestationCount, pointer) = encodedSignature.readUint8(pointer); attestationList = new Attestation[](attestationCount); // Parse each attestation and its identity signature, store in memory for (uint256 i = 0; i < attestationCount; i++) { Attestation memory att; (att, pointer) = LibAttestation.fromPacked(encodedSignature, pointer); // Read the identity signature that approves this attestation { bytes32 r; bytes32 s; uint8 v; (r, s, v, pointer) = encodedSignature.readRSVCompact(pointer); // Recover the identity signer from the attestation identity signature bytes32 attestationHash = att.toHash(); address recoveredIdentitySigner = ecrecover(attestationHash, v, r, s); if (recoveredIdentitySigner != sig.identitySigner) { revert SessionErrors.InvalidIdentitySigner(); } } attestationList[i] = att; } // If we have any implicit calls, we must have a blacklist in the configuration if (attestationCount > 0 && !hasBlacklistInConfig) { revert SessionErrors.InvalidBlacklist(); } } // ----- Call Signatures ----- { uint256 callsCount = payload.calls.length; sig.callSignatures = new CallSignature[](callsCount); for (uint256 i = 0; i < callsCount; i++) { CallSignature memory callSignature; // Determine signature type { uint8 flag; (flag, pointer) = encodedSignature.readUint8(pointer); callSignature.isImplicit = (flag & 0x80) != 0; if (callSignature.isImplicit) { // Read attestation index from the call_flags uint8 attestationIndex = uint8(flag & 0x7f); // Check if the attestation index is out of range if (attestationIndex >= attestationList.length) { revert SessionErrors.InvalidAttestation(); } // Set the attestation callSignature.attestation = attestationList[attestationIndex]; } else { // Session permission index is the entire byte, top bit is 0 => no conflict callSignature.sessionPermission = flag; } } // Read session signature and recover the signer { bytes32 r; bytes32 s; uint8 v; (r, s, v, pointer) = encodedSignature.readRSVCompact(pointer); bytes32 callHash = hashCallWithReplayProtection(payload, i); callSignature.sessionSigner = ecrecover(callHash, v, r, s); if (callSignature.sessionSigner == address(0)) { revert SessionErrors.InvalidSessionSigner(address(0)); } } sig.callSignatures[i] = callSignature; } } return sig; } /// @notice Recovers the session configuration from the encoded data. /// The encoded layout is: /// - permissions_count: [uint8] /// - permissions_tree_element: [flag, <data>] /// - flag: [uint8] /// - data: [data] /// - if flag == FLAG_PERMISSIONS: [SessionPermissions encoded] /// - if flag == FLAG_NODE: [bytes32 node] /// - if flag == FLAG_BRANCH: [uint256 size, nested encoding...] /// - if flag == FLAG_BLACKLIST: [uint24 blacklist_count, blacklist_addresses...] /// - if flag == FLAG_IDENTITY_SIGNER: [address identity_signer] /// @dev A valid configuration must have exactly one identity signer and at most one blacklist. function recoverConfiguration( bytes calldata encoded ) internal pure returns (DecodedSignature memory sig, bool hasBlacklist) { uint256 pointer; uint256 permissionsCount; // Guess maximum permissions size by bytes length { uint256 maxPermissionsSize = encoded.length / MIN_ENCODED_PERMISSION_SIZE; sig.sessionPermissions = new SessionPermissions[](maxPermissionsSize); } while (pointer < encoded.length) { // First byte is the flag (top 4 bits) and additional data (bottom 4 bits) uint256 firstByte; (firstByte, pointer) = encoded.readUint8(pointer); // The top 4 bits are the flag uint256 flag = (firstByte & 0xf0) >> 4; // Permissions configuration (0x00) if (flag == FLAG_PERMISSIONS) { SessionPermissions memory nodePermissions; uint256 pointerStart = pointer; // Read signer (nodePermissions.signer, pointer) = encoded.readAddress(pointer); // Read chainId (nodePermissions.chainId, pointer) = encoded.readUint256(pointer); // Read value limit (nodePermissions.valueLimit, pointer) = encoded.readUint256(pointer); // Read deadline (nodePermissions.deadline, pointer) = encoded.readUint64(pointer); // Read permissions array (nodePermissions.permissions, pointer) = _decodePermissions(encoded, pointer); // Update root { bytes32 permissionHash = _leafHashForPermissions(encoded[pointerStart:pointer]); sig.imageHash = sig.imageHash != bytes32(0) ? LibOptim.fkeccak256(sig.imageHash, permissionHash) : permissionHash; } // Push node permissions to the permissions array sig.sessionPermissions[permissionsCount++] = nodePermissions; continue; } // Node (0x01) if (flag == FLAG_NODE) { // Read pre-hashed node bytes32 node; (node, pointer) = encoded.readBytes32(pointer); // Update root sig.imageHash = sig.imageHash != bytes32(0) ? LibOptim.fkeccak256(sig.imageHash, node) : node; continue; } // Branch (0x02) if (flag == FLAG_BRANCH) { // Read branch size uint256 size; { uint256 sizeSize = uint8(firstByte & 0x0f); (size, pointer) = encoded.readUintX(pointer, sizeSize); } // Process branch uint256 nrindex = pointer + size; (DecodedSignature memory branchSig, bool branchHasBlacklist) = recoverConfiguration(encoded[pointer:nrindex]); pointer = nrindex; // Store the branch blacklist if (branchHasBlacklist) { if (hasBlacklist) { // Blacklist already set revert SessionErrors.InvalidBlacklist(); } hasBlacklist = true; sig.implicitBlacklist = branchSig.implicitBlacklist; } // Store the branch identity signer if (branchSig.identitySigner != address(0)) { if (sig.identitySigner != address(0)) { // Identity signer already set revert SessionErrors.InvalidIdentitySigner(); } sig.identitySigner = branchSig.identitySigner; } // Push all branch permissions to the permissions array for (uint256 i = 0; i < branchSig.sessionPermissions.length; i++) { sig.sessionPermissions[permissionsCount++] = branchSig.sessionPermissions[i]; } // Update root sig.imageHash = sig.imageHash != bytes32(0) ? LibOptim.fkeccak256(sig.imageHash, branchSig.imageHash) : branchSig.imageHash; continue; } // Blacklist (0x03) if (flag == FLAG_BLACKLIST) { if (hasBlacklist) { // Blacklist already set revert SessionErrors.InvalidBlacklist(); } hasBlacklist = true; // Read the blacklist count from the first byte's lower 4 bits uint256 blacklistCount = uint256(firstByte & 0x0f); if (blacklistCount == 0x0f) { // If it's max nibble, read the next 2 bytes for the actual size (blacklistCount, pointer) = encoded.readUint16(pointer); } uint256 pointerStart = pointer; // Read the blacklist addresses sig.implicitBlacklist = new address[](blacklistCount); address previousAddress; for (uint256 i = 0; i < blacklistCount; i++) { (sig.implicitBlacklist[i], pointer) = encoded.readAddress(pointer); if (sig.implicitBlacklist[i] < previousAddress) { revert SessionErrors.InvalidBlacklistUnsorted(); } previousAddress = sig.implicitBlacklist[i]; } // Update the root bytes32 blacklistHash = _leafHashForBlacklist(encoded[pointerStart:pointer]); sig.imageHash = sig.imageHash != bytes32(0) ? LibOptim.fkeccak256(sig.imageHash, blacklistHash) : blacklistHash; continue; } // Identity signer (0x04) if (flag == FLAG_IDENTITY_SIGNER) { if (sig.identitySigner != address(0)) { // Identity signer already set revert SessionErrors.InvalidIdentitySigner(); } (sig.identitySigner, pointer) = encoded.readAddress(pointer); // Update the root bytes32 identitySignerHash = _leafHashForIdentitySigner(sig.identitySigner); sig.imageHash = sig.imageHash != bytes32(0) ? LibOptim.fkeccak256(sig.imageHash, identitySignerHash) : identitySignerHash; continue; } revert SessionErrors.InvalidNodeType(flag); } { // Update the permissions array length to the actual count SessionPermissions[] memory permissions = sig.sessionPermissions; assembly { mstore(permissions, permissionsCount) } } return (sig, hasBlacklist); } /// @notice Decodes an array of Permission objects from the encoded data. function _decodePermissions( bytes calldata encoded, uint256 pointer ) internal pure returns (Permission[] memory permissions, uint256 newPointer) { uint256 length; (length, pointer) = encoded.readUint8(pointer); permissions = new Permission[](length); for (uint256 i = 0; i < length; i++) { (permissions[i], pointer) = LibPermission.readPermission(encoded, pointer); } return (permissions, pointer); } /// @notice Hashes the encoded session permissions into a leaf node. function _leafHashForPermissions( bytes calldata encodedPermissions ) internal pure returns (bytes32) { return keccak256(abi.encodePacked(uint8(FLAG_PERMISSIONS), encodedPermissions)); } /// @notice Hashes the encoded blacklist into a leaf node. function _leafHashForBlacklist( bytes calldata encodedBlacklist ) internal pure returns (bytes32) { return keccak256(abi.encodePacked(uint8(FLAG_BLACKLIST), encodedBlacklist)); } /// @notice Hashes the identity signer into a leaf node. function _leafHashForIdentitySigner( address identitySigner ) internal pure returns (bytes32) { return keccak256(abi.encodePacked(uint8(FLAG_IDENTITY_SIGNER), identitySigner)); } /// @notice Hashes a call with replay protection. /// @dev The replay protection is based on the chainId, space, nonce and index in the payload. /// @param payload The payload to hash /// @param callIdx The index of the call to hash /// @return callHash The hash of the call with replay protection function hashCallWithReplayProtection( Payload.Decoded calldata payload, uint256 callIdx ) public view returns (bytes32 callHash) { return keccak256( abi.encodePacked( payload.noChainId ? 0 : block.chainid, payload.space, payload.nonce, callIdx, Payload.hashCall(payload.calls[callIdx]) ) ); } }
ExplicitSessionManager.sol
// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.27; import { Payload } from "../../../modules/Payload.sol"; import { LibBytes } from "../../../utils/LibBytes.sol"; import { SessionErrors } from "../SessionErrors.sol"; import { IExplicitSessionManager, SessionPermissions, SessionUsageLimits } from "./IExplicitSessionManager.sol"; import { Permission, UsageLimit } from "./Permission.sol"; import { PermissionValidator } from "./PermissionValidator.sol"; abstract contract ExplicitSessionManager is IExplicitSessionManager, PermissionValidator { using LibBytes for bytes; /// @notice Special address used for tracking native token value limits address public constant VALUE_TRACKING_ADDRESS = address(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE); /// @inheritdoc IExplicitSessionManager function incrementUsageLimit( UsageLimit[] calldata limits ) external { address wallet = msg.sender; for (uint256 i = 0; i < limits.length; i++) { if (limits[i].usageAmount < getLimitUsage(wallet, limits[i].usageHash)) { // Cannot decrement usage limit revert SessionErrors.InvalidLimitUsageIncrement(); } setLimitUsage(wallet, limits[i].usageHash, limits[i].usageAmount); } } /// @notice Validates an explicit call /// @param payload The decoded payload containing calls /// @param callIdx The index of the call to validate /// @param wallet The wallet's address /// @param sessionSigner The session signer's address /// @param allSessionPermissions All sessions' permissions /// @param permissionIdx The index of the permission to validate /// @param sessionUsageLimits The session usage limits /// @return newSessionUsageLimits The updated session usage limits function _validateExplicitCall( Payload.Decoded calldata payload, uint256 callIdx, address wallet, address sessionSigner, SessionPermissions[] memory allSessionPermissions, uint8 permissionIdx, SessionUsageLimits memory sessionUsageLimits ) internal view returns (SessionUsageLimits memory newSessionUsageLimits) { // Find the permissions for the given session signer SessionPermissions memory sessionPermissions; for (uint256 i = 0; i < allSessionPermissions.length; i++) { if (allSessionPermissions[i].signer == sessionSigner) { sessionPermissions = allSessionPermissions[i]; break; } } if (sessionPermissions.signer == address(0)) { revert SessionErrors.InvalidSessionSigner(sessionSigner); } // Check if session chainId is valid if (sessionPermissions.chainId != 0 && sessionPermissions.chainId != block.chainid) { revert SessionErrors.InvalidChainId(sessionPermissions.chainId); } // Check if session has expired. if (sessionPermissions.deadline != 0 && block.timestamp > sessionPermissions.deadline) { revert SessionErrors.SessionExpired(sessionPermissions.deadline); } // Delegate calls are not allowed Payload.Call calldata call = payload.calls[callIdx]; if (call.delegateCall) { revert SessionErrors.InvalidDelegateCall(); } // Calls to incrementUsageLimit are the only allowed calls to this contract if (call.to == address(this)) { if (callIdx != 0) { // IncrementUsageLimit call is only allowed as the first call revert SessionErrors.InvalidLimitUsageIncrement(); } if (call.value > 0) { revert SessionErrors.InvalidValue(); } // No permissions required for the increment call return sessionUsageLimits; } // Get the permission for the current call if (permissionIdx >= sessionPermissions.permissions.length) { revert SessionErrors.MissingPermission(); } Permission memory permission = sessionPermissions.permissions[permissionIdx]; // Validate the permission for the current call (bool isValid, UsageLimit[] memory limits) = validatePermission(permission, call, wallet, sessionSigner, sessionUsageLimits.limits); if (!isValid) { revert SessionErrors.InvalidPermission(); } sessionUsageLimits.limits = limits; // Increment the total value used if (call.value > 0) { sessionUsageLimits.totalValueUsed += call.value; } if (sessionUsageLimits.totalValueUsed > sessionPermissions.valueLimit) { // Value limit exceeded revert SessionErrors.InvalidValue(); } return sessionUsageLimits; } /// @notice Verifies the limit usage increment /// @param call The first call in the payload, which is expected to be the increment call /// @param sessionUsageLimits The session usage limits /// @dev Reverts if the required increment call is missing or invalid /// @dev If no usage limits are used, this function does nothing function _validateLimitUsageIncrement( Payload.Call calldata call, SessionUsageLimits[] memory sessionUsageLimits ) internal view { // Limits call is only required if there are usage limits used if (sessionUsageLimits.length > 0) { // Verify the first call is the increment call and cannot be skipped if (call.to != address(this) || call.behaviorOnError != Payload.BEHAVIOR_REVERT_ON_ERROR || call.onlyFallback) { revert SessionErrors.InvalidLimitUsageIncrement(); } // Construct expected limit increments uint256 totalLimitsLength = 0; for (uint256 i = 0; i < sessionUsageLimits.length; i++) { totalLimitsLength += sessionUsageLimits[i].limits.length; if (sessionUsageLimits[i].totalValueUsed > 0) { totalLimitsLength++; } } UsageLimit[] memory limits = new UsageLimit[](totalLimitsLength); uint256 limitIndex = 0; for (uint256 i = 0; i < sessionUsageLimits.length; i++) { for (uint256 j = 0; j < sessionUsageLimits[i].limits.length; j++) { limits[limitIndex++] = sessionUsageLimits[i].limits[j]; } if (sessionUsageLimits[i].totalValueUsed > 0) { limits[limitIndex++] = UsageLimit({ usageHash: keccak256(abi.encode(sessionUsageLimits[i].signer, VALUE_TRACKING_ADDRESS)), usageAmount: sessionUsageLimits[i].totalValueUsed }); } } // Verify the increment call data bytes memory expectedData = abi.encodeWithSelector(this.incrementUsageLimit.selector, limits); bytes32 expectedDataHash = keccak256(expectedData); bytes32 actualDataHash = keccak256(call.data); if (actualDataHash != expectedDataHash) { revert SessionErrors.InvalidLimitUsageIncrement(); } } else { // Do not allow self calls if there are no usage limits if (call.to == address(this)) { revert SessionErrors.InvalidLimitUsageIncrement(); } } } }
IExplicitSessionManager.sol
// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.27; import { Permission, UsageLimit } from "./Permission.sol"; /// @notice Permissions configuration for a specific session signer /// @param signer Address of the session signer these permissions apply to /// @param chainId Chain ID of the session (0 = any chain) /// @param valueLimit Maximum native token value this signer can send /// @param deadline Deadline for the session. (0 = no deadline) /// @param permissions Array of encoded permissions granted to this signer struct SessionPermissions { address signer; uint256 chainId; uint256 valueLimit; uint64 deadline; Permission[] permissions; } /// @notice Usage limits configuration for a specific session signer /// @param signer Address of the session signer these limits apply to /// @param limits Array of usage limits /// @param totalValueUsed Total native token value used struct SessionUsageLimits { address signer; UsageLimit[] limits; uint256 totalValueUsed; } /// @title IExplicitSessionManager /// @author Agustin Aguilar, Michael Standen /// @notice Interface for the explicit session manager interface IExplicitSessionManager { /// @notice Increment usage for a caller's given session and target /// @param limits Array of limit/session/target combinations function incrementUsageLimit( UsageLimit[] calldata limits ) external; }
Permission.sol
// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.27; import { LibBytes } from "../../../utils/LibBytes.sol"; /// @notice Permission for a specific session signer /// @param target Address of the target contract this permission applies to /// @param rules Array of parameter rules struct Permission { address target; ParameterRule[] rules; } /// @notice Parameter operation for a specific session signer enum ParameterOperation { EQUAL, NOT_EQUAL, GREATER_THAN_OR_EQUAL, LESS_THAN_OR_EQUAL } /// @notice Parameter rule for a specific session signer /// @param cumulative If the value should accumulate over multiple calls /// @param operation Operation to apply to the parameter /// @param value Value to compare against the masked parameter /// @param offset Offset in calldata to read the parameter /// @param mask Mask to apply to the parameter struct ParameterRule { bool cumulative; ParameterOperation operation; bytes32 value; uint256 offset; bytes32 mask; } /// @notice Usage limit for a specific session signer /// @param usageHash Usage identifier /// @param usageAmount Amount of usage struct UsageLimit { bytes32 usageHash; uint256 usageAmount; } using LibBytes for bytes; /// @title LibPermission /// @author Michael Standen /// @notice Library for permission management library LibPermission { /// @notice Error thrown when the rules length exceeds the maximum error RulesLengthExceedsMax(); /// @notice Reads a permission from a packed bytes array /// @param encoded The packed bytes array /// @param pointer The pointer to the start of the permission /// @return permission The decoded permission /// @return newPointer The new pointer to the end of the permission function readPermission( bytes calldata encoded, uint256 pointer ) internal pure returns (Permission memory permission, uint256 newPointer) { // Target (permission.target, pointer) = encoded.readAddress(pointer); // Rules uint256 rulesLength; (rulesLength, pointer) = encoded.readUint8(pointer); permission.rules = new ParameterRule[](rulesLength); for (uint256 i = 0; i < rulesLength; i++) { uint8 operationCumulative; (operationCumulative, pointer) = encoded.readUint8(pointer); // 000X: cumulative permission.rules[i].cumulative = operationCumulative & 1 != 0; // XXX0: operation permission.rules[i].operation = ParameterOperation(operationCumulative >> 1); (permission.rules[i].value, pointer) = encoded.readBytes32(pointer); (permission.rules[i].offset, pointer) = encoded.readUint256(pointer); (permission.rules[i].mask, pointer) = encoded.readBytes32(pointer); } return (permission, pointer); } /// @notice Encodes a permission into a packed bytes array /// @param permission The permission to encode /// @return packed The packed bytes array function toPacked( Permission calldata permission ) internal pure returns (bytes memory packed) { if (permission.rules.length > type(uint8).max) { revert RulesLengthExceedsMax(); } packed = abi.encodePacked(permission.target, uint8(permission.rules.length)); for (uint256 i = 0; i < permission.rules.length; i++) { packed = abi.encodePacked(packed, ruleToPacked(permission.rules[i])); } } /// @notice Encodes a rule into a packed bytes array /// @param rule The rule to encode /// @return packed The packed bytes array function ruleToPacked( ParameterRule calldata rule ) internal pure returns (bytes memory packed) { // Combine operation and cumulative flag into a single byte // 0x[operationx3][cumulative] uint8 operationCumulative = (uint8(rule.operation) << 1) | (rule.cumulative ? 1 : 0); return abi.encodePacked(operationCumulative, rule.value, rule.offset, rule.mask); } }
PermissionValidator.sol
// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.27; import { Payload } from "../../../modules/Payload.sol"; import { LibBytes } from "../../../utils/LibBytes.sol"; import { ParameterOperation, ParameterRule, Permission, UsageLimit } from "./Permission.sol"; /// @title PermissionValidator /// @author Michael Standen, Agustin Aguilar /// @notice Validates permissions for a given call abstract contract PermissionValidator { using LibBytes for bytes; /// @notice Emitted when the usage amount for a given wallet and usage hash is updated event LimitUsageUpdated(address wallet, bytes32 usageHash, uint256 usageAmount); /// @notice Mapping of usage limit hashes to their usage amounts mapping(address => mapping(bytes32 => uint256)) private limitUsage; /// @notice Get the usage amount for a given usage hash and wallet /// @param wallet The wallet address /// @param usageHash The usage hash /// @return The usage amount function getLimitUsage(address wallet, bytes32 usageHash) public view returns (uint256) { return limitUsage[wallet][usageHash]; } /// @notice Set the usage amount for a given usage hash and wallet /// @param wallet The wallet address /// @param usageHash The usage hash /// @param usageAmount The usage amount function setLimitUsage(address wallet, bytes32 usageHash, uint256 usageAmount) internal { limitUsage[wallet][usageHash] = usageAmount; emit LimitUsageUpdated(wallet, usageHash, usageAmount); } /// @notice Validates a rules permission /// @param permission The rules permission to validate /// @param call The call to validate against /// @param wallet The wallet address /// @param signer The signer address /// @param usageLimits Array of current usage limits /// @return True if the permission is valid, false otherwise /// @return newUsageLimits New array of usage limits function validatePermission( Permission memory permission, Payload.Call calldata call, address wallet, address signer, UsageLimit[] memory usageLimits ) public view returns (bool, UsageLimit[] memory newUsageLimits) { if (permission.target != call.to) { return (false, usageLimits); } // Copy usage limits into array with space for new rules newUsageLimits = new UsageLimit[](usageLimits.length + permission.rules.length); for (uint256 i = 0; i < usageLimits.length; i++) { newUsageLimits[i] = usageLimits[i]; } uint256 actualLimitsCount = usageLimits.length; // Check each rule for (uint256 i = 0; i < permission.rules.length; i++) { ParameterRule memory rule = permission.rules[i]; // Extract value from calldata at offset (bytes32 value,) = call.data.readBytes32(rule.offset); // Apply mask value = value & rule.mask; if (rule.cumulative) { // Calculate cumulative usage uint256 value256 = uint256(value); // Find the usage limit for the current rule bytes32 usageHash = keccak256(abi.encode(signer, permission, i)); uint256 previousUsage; UsageLimit memory usageLimit; for (uint256 j = 0; j < newUsageLimits.length; j++) { if (newUsageLimits[j].usageHash == bytes32(0)) { // Initialize new usage limit usageLimit = UsageLimit({ usageHash: usageHash, usageAmount: 0 }); newUsageLimits[j] = usageLimit; actualLimitsCount = j + 1; break; } if (newUsageLimits[j].usageHash == usageHash) { // Value exists, use it usageLimit = newUsageLimits[j]; previousUsage = usageLimit.usageAmount; break; } } if (previousUsage == 0) { // Not in current payload, use storage previousUsage = getLimitUsage(wallet, usageHash); } // Cumulate usage value256 += previousUsage; usageLimit.usageAmount = value256; // Use the cumulative value for comparison value = bytes32(value256); } // Compare based on operation if (rule.operation == ParameterOperation.EQUAL) { if (value != rule.value) { return (false, usageLimits); } } else if (rule.operation == ParameterOperation.LESS_THAN_OR_EQUAL) { if (uint256(value) > uint256(rule.value)) { return (false, usageLimits); } } else if (rule.operation == ParameterOperation.NOT_EQUAL) { if (value == rule.value) { return (false, usageLimits); } } else if (rule.operation == ParameterOperation.GREATER_THAN_OR_EQUAL) { if (uint256(value) < uint256(rule.value)) { return (false, usageLimits); } } } // Fix array length assembly { mstore(newUsageLimits, actualLimitsCount) } return (true, newUsageLimits); } }
Attestation.sol
// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.27; import { LibBytes } from "../../../utils/LibBytes.sol"; import { ACCEPT_IMPLICIT_REQUEST_MAGIC_PREFIX } from "./ISignalsImplicitMode.sol"; using LibBytes for bytes; /// @notice Attestation for a specific session /// @param approvedSigner Address of the approved signer /// @param identityType Identity type /// @param issuerHash Hash of the issuer /// @param audienceHash Hash of the audience /// @param applicationData Unspecified application data /// @param authData Auth data struct Attestation { address approvedSigner; bytes4 identityType; bytes32 issuerHash; bytes32 audienceHash; bytes applicationData; AuthData authData; } /// @notice Auth data for an attestation /// @param redirectUrl Authorization redirect URL /// @param issuedAt Timestamp of the attestation issuance struct AuthData { string redirectUrl; uint64 issuedAt; } /// @title LibAttestation /// @author Michael Standen /// @notice Library for attestation management library LibAttestation { /// @notice Hashes an attestation function toHash( Attestation memory attestation ) internal pure returns (bytes32) { return keccak256(toPacked(attestation)); } /// @notice Decodes an attestation from a packed bytes array /// @param encoded The packed bytes array /// @param pointer The pointer to the start of the attestation /// @return attestation The decoded attestation /// @return newPointer The new pointer to the end of the attestation function fromPacked( bytes calldata encoded, uint256 pointer ) internal pure returns (Attestation memory attestation, uint256 newPointer) { newPointer = pointer; (attestation.approvedSigner, newPointer) = encoded.readAddress(newPointer); (attestation.identityType, newPointer) = encoded.readBytes4(newPointer); (attestation.issuerHash, newPointer) = encoded.readBytes32(newPointer); (attestation.audienceHash, newPointer) = encoded.readBytes32(newPointer); // Application data (arbitrary bytes) uint256 dataSize; (dataSize, newPointer) = encoded.readUint24(newPointer); attestation.applicationData = encoded[newPointer:newPointer + dataSize]; newPointer += dataSize; // Auth data (attestation.authData, newPointer) = fromPackedAuthData(encoded, newPointer); return (attestation, newPointer); } /// @notice Decodes the auth data from a packed bytes /// @param encoded The packed bytes containing the auth data /// @param pointer The pointer to the start of the auth data within the encoded data /// @return authData The decoded auth data /// @return newPointer The pointer to the end of the auth data within the encoded data function fromPackedAuthData( bytes calldata encoded, uint256 pointer ) internal pure returns (AuthData memory authData, uint256 newPointer) { uint24 redirectUrlLength; (redirectUrlLength, pointer) = encoded.readUint24(pointer); authData.redirectUrl = string(encoded[pointer:pointer + redirectUrlLength]); pointer += redirectUrlLength; (authData.issuedAt, pointer) = encoded.readUint64(pointer); return (authData, pointer); } /// @notice Encodes an attestation into a packed bytes array /// @param attestation The attestation to encode /// @return encoded The packed bytes array function toPacked( Attestation memory attestation ) internal pure returns (bytes memory encoded) { return abi.encodePacked( attestation.approvedSigner, attestation.identityType, attestation.issuerHash, attestation.audienceHash, uint24(attestation.applicationData.length), attestation.applicationData, toPackAuthData(attestation.authData) ); } /// @notice Encodes the auth data into a packed bytes array /// @param authData The auth data to encode /// @return encoded The packed bytes array function toPackAuthData( AuthData memory authData ) internal pure returns (bytes memory encoded) { return abi.encodePacked(uint24(bytes(authData.redirectUrl).length), bytes(authData.redirectUrl), authData.issuedAt); } /// @notice Generates the implicit request magic return value /// @param attestation The attestation /// @param wallet The wallet /// @return magic The expected implicit request magic function generateImplicitRequestMagic(Attestation memory attestation, address wallet) internal pure returns (bytes32) { return keccak256( abi.encodePacked(ACCEPT_IMPLICIT_REQUEST_MAGIC_PREFIX, wallet, attestation.audienceHash, attestation.issuerHash) ); } }
ISignalsImplicitMode.sol
// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.27; import { Payload } from "../../../modules/Payload.sol"; import { Attestation } from "./Attestation.sol"; /// @dev Magic prefix for the implicit request bytes32 constant ACCEPT_IMPLICIT_REQUEST_MAGIC_PREFIX = keccak256(abi.encodePacked("acceptImplicitRequest")); /// @title ISignalsImplicitMode /// @author Agustin Aguilar, Michael Standen /// @notice Interface for the contracts that support implicit mode validation interface ISignalsImplicitMode { /// @notice Determines if an implicit request is valid /// @param wallet The wallet's address /// @param attestation The attestation data /// @param call The call to validate /// @return magic The hash of the implicit request if valid function acceptImplicitRequest( address wallet, Attestation calldata attestation, Payload.Call calldata call ) external view returns (bytes32 magic); }
ImplicitSessionManager.sol
// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.27; import { Payload } from "../../../modules/Payload.sol"; import { SessionErrors } from "../SessionErrors.sol"; import { Attestation, LibAttestation } from "./Attestation.sol"; import { ISignalsImplicitMode } from "./ISignalsImplicitMode.sol"; using LibAttestation for Attestation; /// @title ImplicitSessionManager /// @author Agustin Aguilar, Michael Standen /// @notice Manager for implicit sessions abstract contract ImplicitSessionManager { /// @notice Validates a call in implicit mode /// @param call The call to validate /// @param wallet The wallet's address /// @param sessionSigner The session signer's address /// @param attestation The session attestation function _validateImplicitCall( Payload.Call calldata call, address wallet, address sessionSigner, Attestation memory attestation, address[] memory blacklist ) internal view { // Validate the session signer is attested if (sessionSigner != attestation.approvedSigner) { revert SessionErrors.InvalidSessionSigner(sessionSigner); } // Delegate calls are not allowed if (call.delegateCall) { revert SessionErrors.InvalidDelegateCall(); } // Check if the signer is blacklisted if (_isAddressBlacklisted(sessionSigner, blacklist)) { revert SessionErrors.BlacklistedAddress(sessionSigner); } // Check if the target address is blacklisted if (_isAddressBlacklisted(call.to, blacklist)) { revert SessionErrors.BlacklistedAddress(call.to); } // No value if (call.value > 0) { revert SessionErrors.InvalidValue(); } // Validate the implicit request bytes32 result = ISignalsImplicitMode(call.to).acceptImplicitRequest(wallet, attestation, call); bytes32 attestationMagic = attestation.generateImplicitRequestMagic(wallet); if (result != attestationMagic) { revert SessionErrors.InvalidImplicitResult(); } } /// @notice Checks if an address is in the blacklist using binary search /// @param target The address to check /// @param blacklist The sorted array of blacklisted addresses /// @return bool True if the address is blacklisted, false otherwise function _isAddressBlacklisted(address target, address[] memory blacklist) internal pure returns (bool) { int256 left = 0; int256 right = int256(blacklist.length) - 1; while (left <= right) { int256 mid = left + (right - left) / 2; address currentAddress = blacklist[uint256(mid)]; if (currentAddress == target) { return true; } else if (currentAddress < target) { left = mid + 1; } else { right = mid - 1; } } return false; } }
Payload.sol
// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.27; import { LibBytes } from "../utils/LibBytes.sol"; using LibBytes for bytes; /// @title Payload /// @author Agustin Aguilar, Michael Standen, William Hua /// @notice Library for encoding and decoding payloads library Payload { /// @notice Error thrown when the kind is invalid error InvalidKind(uint8 kind); /// @dev keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)") bytes32 private constant EIP712_DOMAIN_TYPEHASH = 0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f; /// @dev keccak256("Sequence Wallet") bytes32 private constant EIP712_DOMAIN_NAME_SEQUENCE = 0x4aa45ca7ad825ceb1bf35643f0a58c295239df563b1b565c2485f96477c56318; /// @dev keccak256("3") bytes32 private constant EIP712_DOMAIN_VERSION_SEQUENCE = 0x2a80e1ef1d7842f27f2e6be0972bb708b9a135c38860dbe73c27c3486c34f4de; function domainSeparator(bool _noChainId, address _wallet) internal view returns (bytes32 _domainSeparator) { return keccak256( abi.encode( EIP712_DOMAIN_TYPEHASH, EIP712_DOMAIN_NAME_SEQUENCE, EIP712_DOMAIN_VERSION_SEQUENCE, _noChainId ? uint256(0) : uint256(block.chainid), _wallet ) ); } /// @dev keccak256("Call(address to,uint256 value,bytes data,uint256 gasLimit,bool delegateCall,bool onlyFallback,uint256 behaviorOnError)") bytes32 private constant CALL_TYPEHASH = 0x0603985259a953da1f65a522f589c17bd1d0117ec1d3abb7c0788aef251ef437; /// @dev keccak256("Calls(Call[] calls,uint256 space,uint256 nonce,address[] wallets)Call(address to,uint256 value,bytes data,uint256 gasLimit,bool delegateCall,bool onlyFallback,uint256 behaviorOnError)") bytes32 private constant CALLS_TYPEHASH = 0x11e1e4079a79a66e4ade50033cfe2678cdd5341d2dfe5ef9513edb1a0be147a2; /// @dev keccak256("Message(bytes message,address[] wallets)") bytes32 private constant MESSAGE_TYPEHASH = 0xe19a3b94fc3c7ece3f890d98a99bc422615537a08dea0603fa8425867d87d466; /// @dev keccak256("ConfigUpdate(bytes32 imageHash,address[] wallets)") bytes32 private constant CONFIG_UPDATE_TYPEHASH = 0x11fdeb7e8373a1aa96bfac8d0ea91526b2c5d15e5cee20e0543e780258f3e8e4; /// @notice Kind of transaction uint8 public constant KIND_TRANSACTIONS = 0x00; /// @notice Kind of digest uint8 public constant KIND_MESSAGE = 0x01; /// @notice Kind of config update uint8 public constant KIND_CONFIG_UPDATE = 0x02; /// @notice Kind of message uint8 public constant KIND_DIGEST = 0x03; /// @notice Behavior on error: ignore error uint8 public constant BEHAVIOR_IGNORE_ERROR = 0x00; /// @notice Behavior on error: revert on error uint8 public constant BEHAVIOR_REVERT_ON_ERROR = 0x01; /// @notice Behavior on error: abort on error uint8 public constant BEHAVIOR_ABORT_ON_ERROR = 0x02; /// @notice Payload call information /// @param to Address of the target contract /// @param value Value to send with the call /// @param data Data to send with the call /// @param gasLimit Gas limit for the call /// @param delegateCall If the call is a delegate call /// @param onlyFallback If the call should only be executed in an error scenario /// @param behaviorOnError Behavior on error struct Call { address to; uint256 value; bytes data; uint256 gasLimit; bool delegateCall; bool onlyFallback; uint256 behaviorOnError; } /// @notice Decoded payload /// @param kind Kind of payload /// @param noChainId If the chain ID should be omitted /// @param calls Array of calls (transaction kind) /// @param space Nonce space for the calls (transaction kind) /// @param nonce Nonce value for the calls (transaction kind) /// @param message Message to validate (message kind) /// @param imageHash Image hash to update to (config update kind) /// @param digest Digest to validate (digest kind) /// @param parentWallets Parent wallets struct Decoded { uint8 kind; bool noChainId; // Transaction kind Call[] calls; uint256 space; uint256 nonce; // Message kind bytes message; // Config update kind bytes32 imageHash; // Digest kind for 1271 bytes32 digest; // Parent wallets address[] parentWallets; } function fromMessage( bytes memory message ) internal pure returns (Decoded memory _decoded) { _decoded.kind = KIND_MESSAGE; _decoded.message = message; } function fromConfigUpdate( bytes32 imageHash ) internal pure returns (Decoded memory _decoded) { _decoded.kind = KIND_CONFIG_UPDATE; _decoded.imageHash = imageHash; } function fromDigest( bytes32 digest ) internal pure returns (Decoded memory _decoded) { _decoded.kind = KIND_DIGEST; _decoded.digest = digest; } function fromPackedCalls( bytes calldata packed ) internal view returns (Decoded memory _decoded) { _decoded.kind = KIND_TRANSACTIONS; // Read the global flag (uint256 globalFlag, uint256 pointer) = packed.readFirstUint8(); // First bit determines if space is zero or not if (globalFlag & 0x01 == 0x01) { _decoded.space = 0; } else { (_decoded.space, pointer) = packed.readUint160(pointer); } // Next 3 bits determine the size of the nonce uint256 nonceSize = (globalFlag >> 1) & 0x07; if (nonceSize > 0) { // Read the nonce (_decoded.nonce, pointer) = packed.readUintX(pointer, nonceSize); } uint256 numCalls; // Bit 5 determines if the batch contains a single call if (globalFlag & 0x10 == 0x10) { numCalls = 1; } else { // Bit 6 determines if the number of calls uses 1 byte or 2 bytes if (globalFlag & 0x20 == 0x20) { (numCalls, pointer) = packed.readUint16(pointer); } else { (numCalls, pointer) = packed.readUint8(pointer); } } // Read the calls _decoded.calls = new Call[](numCalls); for (uint256 i = 0; i < numCalls; i++) { uint8 flags; (flags, pointer) = packed.readUint8(pointer); // First bit determines if this is a call to self // or a call to another address if (flags & 0x01 == 0x01) { // Call to self _decoded.calls[i].to = address(this); } else { // Call to another address (_decoded.calls[i].to, pointer) = packed.readAddress(pointer); } // Second bit determines if the call has value or not if (flags & 0x02 == 0x02) { (_decoded.calls[i].value, pointer) = packed.readUint256(pointer); } // Third bit determines if the call has data or not if (flags & 0x04 == 0x04) { // 3 bytes determine the size of the calldata uint256 calldataSize; (calldataSize, pointer) = packed.readUint24(pointer); _decoded.calls[i].data = packed[pointer:pointer + calldataSize]; pointer += calldataSize; } // Fourth bit determines if the call has a gas limit or not if (flags & 0x08 == 0x08) { (_decoded.calls[i].gasLimit, pointer) = packed.readUint256(pointer); } // Fifth bit determines if the call is a delegate call or not _decoded.calls[i].delegateCall = (flags & 0x10 == 0x10); // Sixth bit determines if the call is fallback only _decoded.calls[i].onlyFallback = (flags & 0x20 == 0x20); // Last 2 bits are directly mapped to the behavior on error _decoded.calls[i].behaviorOnError = (flags & 0xC0) >> 6; } } function hashCall( Call memory c ) internal pure returns (bytes32) { return keccak256( abi.encode( CALL_TYPEHASH, c.to, c.value, keccak256(c.data), c.gasLimit, c.delegateCall, c.onlyFallback, c.behaviorOnError ) ); } function hashCalls( Call[] memory calls ) internal pure returns (bytes32) { // In EIP712, an array is often hashed as the keccak256 of the concatenated // hashes of each item. So we hash each Call, pack them, and hash again. bytes32[] memory callHashes = new bytes32[](calls.length); for (uint256 i = 0; i < calls.length; i++) { callHashes[i] = hashCall(calls[i]); } return keccak256(abi.encodePacked(callHashes)); } function toEIP712( Decoded memory _decoded ) internal pure returns (bytes32) { bytes32 walletsHash = keccak256(abi.encodePacked(_decoded.parentWallets)); if (_decoded.kind == KIND_TRANSACTIONS) { bytes32 callsHash = hashCalls(_decoded.calls); // The top-level struct for Calls might be something like: // Calls(bytes32 callsHash,uint256 space,uint256 nonce,bytes32 walletsHash) return keccak256(abi.encode(CALLS_TYPEHASH, callsHash, _decoded.space, _decoded.nonce, walletsHash)); } else if (_decoded.kind == KIND_MESSAGE) { // If you define your top-level as: Message(bytes32 messageHash,bytes32 walletsHash) return keccak256(abi.encode(MESSAGE_TYPEHASH, keccak256(_decoded.message), walletsHash)); } else if (_decoded.kind == KIND_CONFIG_UPDATE) { // Top-level: ConfigUpdate(bytes32 imageHash,bytes32 walletsHash) return keccak256(abi.encode(CONFIG_UPDATE_TYPEHASH, _decoded.imageHash, walletsHash)); } else if (_decoded.kind == KIND_DIGEST) { // Top-level: Use MESSAGE_TYPEHASH but assume the digest is already the hashed message return keccak256(abi.encode(MESSAGE_TYPEHASH, _decoded.digest, walletsHash)); } else { // Unknown kind revert InvalidKind(_decoded.kind); } } function hash( Decoded memory _decoded ) internal view returns (bytes32) { bytes32 domain = domainSeparator(_decoded.noChainId, address(this)); bytes32 structHash = toEIP712(_decoded); return keccak256(abi.encodePacked("\x19\x01", domain, structHash)); } function hashFor(Decoded memory _decoded, address _wallet) internal view returns (bytes32) { bytes32 domain = domainSeparator(_decoded.noChainId, _wallet); bytes32 structHash = toEIP712(_decoded); return keccak256(abi.encodePacked("\x19\x01", domain, structHash)); } }
ISapient.sol
// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.27; import { Payload } from "../Payload.sol"; /// @title ISapient /// @author Agustin Aguilar, Michael Standen /// @notice Sapient signers take an explicit payload and return their own "imageHash" as result /// @dev The consumer of this signer must validate if the imageHash is valid or not, for the desired configuration interface ISapient { /// @notice Recovers the image hash of a given signature /// @param payload The payload to recover the signature from /// @param signature The signature to recover the image hash from /// @return imageHash The recovered image hash function recoverSapientSignature( Payload.Decoded calldata payload, bytes calldata signature ) external view returns (bytes32 imageHash); } /// @title ISapientCompact /// @author Agustin Aguilar, Michael Standen /// @notice Sapient signers take a compacted payload and return their own "imageHash" as result /// @dev The consumer of this signer must validate if the imageHash is valid or not, for the desired configuration interface ISapientCompact { /// @notice Recovers the image hash of a given signature, using a hashed payload /// @param digest The digest of the payload /// @param signature The signature to recover the image hash from /// @return imageHash The recovered image hash function recoverSapientSignatureCompact( bytes32 digest, bytes calldata signature ) external view returns (bytes32 imageHash); }
LibBytes.sol
// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.18; /// @title Library for reading data from bytes arrays /// @author Agustin Aguilar (aa@horizon.io), Michael Standen (mstan@horizon.io) /// @notice This library contains functions for reading data from bytes arrays. /// @dev These functions do not check if the input index is within the bounds of the data array. /// @dev Reading out of bounds may return dirty values. library LibBytes { function readFirstUint8( bytes calldata _data ) internal pure returns (uint8 a, uint256 newPointer) { assembly { let word := calldataload(_data.offset) a := shr(248, word) newPointer := 1 } } function readUint8(bytes calldata _data, uint256 _index) internal pure returns (uint8 a, uint256 newPointer) { assembly { let word := calldataload(add(_index, _data.offset)) a := shr(248, word) newPointer := add(_index, 1) } } function readUint16(bytes calldata _data, uint256 _index) internal pure returns (uint16 a, uint256 newPointer) { assembly { let word := calldataload(add(_index, _data.offset)) a := shr(240, word) newPointer := add(_index, 2) } } function readUint24(bytes calldata _data, uint256 _index) internal pure returns (uint24 a, uint256 newPointer) { assembly { let word := calldataload(add(_index, _data.offset)) a := shr(232, word) newPointer := add(_index, 3) } } function readUint64(bytes calldata _data, uint256 _index) internal pure returns (uint64 a, uint256 newPointer) { assembly { let word := calldataload(add(_index, _data.offset)) a := shr(192, word) newPointer := add(_index, 8) } } function readUint160(bytes calldata _data, uint256 _index) internal pure returns (uint160 a, uint256 newPointer) { assembly { let word := calldataload(add(_index, _data.offset)) a := shr(96, word) newPointer := add(_index, 20) } } function readUint256(bytes calldata _data, uint256 _index) internal pure returns (uint256 a, uint256 newPointer) { assembly { a := calldataload(add(_index, _data.offset)) newPointer := add(_index, 32) } } function readUintX( bytes calldata _data, uint256 _index, uint256 _length ) internal pure returns (uint256 a, uint256 newPointer) { assembly { let word := calldataload(add(_index, _data.offset)) let shift := sub(256, mul(_length, 8)) a := and(shr(shift, word), sub(shl(mul(8, _length), 1), 1)) newPointer := add(_index, _length) } } function readBytes4(bytes calldata _data, uint256 _pointer) internal pure returns (bytes4 a, uint256 newPointer) { assembly { let word := calldataload(add(_pointer, _data.offset)) a := and(word, 0xffffffff00000000000000000000000000000000000000000000000000000000) newPointer := add(_pointer, 4) } } function readBytes32(bytes calldata _data, uint256 _pointer) internal pure returns (bytes32 a, uint256 newPointer) { assembly { a := calldataload(add(_pointer, _data.offset)) newPointer := add(_pointer, 32) } } function readAddress(bytes calldata _data, uint256 _index) internal pure returns (address a, uint256 newPointer) { assembly { let word := calldataload(add(_index, _data.offset)) a := and(shr(96, word), 0xffffffffffffffffffffffffffffffffffffffff) newPointer := add(_index, 20) } } /// @dev ERC-2098 Compact Signature function readRSVCompact( bytes calldata _data, uint256 _index ) internal pure returns (bytes32 r, bytes32 s, uint8 v, uint256 newPointer) { uint256 yParityAndS; assembly { r := calldataload(add(_index, _data.offset)) yParityAndS := calldataload(add(_index, add(_data.offset, 32))) newPointer := add(_index, 64) } uint256 yParity = uint256(yParityAndS >> 255); s = bytes32(uint256(yParityAndS) & ((1 << 255) - 1)); v = uint8(yParity) + 27; } }
LibOptim.sol
// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.18; /// @title LibOptim /// @author Agustin Aguilar /// @notice Library for optimized EVM operations library LibOptim { /** * @notice Computes the keccak256 hash of two 32-byte inputs. * @dev It uses only scratch memory space. * @param _a The first 32 bytes of the hash. * @param _b The second 32 bytes of the hash. * @return c The keccak256 hash of the two 32-byte inputs. */ function fkeccak256(bytes32 _a, bytes32 _b) internal pure returns (bytes32 c) { assembly { mstore(0, _a) mstore(32, _b) c := keccak256(0, 64) } } /** * @notice Returns the return data from the last call. * @return r The return data from the last call. */ function returnData() internal pure returns (bytes memory r) { assembly { let size := returndatasize() r := mload(0x40) let start := add(r, 32) mstore(0x40, add(start, size)) mstore(r, size) returndatacopy(start, 0, size) } } /** * @notice Calls another contract with the given parameters. * @dev This method doesn't increase the memory pointer. * @param _to The address of the contract to call. * @param _val The value to send to the contract. * @param _gas The amount of gas to provide for the call. * @param _data The data to send to the contract. * @return r The success status of the call. */ function call(address _to, uint256 _val, uint256 _gas, bytes memory _data) internal returns (bool r) { assembly { r := call(_gas, _to, _val, add(_data, 32), mload(_data), 0, 0) } } /** * @notice Calls another contract with the given parameters, using delegatecall. * @dev This method doesn't increase the memory pointer. * @param _to The address of the contract to call. * @param _gas The amount of gas to provide for the call. * @param _data The data to send to the contract. * @return r The success status of the call. */ function delegatecall(address _to, uint256 _gas, bytes memory _data) internal returns (bool r) { assembly { r := delegatecall(_gas, _to, add(_data, 32), mload(_data), 0, 0) } } }
Gas Token: