diff --git a/ante/ante.go b/ante/ante.go index 15609516..75329bf8 100644 --- a/ante/ante.go +++ b/ante/ante.go @@ -2,6 +2,7 @@ package ante import ( anteinterfaces "github.com/cosmos/evm/ante/interfaces" + "github.com/cosmos/evm/stakingguard" ibckeeper "github.com/cosmos/ibc-go/v10/modules/core/keeper" errorsmod "cosmossdk.io/errors" @@ -33,6 +34,10 @@ type HandlerOptions struct { // use dynamic fee checker or the cosmos-sdk default one for native transactions DynamicFeeChecker bool PendingTxListener PendingTxListener + // StakingGuardAllowlist, when set, turns on the PoA staking-guard ante decorator for the + // Cosmos/authz path. It blocks validator creation and unjail, limits the delegation family + // to the allowlisted vault, and recurses into authz.MsgExec. + StakingGuardAllowlist stakingguard.StakingPolicyFunc } // Validate checks if the keepers are defined diff --git a/ante/cosmos.go b/ante/cosmos.go index ea014d12..083dd2ca 100644 --- a/ante/cosmos.go +++ b/ante/cosmos.go @@ -3,6 +3,7 @@ package ante import ( cosmosante "github.com/cosmos/evm/ante/cosmos" evmante "github.com/cosmos/evm/ante/evm" + "github.com/cosmos/evm/stakingguard" evmtypes "github.com/cosmos/evm/x/vm/types" ibcante "github.com/cosmos/ibc-go/v10/modules/core/ante" poaante "github.com/xrplevm/node/v10/x/poa/ante" @@ -42,5 +43,9 @@ func newCosmosAnteHandler(ctx sdk.Context, options HandlerOptions) sdk.AnteHandl ante.NewIncrementSequenceDecorator(options.AccountKeeper), ibcante.NewRedundantRelayDecorator(options.IBCKeeper), poaante.NewPoaDecorator(), + // PoA staking guard: blocks MsgCreateValidator and MsgUnjail, limits the delegation + // family to the allowlisted vault, and recurses into authz.MsgExec. The vendored PoA + // decorator above handles neither validator management nor authz unwrapping. + stakingguard.NewDecorator(options.StakingGuardAllowlist), ) } diff --git a/contracts/package-lock.json b/contracts/package-lock.json index 325711df..23d755d0 100644 --- a/contracts/package-lock.json +++ b/contracts/package-lock.json @@ -12,7 +12,7 @@ "@account-abstraction/contracts": "^0.6.0" }, "devDependencies": { - "@openzeppelin/contracts": "^5.4.0", + "@openzeppelin/contracts": "4.9.6", "hardhat": "^3.0.15" } }, @@ -802,9 +802,9 @@ } }, "node_modules/@openzeppelin/contracts": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-5.4.0.tgz", - "integrity": "sha512-eCYgWnLg6WO+X52I16TZt8uEjbtdkgLC0SUX/xnAksjjrQI4Xfn4iBRoI5j55dmlOhDv1Y7BoR3cU7e3WWhC6A==", + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-4.9.6.tgz", + "integrity": "sha512-xSmezSupL+y9VkHZJGDoCBpmnB2ogM13ccaYDWqJTfS3dbuHkgjuwDFUmaFauBCboQMGB/S5UqUl2y54X99BmA==", "dev": true, "license": "MIT" }, diff --git a/contracts/solidity/StakedBondVault.json b/contracts/solidity/StakedBondVault.json index 50e9dd23..fba2b668 100644 --- a/contracts/solidity/StakedBondVault.json +++ b/contracts/solidity/StakedBondVault.json @@ -10,6 +10,11 @@ "name": "admin", "type": "address" }, + { + "internalType": "address", + "name": "werc20", + "type": "address" + }, { "internalType": "string", "name": "name_", @@ -193,6 +198,19 @@ "name": "RoleRevoked", "type": "event" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "bps", + "type": "uint256" + } + ], + "name": "SlashBufferUpdated", + "type": "event" + }, { "anonymous": false, "inputs": [ @@ -449,6 +467,19 @@ "name": "WithdrawalRequested", "type": "event" }, + { + "inputs": [], + "name": "BPS_DENOM", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "DEFAULT_ADMIN_ROLE", @@ -477,7 +508,7 @@ }, { "inputs": [], - "name": "MAX_UNBONDING_ENTRIES", + "name": "MAX_DELEGATION_BPS_PER_VALIDATOR", "outputs": [ { "internalType": "uint256", @@ -490,7 +521,7 @@ }, { "inputs": [], - "name": "MAX_VALIDATORS", + "name": "MAX_SLASH_BUFFER_BPS", "outputs": [ { "internalType": "uint256", @@ -503,7 +534,7 @@ }, { "inputs": [], - "name": "MIN_OPERATION_ASSETS", + "name": "MAX_UNBONDING_ENTRIES", "outputs": [ { "internalType": "uint256", @@ -516,12 +547,25 @@ }, { "inputs": [], - "name": "NATIVE_WERC20", + "name": "MAX_VALIDATORS", "outputs": [ { - "internalType": "address", + "internalType": "uint256", "name": "", - "type": "address" + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MIN_OPERATION_ASSETS", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" } ], "stateMutability": "view", @@ -553,6 +597,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "POKE_STEP_COUNT", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "SCORE_SCALE", @@ -1347,6 +1404,19 @@ "stateMutability": "payable", "type": "function" }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "bps", + "type": "uint256" + } + ], + "name": "setSlashBufferBps", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, { "inputs": [ { @@ -1360,6 +1430,38 @@ "stateMutability": "payable", "type": "function" }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "maxOps", + "type": "uint256" + } + ], + "name": "settle", + "outputs": [ + { + "internalType": "uint256", + "name": "ops", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "slashBufferBps", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -1609,6 +1711,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "uint64[]", + "name": "proposalIds", + "type": "uint64[]" + } + ], + "name": "voteAbstain", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, { "inputs": [ { @@ -1765,36 +1880,36 @@ "type": "function" } ], - "bytecode": "0x60c0604090808252346200053c5762006016803803809162000022828562000540565b833981016060828203126200053c5781516001600160a01b038116908190036200053c576020838101516001600160401b0394919291908581116200053c57846200006f91830162000580565b93868201518681116200053c5762000088920162000580565b93835181811162000447576003908154906001968783811c9316801562000531575b878410146200051d578190601f93848111620004ca575b50879084831160011462000467575f926200045b575b50505f1982851b1c191690871b1782555b8651928311620004475760049687548781811c911680156200043c575b8782101462000429579081838695949311620003d2575b50869184116001146200036b575f936200035f575b505082861b925f19911b1c19161784555b6200014c620005e8565b901562000356575b60a05273d4949664cd82660aae99bedc034a0dea8a0bd5176080528260065560ff1993600885600a541617600a558360115581156200032b5750906016915f8052600590818352865f20815f52835260ff875f20541615620002f7575b7f97667070c54ef182b0f5858b034beac1b6f3089aa2d3188bb1e8929f4fa9b929805f52828452875f20825f52845260ff885f20541615620002c2575b507f65d7a28e3265b37a6474929f336521b332c1681b933f6cb9f3376673440d862a91825f52808452875f20825f52845260ff885f205416156200028d575b5050506801000000000000000160018060801b03196010541617601055825f5252825f20918254161790555161593f9081620006b78239608051818181611006015281816114e201528181614b9201526157d9015260a05181611a9b0152f35b825f528352865f20815f528352865f20858782541617905533915f8051602062005ff68339815191525f80a45f80806200022d565b805f52828452875f20825f528452875f2086888254161790558133915f8051602062005ff68339815191525f80a45f620001ee565b5f8052818352865f20815f528352865f20858782541617905533815f5f8051602062005ff68339815191528180a4620001b1565b855162461bcd60e51b8152908101839052600560248201526420a226a4a760d91b6044820152606490fd5b50601262000154565b015191505f8062000131565b9190879450601f19841692895f52875f20935f5b89828210620003bb5750508511620003a1575b50505050811b01845562000142565b01519060f8845f19921b161c191690555f80808062000392565b8385015187558b989096019593840193016200037f565b9091929350885f52865f208380870160051c8201928988106200041f575b918a918897969594930160051c01915b828110620004105750506200011c565b5f81558796508a910162000400565b92508192620003f0565b602289634e487b7160e01b5f525260245ffd5b90607f169062000105565b634e487b7160e01b5f52604160045260245ffd5b015190505f80620000d7565b90899350601f19831691865f52895f20925f5b8b828210620004b357505084116200049b575b505050811b018255620000e8565b01515f1983871b60f8161c191690555f80806200048d565b8385015186558d979095019493840193016200047a565b909150845f52875f208480850160051c8201928a861062000513575b918b91869594930160051c01915b82811062000504575050620000c1565b5f81558594508b9101620004f4565b92508192620004e6565b634e487b7160e01b5f52602260045260245ffd5b92607f1692620000aa565b5f80fd5b601f909101601f19168101906001600160401b038211908210176200044757604052565b6001600160401b0381116200044757601f01601f191660200190565b919080601f840112156200053c578251906200059c8262000564565b91620005ac604051938462000540565b8083526020918282870101116200053c575f5b818110620005d45750825f9394955001015290565b8581018301518482018401528201620005bf565b6040805163313ce56760e01b6020820190815260048252929181016001600160401b0381118282101762000447576040525f80938192519073d4949664cd82660aae99bedc034a0dea8a0bd5175afa3d15620006ad573d906200064b8262000564565b916200065b604051938462000540565b82523d84602084013e5b80620006a0575b62000677575b508190565b6020818051810103126200069c576020015160ff811162000672576001925060ff1690565b8280fd5b506020815110156200066c565b6060906200066556fe60806040526004361015610011575f80fd5b5f3560e01c806301e1d11414611e1e57806301ffc9a714611dc857806303e5fa8814611d9f57806306fdde0314611cfd57806307a2d13a146113c2578063095ea7b314611cd75780630a28a47714611c955780630a763da114611c6f5780630f43a67714611c5257806318160ddd14611c355780631f9acd7e14611c1a57806323b872dd14611be2578063248a9ca314611bb5578063291cdb8c14611b905780632d9414b514611b765780632f2ff15d14611ace578063313ce56714611a8757806332145f9014611a5c578063331c138c1461181357806333a73916146117f657806333bb7f911461162557806335aa2e44146115a357806336568abe1461151157806338d52e0f146114cd578063391ed03c14611468578063395093511461141b578063402d267d146106dc5780634221b2c2146113fe5780634864b09d146113e1578063499bafcc146113c75780634cdad506146113c25780634f7b9013146113a1578063559904cb146112cd578063588f134314610e2a5780635ac22fd214610df657806360da3e8314610dd45780636a84a98514610db75780636c68e40314610d7b5780636e553f65146109c857806370a0823114610d80578063714897df14610d7b5780637316a19614610d5b57806377397cdd14610d3e5780637aed3cbb14610d215780637d41c86e14610a9057806380d04de814610a7357806391d1485414610a2a578063937b2581146109cd57806394bf804d146109c857806395d89b41146108e5578063a217fddf146108cb578063a41fcdc3146108b0578063a457c2d71461080d578063a9059cbb146107dc578063ac44da52146107ba578063b3d7f6b914610770578063b460af941461073d578063b9bcb70f14610742578063ba0876521461073d578063c63d75b6146106dc578063c6e6f59214610364578063ce96cb77146106dc578063d547741f146106fe578063d77bb97c146106e1578063d905777e146106dc578063dd62ed3e1461068f578063e63ab1e914610655578063e9f2838e14610630578063ecdcce2414610369578063ef8b30f7146103645763f5b541a614610326575f80fd5b34610360575f3660031901126103605760206040517f97667070c54ef182b0f5858b034beac1b6f3089aa2d3188bb1e8929f4fa9b9298152f35b5f80fd5b61214a565b602080600319360112610360576004359060ff82168092036103605761038f3415612984565b7f97667070c54ef182b0f5858b034beac1b6f3089aa2d3188bb1e8929f4fa9b929805f526005825260405f20335f52825260ff60405f205416156104a2575081151580610498575b1561046c57817fe28c6c3955ed05a471a85214f10fe95cb125572d2aadeb008a7c5992533984969260ff19600a541617600a556104126136a9565b61041d600954611f00565b8061042d575b50604051908152a1005b601f811160011461044457505f6009555b83610423565b5f9060098252610462601f858420920160051c820160018301612adf565b816009555561043e565b6064906040519062461bcd60e51b82526004820152600560248201526410d3d5539560da1b6044820152fd5b50808211156103d7565b906104ac336126f1565b916040516104b981611fc9565b6042815282810191606036843781511561061c5760308353815160019081101561061c57607860218401536041905b8082116105c557505061059557610561936105709260489260405196879376020b1b1b2b9b9a1b7b73a3937b61d1030b1b1b7bab73a1604d1b88860152610538815180928a603789019101611e38565b8401917001034b99036b4b9b9b4b733903937b6329607d1b603784015251809386840190611e38565b0103602881018552018361202e565b61059160405192839262461bcd60e51b845260048401526024830190611e59565b0390fd5b6064836040519062461bcd60e51b825280600483015260248201525f805160206158aa8339815191526044820152fd5b9091600f8116601081101561061c576f181899199a1a9b1b9c1cb0b131b232b360811b901a6105f484866126e0565b5360041c918015610608575f1901906104e8565b634e487b7160e01b5f52601160045260245ffd5b634e487b7160e01b5f52603260045260245ffd5b34610360575f36600319011261036057602060ff60125460081c166040519015158152f35b34610360575f3660031901126103605760206040517f65d7a28e3265b37a6474929f336521b332c1681b933f6cb9f3376673440d862a8152f35b34610360576040366003190112610360576106a8611e9c565b6106b0611eb2565b9060018060a01b038091165f52600160205260405f2091165f52602052602060405f2054604051908152f35b61204f565b34610360575f366003190112610360576020600e54604051908152f35b346103605760403660031901126103605761073b60043561071d611eb2565b90805f526005602052610736600160405f20015461218c565b61232b565b005b61210b565b34610360575f36600319011261036057602060405173d4949664cd82660aae99bedc034a0dea8a0bd5178152f35b3461036057602036600319011261036057610789612964565b60018101809111610608576002549060018201809211610608576020916107b2916004356127e9565b604051908152f35b34610360575f366003190112610360576020604051670de0b6b3a76400008152f35b34610360576040366003190112610360576108026107f8611e9c565b60243590336123ad565b602060405160018152f35b3461036057604036600319011261036057610826611e9c565b60243590335f52600160205260405f2060018060a01b0382165f5260205260405f20549180831061085d5761080292039033612507565b60405162461bcd60e51b815260206004820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f77604482015264207a65726f60d81b6064820152608490fd5b34610360575f36600319011261036057602060405160078152f35b34610360575f3660031901126103605760206040515f8152f35b34610360575f366003190112610360576040515f9060045461090681611f00565b8083526001918083169081156109a05750600114610947575b6109438361092f8187038261202e565b604051918291602083526020830190611e59565b0390f35b60045f90815260209450917f8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19b5b82841061098d5750505081019091019061092f8161091f565b8054858501870152928501928101610974565b610943955061092f93506020915091849260ff191682840152151560051b820101935061091f565b61208d565b34610360576020366003190112610360576004355f526015602052608060405f2060018060a01b036001600160401b03818354169260026001820154910154926040519485528116602085015260a01c1660408301526060820152f35b3461036057604036600319011261036057610a43611eb2565b6004355f52600560205260405f209060018060a01b03165f52602052602060ff60405f2054166040519015158152f35b34610360575f366003190112610360576020600c54604051908152f35b610a99366120d6565b610aa33415612984565b610aab6129b8565b60ff60125460081c16610cf157610ac3831515612a77565b6001600160a01b0391808316610ada811515612a0e565b838316928315610cc45785610b37817f1dfdc691a597a83af2430bd31addf8d2129d012eab8b35de6d696dc91e64852393610b2866038d7ea4c68000610b2160209c612697565b1015612a45565b873303610cb4575b30906123ad565b601154958693610b4685612aac565b601155601054906001600160401b039283831692835f5260168c528460405f2094855460ff811615908115610ca6575b50610c68575b506002929150601054169260405193610b9485611fc9565b8b85528d85019182526040850190815260608501928884528a5f5260158f528060405f2096511660018060a01b0319875416178655600186019251168254916001600160401b0360a01b905160a01b169163ffffffff60e01b16171790555191015560018101610c058482546123a0565b905580546affffffffffffffff000000610c23848360181c16612aba565b60181b166affffffffffffffff0000001991909116179055601054604080516001600160a01b0397909716875260208701939093521693a46001600655604051908152f35b610c759192939550612aba565b1680916001600160401b031916176010555f5260168a52600260405f2092600160ff1985541617845590848d610b7c565b60ff915060081c168f610b76565b610cbf823383612605565b610b30565b60405162461bcd60e51b815260206004820152600560248201526427aba722a960d91b6044820152606490fd5b60405162461bcd60e51b815260206004820152600860248201526715d7d4105554d15160c21b6044820152606490fd5b34610360575f366003190112610360576020600f54604051908152f35b34610360575f366003190112610360576020601454604051908152f35b34610360575f36600319011261036057602060ff600a5416604051908152f35b612073565b34610360576020366003190112610360576001600160a01b03610da1611e9c565b165f525f602052602060405f2054604051908152f35b34610360575f366003190112610360576020601154604051908152f35b34610360575f36600319011261036057602060ff601254166040519015158152f35b5f36600319011261036057610e0b3415612984565b610e136129b8565b6020610e1d615687565b6001600655604051908152f35b604036600319011261036057610e3e611eb2565b610e483415612984565b610e506129b8565b6004355f52601560205260405f2060405190610e6b82611fc9565b80546001600160a01b039081168084526001830154918216602085015260a09190911c6001600160401b031660408401526002919091015460608301521561129e576001600160401b036040820151165f52601660205260405f2080549260ff8460101c161561126d576001600160a01b038116611268575060208201516001600160a01b03165b60208301516001600160a01b039081169082160361123a57610f206001600160401b038560581c16612aba565b6001600160401b03808660181c169116145f1461121c57610f4a6005830154600684015490612ad2565b935b67ffffffffffffffff60581b610f6e605883901c6001600160401b0316612aba565b60581b16906001600160401b0360581b191617825560068201610f928582546123a0565b9055610fa084600e54612ad2565b600e55610faf84600f54612ad2565b600f556004355f908152601560209081526040808320838155600181018490556002018390555163a9059cbb60e01b9181019182526001600160a01b038481166024830152604480830189905282526110829391927f00000000000000000000000000000000000000000000000000000000000000009091169190819061103760648661202e565b6040519461104486612013565b602086527f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c65646020870152519082855af161107c612b4b565b91612b7a565b80519081159182156111fa575b5050156111a2576001600160401b036040840151166040519185835260018060a01b0316917f6443ab78e20802b0a48c253c5a7e05576a8bedd83aec398a8bba1164e7cf6fc3602060043592a4546001600160401b03808260181c169160581c1614611106575b6020826001600655604051908152f35b60406001600160401b03910151165f52601660205260405f20905f82555f60018301555f600283015560086003925f848201555f60048201555f60058201555f60068201555f6007820155018054905f815581611166575b5050906110f6565b8184029184830403610608575f5260205f20908101905b8181101561115e57806111908592612af5565b5f60018201555f60028201550161117d565b60405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b6064820152608490fd5b81925090602091810103126103605760206112159101612b3e565b858061108f565b611234606084015160058401546001850154916128f0565b93610f4c565b60405162461bcd60e51b815260206004820152600660248201526514105653d55560d21b6044820152606490fd5b610ef3565b60405162461bcd60e51b8152602060048201526009602482015268434c41494d41424c4560b81b6044820152606490fd5b60405162461bcd60e51b8152602060048201526007602482015266149154555154d560ca1b6044820152606490fd5b34610360576020366003190112610360576004356001600160401b03808216809203610360576101a0915f52601660205260405f2090815491600181015490600281015460038201546004830154906005840154926007600686015495015495876040519960ff811615158b5260ff8160081c16151560208c015260ff8160101c16151560408c0152818160181c1660608c015260581c1660808a015260a089015260c088015260e08701526101008601526101208501526101408401528060070b61016084015260401c16610180820152f35b34610360575f36600319011261036057602060405166038d7ea4c680008152f35b611e7e565b34610360575f3660031901126103605760206107b2615740565b34610360575f366003190112610360576020600b54604051908152f35b34610360575f366003190112610360576020601354604051908152f35b3461036057604036600319011261036057610802611437611e9c565b335f52600160205260405f2060018060a01b0382165f5260205261146160243560405f20546123a0565b9033612507565b346103605760203660031901126103605760043560ff81168103610360573033036114a257611498602091612cd6565b6040519015158152f35b606460405162461bcd60e51b815260206004820152600460248201526329a2a62360e11b6044820152fd5b34610360575f366003190112610360576040517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b346103605760403660031901126103605761152a611eb2565b336001600160a01b038216036115465761073b9060043561232b565b60405162461bcd60e51b815260206004820152602f60248201527f416363657373436f6e74726f6c3a2063616e206f6e6c792072656e6f756e636560448201526e103937b632b9903337b91039b2b63360891b6064820152608490fd5b3461036057602036600319011261036057600435600754811015610360576115cd61160f91611ec8565b50604051906115e7826115e08184611f38565b038361202e565b60018101549060ff600360028301549201541690604051948594608086526080860190611e59565b9260208501526040840152151560608301520390f35b6020806003193601126103605761163a611e9c565b6116426129b8565b60ff601254166117c6576001600160a01b03168015906116628215612a0e565b61167566038d7ea4c68000341015612a45565b61167d614b6d565b50600254611689612964565b81611768575034925b61169d841515612a77565b6116a934600b546123a0565b600b5561172357826116ba916123a0565b600255805f525f835260405f20828154019055805f5f805160206158ea83398151915285604051868152a360405134815282848201527fdcbc1c05240f31ff3ad067ef1ee35ce4997762752e3a095284754544f4c709d760403392a36001600655604051908152f35b60405162461bcd60e51b815260048101859052601f60248201527f45524332303a206d696e7420746f20746865207a65726f2061646472657373006044820152606490fd5b801561179557600182019081831161060857600181018091116106085761178f91346128f0565b92611692565b60405162461bcd60e51b8152600481018690526009602482015268125394d3d31591539560ba1b6044820152606490fd5b60405162461bcd60e51b81526004810183905260086024820152671117d4105554d15160c21b6044820152606490fd5b34610360575f366003190112610360576020600d54604051908152f35b6060366003190112610360576004358015158091036103605760248035908115158092036103605760443590811515809203610360576118533415612984565b335f9081527f99f2891db7a8db76249871a02b43b68c0bb8f22ef8a3bc1034ae9375ef6b7c3c60209081526040909120547f65d7a28e3265b37a6474929f336521b332c1681b933f6cb9f3376673440d862a9060ff16156118db5750505061ff009060ff62ff00006012549260101b1694169062ffffff1916179160081b1617176012555f80f35b6118e4336126f1565b906040516118f181611fc9565b60428152838101916060368437815115611a4957603083538151600190811015611a3657607860218401536041905b8082116119cd57505061199e57604861059193611973936119829360405195869376020b1b1b2b9b9a1b7b73a3937b61d1030b1b1b7bab73a1604d1b8a860152610538815180928c603789019101611e38565b0103602881018452018261202e565b60405193849362461bcd60e51b85526004850152830190611e59565b606485856040519162461bcd60e51b83528160048401528201525f805160206158aa8339815191526044820152fd5b9091600f81166010811015611a23576f181899199a1a9b1b9c1cb0b131b232b360811b901a6119fc84866126e0565b5360041c918015611a10575f190190611920565b87634e487b7160e01b5f5260116004525ffd5b88634e487b7160e01b5f5260326004525ffd5b86634e487b7160e01b5f5260326004525ffd5b85634e487b7160e01b5f5260326004525ffd5b602036600319011261036057611a723415612984565b611a7a6129b8565b6020610e1d600435612c13565b34610360575f3660031901126103605760ff7f00000000000000000000000000000000000000000000000000000000000000001660ff811161060857602090604051908152f35b3461036057604036600319011261036057600435611aea611eb2565b815f526005602052611b02600160405f20015461218c565b815f52600560205260405f209060018060a01b031690815f5260205260ff60405f20541615611b2d57005b815f52600560205260405f20815f5260205260405f20600160ff1982541617905533917f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d5f80a4005b34610360575f3660031901126103605760206107b261373d565b34610360575f36600319011261036057602060ff60125460101c166040519015158152f35b34610360576020366003190112610360576004355f5260056020526020600160405f200154604051908152f35b3461036057606036600319011261036057610802611bfe611e9c565b611c06611eb2565b60443591611c15833383612605565b6123ad565b34610360575f36600319011261036057602060405160088152f35b34610360575f366003190112610360576020600254604051908152f35b34610360575f366003190112610360576020600754604051908152f35b34610360575f3660031901126103605760206001600160401b0360105416604051908152f35b34610360576020366003190112610360576002546001810180911161060857611cbc612964565b9060018201809211610608576020916107b2916004356127e9565b3461036057604036600319011261036057610802611cf3611e9c565b6024359033612507565b34610360575f366003190112610360576040515f90600354611d1e81611f00565b8083526001918083169081156109a05750600114611d46576109438361092f8187038261202e565b60035f90815260209450917fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b5b828410611d8c5750505081019091019061092f8161091f565b8054858501870152928501928101611d73565b34610360575f3660031901126103605760206001600160401b0360105460401c16604051908152f35b346103605760203660031901126103605760043563ffffffff60e01b811680910361036057602090637965db0b60e01b8114908115611e0d575b506040519015158152f35b6301ffc9a760e01b14905082611e02565b34610360575f3660031901126103605760206107b2612964565b5f5b838110611e495750505f910152565b8181015183820152602001611e3a565b90602091611e7281518092818552858086019101611e38565b601f01601f1916010190565b346103605760203660031901126103605760206107b2600435612697565b600435906001600160a01b038216820361036057565b602435906001600160a01b038216820361036057565b60075481101561061c5760075f5260205f209060021b01905f90565b60085481101561061c5760085f5260205f209060021b01905f90565b90600182811c92168015611f2e575b6020831014611f1a57565b634e487b7160e01b5f52602260045260245ffd5b91607f1691611f0f565b905f9291805491611f4883611f00565b9182825260019384811690815f14611fa65750600114611f69575b50505050565b90919394505f52602092835f2092845f945b838610611f9257505050500101905f808080611f63565b805485870183015294019385908201611f7b565b9294505050602093945060ff191683830152151560051b0101905f808080611f63565b608081019081106001600160401b03821117611fe457604052565b634e487b7160e01b5f52604160045260245ffd5b60a081019081106001600160401b03821117611fe457604052565b604081019081106001600160401b03821117611fe457604052565b90601f801991011681019081106001600160401b03821117611fe457604052565b3461036057602036600319011261036057612068611e9c565b5060206040515f8152f35b34610360575f366003190112610360576020604051818152f35b34610360576040366003190112610360576120a6611eb2565b5060405162461bcd60e51b815260206004820152600760248201526611115413d4d25560ca1b6044820152606490fd5b606090600319011261036057600435906001600160a01b03906024358281168103610360579160443590811681036103605790565b3461036057612119366120d6565b505060405162461bcd60e51b815260206004820152600660248201526552454445454d60d01b604482015260649150fd5b34610360576020366003190112610360576002546001810180911161060857612171612964565b9060018201809211610608576020916107b2916004356128f0565b5f9080825260209060058252604092838120338252835260ff8482205416156121b55750505050565b6121be336126f1565b918451906121cb82611fc9565b6042825284820192606036853782511561231757603084538251906001918210156123175790607860218501536041915b8183116122aa5750505061227b57604861059193869361225f93612250985198899376020b1b1b2b9b9a1b7b73a3937b61d1030b1b1b7bab73a1604d1b8a860152610538815180928c603789019101611e38565b0103602881018752018561202e565b5192839262461bcd60e51b845260048401526024830190611e59565b60648486519062461bcd60e51b825280600483015260248201525f805160206158aa8339815191526044820152fd5b909192600f81166010811015612303576f181899199a1a9b1b9c1cb0b131b232b360811b901a6122da85876126e0565b5360041c9280156122ef575f190191906121fc565b634e487b7160e01b82526011600452602482fd5b634e487b7160e01b83526032600452602483fd5b634e487b7160e01b81526032600452602490fd5b905f918083526005602052604083209160018060a01b03169182845260205260ff60408420541661235b57505050565b8083526005602052604083208284526020526040832060ff1981541690557ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b339380a4565b9190820180921161060857565b6001600160a01b039081169182156124b45716918215612463575f8281528060205260408120549180831061240f57604082825f805160206158ea833981519152958760209652828652038282205586815220818154019055604051908152a3565b60405162461bcd60e51b815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e7420657863656564732062604482015265616c616e636560d01b6064820152608490fd5b60405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b6064820152608490fd5b60405162461bcd60e51b815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604482015264647265737360d81b6064820152608490fd5b6001600160a01b039081169182156125b457169182156125645760207f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591835f526001825260405f20855f5282528060405f2055604051908152a3565b60405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b6064820152608490fd5b60405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608490fd5b9060018060a01b038083165f52600160205260405f209082165f5260205260405f2054925f1984036126375750505050565b80841061265257612649930391612507565b5f808080611f63565b60405162461bcd60e51b815260206004820152601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e63650000006044820152606490fd5b61269f612964565b60018101809111610608576002549060018201809211610608576126c2926128f0565b90565b6001600160401b038111611fe457601f01601f191660200190565b90815181101561061c570160200190565b60405190606082018281106001600160401b03821117611fe457604052602a825260208201604036823782511561061c5760309053815160019081101561061c57607860218401536029905b80821161277e57505061274d5790565b606460405162461bcd60e51b815260206004820152602060248201525f805160206158aa8339815191526044820152fd5b9091600f811660108110156127d5576f181899199a1a9b1b9c1cb0b131b232b360811b901a6127ad84866126e0565b5360041c9180156127c1575f19019061273d565b60245f634e487b7160e01b81526011600452fd5b60245f634e487b7160e01b81526032600452fd5b91906127f68282856128f0565b92821561281357096128055790565b600181018091116106085790565b634e487b7160e01b5f52601260045260245ffd5b90670de0b6b3a7640000905f1982840992828102928380861095039480860395146128e257848311156128a55782910960018219018216809204600280826003021880830282030280830282030280830282030280830282030280830282030280920290030293600183805f03040190848311900302920304170290565b60405162461bcd60e51b81526020600482015260156024820152744d6174683a206d756c446976206f766572666c6f7760581b6044820152606490fd5b505080925015612813570490565b915f1982840992828102928380861095039480860395146128e257848311156128a55782910960018219018216809204600280826003021880830282030280830282030280830282030280830282030280830282030280920290030293600183805f03040190848311900302920304170290565b60025415612980576126c2600b5461297a615740565b906123a0565b5f90565b1561298b57565b60405162461bcd60e51b815260206004820152600560248201526456414c554560d81b6044820152606490fd5b6002600654146129c9576002600655565b60405162461bcd60e51b815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c006044820152606490fd5b15612a1557565b60405162461bcd60e51b81526020600482015260086024820152672922a1a2a4ab22a960c11b6044820152606490fd5b15612a4c57565b60405162461bcd60e51b815260206004820152600360248201526226a4a760e91b6044820152606490fd5b15612a7e57565b60405162461bcd60e51b815260206004820152600660248201526553484152455360d01b6044820152606490fd5b5f1981146106085760010190565b9060016001600160401b038093160191821161060857565b9190820391821161060857565b818110612aea575050565b5f8155600101612adf565b612aff8154611f00565b9081612b09575050565b81601f5f9311600114612b1b5750555b565b908083918252612b3a601f60208420940160051c840160018501612adf565b5555565b5190811515820361036057565b3d15612b75573d90612b5c826126c5565b91612b6a604051938461202e565b82523d5f602084013e565b606090565b91929015612bdc5750815115612b8e575090565b3b15612b975790565b60405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606490fd5b825190915015612bef5750805190602001fd5b60405162461bcd60e51b815260206004820152908190610591906024830190611e59565b5f9060ff60125460101c16612ca6578015612ca057612c59612c53612c4d612c47612c41612c5f9686613789565b8561384a565b846138d2565b8361395a565b826139e1565b90613a69565b90612c6b601354612aac565b601355816014556040518281527f4889d7d38c1d1f265318505a98367d07d41591d6b7f9cc000f37ea92b8311daa60203392a2565b50505f90565b60405162461bcd60e51b81526020600482015260086024820152671417d4105554d15160c21b6044820152606490fd5b60ff16600181146136a05760028114613697576003811461368e5760048114612d1d5760058114612d1457600614612d0c575f90565b6126c26149ec565b506126c26146f9565b50604051612d2a81611ff8565b604051600954815f612d3b83611f00565b808352926001811690811561366f5750600114613630575b612d5f9250038261202e565b81525f6020820152602060408201525f60608201525f60808201525f60405180809363186b216760e01b82526040600483015260126044830152711093d39117d4d510551554d7d093d391115160721b6064830152608060248301526080612dd4825160a06084860152610124850190611e59565b916001600160401b0360208201511660a48501526001600160401b0360408201511660c48501526060810151151560e48501520151151561010483015203816108005afa918215613625575f91829361324e575b505f5b8251811015612edc57612e3e81846146e5565b5190604082015115801590612eb0575b8015612ea4575b612e9a5760808201519161012081015190670de0b6b3a764000091820180921161060857612e89612e9092612e9595612827565b9051614e0b565b612aac565b612e2b565b612e959150612aac565b50608082015115612e55565b5060608201516004811015612ec85760031415612e4e565b634e487b7160e01b5f52602160045260245ffd5b50909180519283516001600160401b038111611fe457612efd600954611f00565b601f81116131f5575b50602094601f8211600114613191579481929394955f92613186575b50508160011b915f199060031b1c1916176009555b81515115612f56575b511590811591612f4e575090565b905051511590565b9190612f60615687565b505f5b600754811015612f9f57806003612f7c612f9a93611ec8565b5001805460ff191690555f6001612f9283611ec8565b500155612aac565b612f63565b50905f5b60085481101561306a57612fd8612fcc612fd3612fbf84611ee4565b5060405192838092611f38565b038261202e565b615863565b90156130185790816003612fee61301394611ec8565b5001600160ff198254161790556001612f928161300a85611ee4565b50015492611ec8565b612fa3565b50600790815460208110613032575b506130139150612aac565b61303b82611ee4565b50600160401b821015611fe45761305e8261301395600161306495019055611ec8565b90615067565b5f613027565b5090915f915b60078054808510156131425760ff600361308987611ec8565b50015416158061312d575b1561311b575f1990808201908111610608576130b26130bc91611ec8565b5061305e87611ec8565b815480156131075701906130cf82611ec8565b6130f4576003816130e05f93612af5565b826001820155826002820155015555613070565b634e487b7160e01b5f525f60045260245ffd5b634e487b7160e01b5f52603160045260245ffd5b50509161312790612aac565b91613070565b50600261313986611ec8565b50015415613094565b505092915061314f6136a9565b7f17e14f5a0c6831022cc1f0f72d2d717ac74633f5193b19f76e18d9711b6d7cec602061317a61373d565b604051908152a1612f40565b015190505f80612f22565b601f1982169560095f5260205f20915f5b8881106131dd575083600195969798106131c5575b505050811b01600955612f37565b01515f1960f88460031b161c191690555f80806131b7565b919260206001819286850151815501940192016131a2565b60095f5261323e907f6e1540171b6c0c960b71a7020d9f60077f6af931a8bbf590da0223dacf75c7af601f840160051c81019160208510613244575b601f0160051c0190612adf565b5f612f06565b9091508190613231565b919092503d8082843e613261818461202e565b6040838281010312613364578251916001600160401b03831161335d57818401601f84860101121561335d578284015161329a8161466b565b936132a8604051958661202e565b818552602085019084870160208460051b838a0101011161336057602081880101915b60208460051b838a0101018310613368575050505060208401516001600160401b038111613364578401916040838287010312613364576040519461330f86612013565b83516001600160401b03811161336057840192828201601f8501121561335d575060209261335094926133489201908481519101614682565b8552016146d1565b602083015290915f612e28565b80fd5b8380fd5b5080fd5b82516001600160401b038111613621576101608984018201888b0103601f1901126136215760405190816101608101106001600160401b036101608401111761360d576101608201604052602081858c010101516001600160401b038111613609578a60206133e09284888d850194010101016146b7565b8252604081858c010101516001600160401b038111613609578a602061340f9284888d850194010101016146b7565b6020830152613424606082868d010101612b3e565b6040830152608081858c010101516004811015613609576060830152898401810160a081810151608085015260c08201519084015260e001516001600160401b0381116136095760a0858c01830182018a8d0103601f190112613609576040519060208184888f61349487611ff8565b01010101516001600160401b038111613605578184888f6134c68f9560206040978401918787878701010101016146b7565b875201010101516001600160401b038111613605578184888f6134fa8f9560206060978401918787878701010101016146b7565b602088015201010101516001600160401b038111613605578184888f6135318f9560206080978401918787878701010101016146b7565b604088015201010101516001600160401b038111613605578184888f6135688f95602060a0978401918787878701010101016146b7565b60608801520101010151906001600160401b0382116136055761016084602097948f938a8f8b9a976135a6938c9b8c938a01948a01010101016146b7565b608082015260c085015288610100936135c4858484840101016145e2565b60e0870152610120946135dc868585850101016145e2565b9087015261014094858484840101015190870152010101519082015281520193019290506132cb565b8980fd5b8780fd5b634e487b7160e01b87526041600452602487fd5b8580fd5b6040513d5f823e3d90fd5b505060095f52816020805f20925f905b8082106136555750612d5f9350820101612d53565b845482870184015260019094019385935090820190613640565b60209250612d5f94915060ff191682840152151560051b820101612d53565b506126c2614b6d565b506126c2613af1565b506126c261422e565b6008545f80600855816136ba575050565b6001600160fe1b038216820361372957600881527ff3f7a9fe364faab93b216da50a3214154f22a0a2b415b23a84c8169e8b636ee3600292831b810192905b8381106137065750505050565b80613712600492612af5565b8360018201558383820155836003820155016136f9565b634e487b7160e01b81526011600452602490fd5b5f905f600754905b818110613750575050565b60ff600361375d83611ec8565b50015416613774575b61376f90612aac565b613745565b9261378161376f91612aac565b939050613766565b8110156126c257604051630e47b40f60e21b8152600160048201526020905f90828160248185305af182918161380f575b506137f55750505f805160206158ca8339815191526001916137da612b4b565b906137ef604051928284938452830190611e59565b0390a290565b909150613800575090565b60018201809211613729575090565b9091508381813d8311613843575b613827818361202e565b8101031261383f5761383890612b3e565b905f6137ba565b8280fd5b503d61381d565b8110156126c257604051630e47b40f60e21b8152600260048201526020905f90828160248185305af182918161389b575b506137f55750505f805160206158ca8339815191526002916137da612b4b565b9091508381813d83116138cb575b6138b3818361202e565b8101031261383f576138c490612b3e565b905f61387b565b503d6138a9565b8110156126c257604051630e47b40f60e21b8152600360048201526020905f90828160248185305af1829181613923575b506137f55750505f805160206158ca8339815191526003916137da612b4b565b9091508381813d8311613953575b61393b818361202e565b8101031261383f5761394c90612b3e565b905f613903565b503d613931565b8110156126c257604051630e47b40f60e21b81526004808201526020905f90828160248185305af18291816139aa575b506137f55750505f805160206158ca8339815191526004916137da612b4b565b9091508381813d83116139da575b6139c2818361202e565b8101031261383f576139d390612b3e565b905f61398a565b503d6139b8565b8110156126c257604051630e47b40f60e21b8152600560048201526020905f90828160248185305af1829181613a32575b506137f55750505f805160206158ca8339815191526005916137da612b4b565b9091508381813d8311613a62575b613a4a818361202e565b8101031261383f57613a5b90612b3e565b905f613a12565b503d613a40565b8110156126c257604051630e47b40f60e21b8152600660048201526020905f90828160248185305af1829181613aba575b506137f55750505f805160206158ca8339815191526006916137da612b4b565b9091508381813d8311613aea575b613ad2818361202e565b8101031261383f57613ae390612b3e565b905f613a9a565b503d613ac8565b6001600160401b03601054165f52601660205260405f20805460ff811615908115614220575b508015614214575b61420f57613b2b614b6d565b50600181015490613b3b82612697565b9182156142085761010061ffff1983541617825582600283015530156141b957305f525f60205260405f20549080821061416957805f923084528360205203604083205580600254036002556040519081525f805160206158ea83398151915260203092a3613bc0600b548381105f1461416257805b818061412b575b505083612ad2565b905f9180613c80575b50613c3e6001827f5d78323b7b570071308d257743c74a9a2401f77d37e9de9f908da12c8917a40b606060076001600160401b0396019786199889815416898916179055600484015415613c62575b60105497878916958695015491604051928352602083015260070b6040820152a2612aba565b1691829116176010555f52601660205260405f20600160ff19825416179055600190565b835462ff000019166201000017845560038401546005850155613c18565b9150925f91600754156140f957613c95615687565b50905f5b6007548110806140f0575b156140ae57613cb281611ec8565b5060028101548015801561408b575b614080578481101561407857905b604051630fb6accf60e21b81529060208280613cf0868530600485016145f0565b03815f6108005af15f928161403c575b50613d1657505050613d1190612aac565b613c99565b613d7e81613d2f856002612fcc959a97989a0154612ad2565b6002820155600c613d41878254612ad2565b9055600d613d508782546123a0565b9055600e613d5f8782546123a0565b905560048b01613d708782546123a0565b905560405192838092611f38565b5f6080604051613d8d81611ff8565b606081528260208201528260408201528260608201520152613def5f60405192613db684611ff8565b8084528660208501528160408501528460070b60608501528160808501526040518093819263a03ffee160e01b8352306004840161545d565b03816108005afa5f9181614018575b50613f70575b506008890154600160401b811015611fe457806001613e2c920160088c015560088b0161461a565b6130f45781518051906001600160401b038211611fe457613e5782613e518554611f00565b85614633565b602090601f8311600114613f075760029392915f9183613efc575b50508160011b915f199060031b1c19161781555b602083015160018201550190604081015182546001600160401b03606084015160401b93608082811b91015160801b16938160401b169216906001600160401b0360c01b1617171790558560070b8160070b13613ef2575b50613d1191613eec91612ad2565b92612aac565b9450613d11613ede565b015190505f80613e72565b90835f5260205f20915f5b601f1985168110613f58575091839160019360029695601f19811610613f40575b505050811b018155613e86565b01515f1960f88460031b161c191690555f8080613f33565b91926020600181928685015181550194019201613f12565b9790969195945f5b60408a015180518210156140095790613f9481613fb3936146e5565b518960070b602082015160070b1480613fe5575b613fb8575b50612aac565b613f78565b606081015160208c0152805160070b60408c01526080908101516001600160401b0316908b01525f613fad565b506001600160401b036080820151166001600160401b0360808d0151161115613fa8565b5050949591969097505f613e04565b6140359192503d805f833e61402d818361202e565b8101906152fa565b905f613dfe565b9092506020813d602011614070575b816140586020938361202e565b8101031261036057614069906145e2565b915f613d00565b3d915061404b565b508390613ccf565b5050613d1190612aac565b5060076140a76040516140a281612fcc8188611f38565b6155cb565b1015613cc1565b509391906140bf5790613c3e613bc9565b60405162461bcd60e51b81526020600482015260096024820152681111531151d055115160ba1b6044820152606490fd5b50821515613ca4565b60405162461bcd60e51b815260206004820152600a60248201526956414c494441544f525360b01b6044820152606490fd5b61413491612ad2565b600b5561414381600e546123a0565b600e5561415281600f546123a0565b600f558060038401555f81613bb8565b8390613bb1565b60405162461bcd60e51b815260206004820152602260248201527f45524332303a206275726e20616d6f756e7420657863656564732062616c616e604482015261636560f01b6064820152608490fd5b60405162461bcd60e51b815260206004820152602160248201527f45524332303a206275726e2066726f6d20746865207a65726f206164647265736044820152607360f81b6064820152608490fd5b5050505f90565b505f90565b50600181015415613b1f565b60ff915060081c165f613b17565b6001600160401b036010805482604091821c16935b825493808616908086168210156145d7575f93828552602091601683528186209081549860ff9960089a80828d1c16159182156145cb575b505080156145bf575b6145775750600782018181541642106145445780548281861c16156144d9575054831c164311156144a9579497600197860196899691875b89548c1015614354578b6142d96142d3828d61461a565b506154a1565b90614308575b506143029160016142f36142fc938e61461a565b500154906123a0565b9b612aac565b9a6142bc565b9b50908a908a9c614319848461461a565b50600101548110614302946142fc946001936142f393614340575b505093505050916142df565b8461434b848461461a565b5001555f614334565b929598509295995092959760048501918254808210614477575b50501561444857906143f77f73f330dd18a8f4760be8fa04b392ba243a76dcbe07487724e846182d63dbfafa97986144139354906143aa6157be565b9050808210156144425750805b600d6143c4828254612ad2565b90556143dc826143d7600e938454612ad2565b6123a0565b9055600f6143eb8282546123a0565b905560038601546123a0565b60058501908155845462ff000019166201000017909455612aba565b815467ffffffffffffffff60401b191690841b67ffffffffffffffff60401b16179055549051908152a2600190565b906143b7565b50815467ffffffffffffffff60401b1916931b67ffffffffffffffff60401b1692909217909155509192915050565b8161448191612ad2565b908355600d614491828254612ad2565b90556144a0600e918254612ad2565b90555f8061436e565b50855467ffffffffffffffff60401b191697901b67ffffffffffffffff60401b1696909617909355909350915050565b919599509396507f5e49965d9b92858198b2c5bab7c3a3fa70fcaf004a265239d97b798b708646ce9792959991506001600160401b0360401b6001600160401b0360401b19918282438a1b169116178555835492871b16911617905554821c169051908152a2600190565b5050865467ffffffffffffffff60401b19169890911b67ffffffffffffffff60401b169790971790945550909350915050565b9350975050935085929691506001600160401b0360401b61459783612aba565b67ffffffffffffffff60401b1990921691851b16178455168381146127c15760010193614243565b50600483015415614284565b8b1c1690505f8061427b565b505093505050505f90565b51908160070b820361036057565b9392916146159060409260018060a01b03168652606060208701526060860190611f38565b930152565b805482101561061c575f52600360205f20910201905f90565b9190601f811161464257505050565b612b19925f5260205f20906020601f840160051c8301931061324457601f0160051c0190612adf565b6001600160401b038111611fe45760051b60200190565b9092919261468f816126c5565b9161469d604051938461202e565b829482845282820111610360576020612b19930190611e38565b9080601f830112156103605781516126c292602001614682565b51906001600160401b038216820361036057565b805182101561061c5760209160051b010190565b6002806007541061420f5761470c615687565b506007545f80805b83811061499e57508115908115614992575b506142085790614735826150b4565b915f94919490819482938390600c54915b8381106148de5750505050159081156148d5575b5080156148cc575b6148c357808210156148bb5750915b66038d7ea4c6800083106148b35761478b61479291611ec8565b5091611ec8565b50916040516354b826f560e01b815230600482015260806024820152602081806147d46147c26084830188611f38565b82810360031901604484015288611f38565b85606483015203815f6108005af15f9181614878575b506147f85750505050505f90565b61486261485494867f2a3f2dce4ae3c46ff8fd3dbcf9cac1ca39da18b59d0ac57b2047d061ec99c1cb97860161482f868254612ad2565b9055810161483e8582546123a0565b9055604051958695608087526080870190611f38565b908582036020870152611f38565b91604084015260070b60608301520390a1600190565b90916020823d82116148ab575b816148926020938361202e565b8101031261335d57506148a4906145e2565b905f6147ea565b3d9150614885565b505050505f90565b905091614771565b50505050505f90565b50828414614762565b9050155f61475a565b60ff60036148eb83611ec8565b5001541615614989576148fd81611ec8565b5061490e83600180930154866128f0565b908c898161491b86611ec8565b50015484119182614969575b505061493e575b505061493990612aac565b614746565b929650985095509361493961496187998c6149588a611ec8565b50015490612ad2565b96905f61492e565b61498191925061497886611ec8565b50015484612ad2565b11898e614927565b61493990612aac565b9050600c54105f614726565b60ff60036149ab83611ec8565b500154166149c2575b6149bd90612aac565b614714565b90916149e46149d96149bd9260016142f386611ec8565b93866142f385611ec8565b9190506149b4565b600254158015614b5b575b61298057614a03615687565b505f80600754905b818110614b1c575050801561420f57614a34600c5491614a2e600b5480946123a0565b906151f6565b909291156142085780614b175750805b80821015614b0f5750905b8115612ca057614a5e90611ec8565b50906040516353266bbb60e01b815260208180614a80858730600485016145f0565b03815f6108005af15f9181614ad4575b50614a9c575050505f90565b15612ca0576002614acc92614ab383600b54612ad2565b600b5501614ac28282546123a0565b9055600c546123a0565b600c55600190565b90916020823d8211614b07575b81614aee6020938361202e565b8101031261335d5750614b0090612b3e565b905f614a90565b3d9150614ae1565b905090614a4f565b614a44565b60ff6003614b2983611ec8565b50015416614b40575b614b3b90612aac565b614a0b565b91614b53614b3b9160016142f386611ec8565b929050614b32565b5066038d7ea4c68000600b54106149f7565b600754158015614cff575b612980576040516370a0823160e01b8082523060048301527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906020908184602481865afa938415613625575f94614cd0575b5063ffffffff6007541660405190632efe8a5f60e01b8252306004830152602482015282816044815f6108015af15f9181614c99575b50614c185750505050505f90565b156148b357819060246040518095819382523060048301525afa908115613625575f91614c6d575b509050818111614c505750505f90565b614c6591614c5d91612ad2565b600b546123a0565b600b55600190565b82813d8311614c92575b614c81818361202e565b8101031261335d575051805f614c40565b503d614c77565b90918482813d8311614cc9575b614cb0818361202e565b8101031261335d5750614cc290612b3e565b905f614c0a565b503d614ca6565b90938282813d8311614cf8575b614ce7818361202e565b8101031261335d575051925f614bd4565b503d614cdd565b50614d08615687565b15614b78565b909291926130f45782519283516001600160401b038111611fe457614d3781613e518554611f00565b6020601f8211600114614da1579181606092600394612b1997985f92614d96575b50508160011b915f1990861b1c19161784555b60208101516001850155604081015160028501550151151591019060ff801983541691151516179055565b015190505f80614d58565b601f19821695845f52815f20965f5b818110614df3575092612b199697600395936001938360609710614ddc575b505050811b018455614d6b565b01515f1983881b60f8161c191690555f8080614dcf565b83830151895560019098019760209384019301614db0565b908015614f8d575f5b600854811015614e8357614e4483612fcc614e31612fbf85611ee4565b6020815191012090602081519101201490565b614e5657614e5190612aac565b614e14565b909150614e6281611ee4565b50906001809201548311614e7557505050565b614e7e90611ee4565b500155565b509060085460ff600a54168110614f4c5750600854915f831561231757600881527ff3f7a9fe364faab93b216da50a3214154f22a0a2b415b23a84c8169e8b636ee454600194855b818110614f125750508211614ee05750505050565b61264993614f0c9260405194614ef586611fc9565b855260208501525f60408501526060840152611ee4565b90614d0e565b8287614f1d83611ee4565b50015410614f34575b614f2f90612aac565b614ecb565b9250905084614f4283611ee4565b5001549082614f26565b9160405191614f5a83611fc9565b825260208201525f604082015260016060820152600160401b821015611fe457614f0c826001612b199401600855611ee4565b5050565b90808214614f8d57614fa38154611f00565b906001600160401b038211611fe4578190614fc882614fc28654611f00565b86614633565b5f90601f8311600114614ffc575f92614ff1575b50508160011b915f199060031b1c1916179055565b015490505f80614fdc565b81526020808220858352818320935090601f1985169083905b82821061504e575050908460019594939210615036575b505050811b019055565b01545f1960f88460031b161c191690555f808061502c565b8495819295850154815560018091019601940190615015565b906130f457818103615077575050565b600360ff818461508a612b199686614f91565b600181015460018601556002810154600286015501541691019060ff801983541691151516179055565b600754915f5b83811061519857505f809381938290600c54915b8381106150de5750505050929190565b60ff60036150eb83611ec8565b500154161561518f576150fd81611ec8565b5061510e83600180930154866128f0565b61511783611ec8565b509160028093015466038d7ea4c68000830190818411610608571180615179575b61514e575b50505061514990612aac565b6150ce565b92995089985091955061514991615170916151688a611ec8565b500154612ad2565b96905f8061513d565b5089615189838561516888611ec8565b11615138565b61514990612aac565b60ff60036151a883969596611ec8565b5001541615806151e0575b6151c8576151c090612aac565b9291926150ba565b9250905060026151d783611ec8565b50015460019291565b5060026151ec82611ec8565b50015415156151b3565b90915f809381938291600754925b83811061525a57505050811561521b575b50929190565b5f5b81811061522a5750615215565b60ff600361523783611ec8565b5001541661524d5761524890612aac565b61521d565b6001955093505f92915050565b60ff600361526783611ec8565b50015416156152f15761527981611ec8565b5061528a84600180930154856128f0565b61529383611ec8565b50916002809301548211806152dc575b6152b9575b5050506152b490612aac565b615204565b9299508998509195506152b4916152d3916149588a611ec8565b96905f806152a8565b50896152eb8461497887611ec8565b116152a3565b6152b490612aac565b90602080838303126103605782516001600160401b039384821161036057019260609283858203126103605760409283519585870187811085821117611fe4578552805184811161036057836153519183016146b7565b875281810151848111610360578361536a9183016146b7565b82880152848101519084821161036057019180601f84011215610360578251936153938561466b565b966153a08751988961202e565b858852838801928460c080980287010195818711610360578501935b8685106153d157505050505050505082015290565b878583031261036057885190888201908282108683111761544957899288928c526153fb886145e2565b81526154088389016145e2565b838201528b8801518c82015285880151868201526080615429818a016146d1565b9082015260a061543a818a016145e2565b908201528152019401936153bc565b60245f634e487b7160e01b81526041600452fd5b6001600160a01b0390911681526040602082018190526126c292910190611e59565b6001600160a01b0390911681526040602082018190526126c292910190611f38565b6040805163a03ffee160e01b81525f939290918483806154c585306004840161547f565b03816108005afa8593816155af575b506154e1575b5050508190565b90919293805b8285015180518210156155a057816154fe916146e5565b5160028501546001600160401b036080918181841c1680159283159485615591575b5050508161557e575b81615567575b50811561555f575b5061554e57506155478391612aac565b90506154e7565b606001516001969095509350505050565b90505f615537565b90506020830151600791871c820b910b145f61552f565b9050825160079082820b910b1490615529565b860151161492505f8080615520565b505093925050505f80806154da565b6155c49194503d8088833e61402d818361202e565b925f6154d4565b5f6155eb916040518093819263a03ffee160e01b8352306004840161545d565b03816108005afa5f918161560c575b5061560457505f90565b604001515190565b61562091923d8091833e61402d818361202e565b905f6155fa565b9190916040818403126103605780519260208201516001600160401b03928382116103605701604081830312610360576040519261566484612013565b81519081116103605760209261567b9183016146b7565b83520151602082015290565b5f905f805b600754811015615738576156bf826156a383611ec8565b506040518093819263120bba7360e11b8352306004840161547f565b03816108005afa839181615712575b506156e9575080826002612f926156e494611ec8565b61568c565b6156e49194602061570c92018051600261570289611ec8565b50015551906123a0565b93612aac565b61572f9192503d8086833e615727818361202e565b810190615627565b9050905f6156ce565b505081600c55565b5f905f80600754915b82811061575557505050565b615762826156a383611ec8565b03816108005afa8391816157a0575b50615786575b5061578190612aac565b615749565b61578191956020615799920151906123a0565b9490615777565b6157b59192503d8086833e615727818361202e565b9050905f615771565b6040516370a0823160e01b81523060048201526020816024817f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165afa908115613625575f91615832575b50615821600b54600f54906123a0565b80821115612ca0576126c291612ad2565b906020823d821161585b575b8161584b6020938361202e565b8101031261335d5750515f615811565b3d915061583e565b60075491905f5b83811061587b57505090505f905f90565b61588e82612fcc614e31612fbf85611ec8565b6158a05761589b90612aac565b61586a565b9250506001919056fe537472696e67733a20686578206c656e67746820696e73756666696369656e74f1190c18d28d5d68156de3ae0f68fd157173bc96def1c9c2fae201841639d0adddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa2646970667358221220800b05ff46a6147d3ec02ecf5b96df769de94c6b17f9db608c34d6c952fb8b5b64736f6c634300081400332f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d", - "deployedBytecode": "0x60806040526004361015610011575f80fd5b5f3560e01c806301e1d11414611e1e57806301ffc9a714611dc857806303e5fa8814611d9f57806306fdde0314611cfd57806307a2d13a146113c2578063095ea7b314611cd75780630a28a47714611c955780630a763da114611c6f5780630f43a67714611c5257806318160ddd14611c355780631f9acd7e14611c1a57806323b872dd14611be2578063248a9ca314611bb5578063291cdb8c14611b905780632d9414b514611b765780632f2ff15d14611ace578063313ce56714611a8757806332145f9014611a5c578063331c138c1461181357806333a73916146117f657806333bb7f911461162557806335aa2e44146115a357806336568abe1461151157806338d52e0f146114cd578063391ed03c14611468578063395093511461141b578063402d267d146106dc5780634221b2c2146113fe5780634864b09d146113e1578063499bafcc146113c75780634cdad506146113c25780634f7b9013146113a1578063559904cb146112cd578063588f134314610e2a5780635ac22fd214610df657806360da3e8314610dd45780636a84a98514610db75780636c68e40314610d7b5780636e553f65146109c857806370a0823114610d80578063714897df14610d7b5780637316a19614610d5b57806377397cdd14610d3e5780637aed3cbb14610d215780637d41c86e14610a9057806380d04de814610a7357806391d1485414610a2a578063937b2581146109cd57806394bf804d146109c857806395d89b41146108e5578063a217fddf146108cb578063a41fcdc3146108b0578063a457c2d71461080d578063a9059cbb146107dc578063ac44da52146107ba578063b3d7f6b914610770578063b460af941461073d578063b9bcb70f14610742578063ba0876521461073d578063c63d75b6146106dc578063c6e6f59214610364578063ce96cb77146106dc578063d547741f146106fe578063d77bb97c146106e1578063d905777e146106dc578063dd62ed3e1461068f578063e63ab1e914610655578063e9f2838e14610630578063ecdcce2414610369578063ef8b30f7146103645763f5b541a614610326575f80fd5b34610360575f3660031901126103605760206040517f97667070c54ef182b0f5858b034beac1b6f3089aa2d3188bb1e8929f4fa9b9298152f35b5f80fd5b61214a565b602080600319360112610360576004359060ff82168092036103605761038f3415612984565b7f97667070c54ef182b0f5858b034beac1b6f3089aa2d3188bb1e8929f4fa9b929805f526005825260405f20335f52825260ff60405f205416156104a2575081151580610498575b1561046c57817fe28c6c3955ed05a471a85214f10fe95cb125572d2aadeb008a7c5992533984969260ff19600a541617600a556104126136a9565b61041d600954611f00565b8061042d575b50604051908152a1005b601f811160011461044457505f6009555b83610423565b5f9060098252610462601f858420920160051c820160018301612adf565b816009555561043e565b6064906040519062461bcd60e51b82526004820152600560248201526410d3d5539560da1b6044820152fd5b50808211156103d7565b906104ac336126f1565b916040516104b981611fc9565b6042815282810191606036843781511561061c5760308353815160019081101561061c57607860218401536041905b8082116105c557505061059557610561936105709260489260405196879376020b1b1b2b9b9a1b7b73a3937b61d1030b1b1b7bab73a1604d1b88860152610538815180928a603789019101611e38565b8401917001034b99036b4b9b9b4b733903937b6329607d1b603784015251809386840190611e38565b0103602881018552018361202e565b61059160405192839262461bcd60e51b845260048401526024830190611e59565b0390fd5b6064836040519062461bcd60e51b825280600483015260248201525f805160206158aa8339815191526044820152fd5b9091600f8116601081101561061c576f181899199a1a9b1b9c1cb0b131b232b360811b901a6105f484866126e0565b5360041c918015610608575f1901906104e8565b634e487b7160e01b5f52601160045260245ffd5b634e487b7160e01b5f52603260045260245ffd5b34610360575f36600319011261036057602060ff60125460081c166040519015158152f35b34610360575f3660031901126103605760206040517f65d7a28e3265b37a6474929f336521b332c1681b933f6cb9f3376673440d862a8152f35b34610360576040366003190112610360576106a8611e9c565b6106b0611eb2565b9060018060a01b038091165f52600160205260405f2091165f52602052602060405f2054604051908152f35b61204f565b34610360575f366003190112610360576020600e54604051908152f35b346103605760403660031901126103605761073b60043561071d611eb2565b90805f526005602052610736600160405f20015461218c565b61232b565b005b61210b565b34610360575f36600319011261036057602060405173d4949664cd82660aae99bedc034a0dea8a0bd5178152f35b3461036057602036600319011261036057610789612964565b60018101809111610608576002549060018201809211610608576020916107b2916004356127e9565b604051908152f35b34610360575f366003190112610360576020604051670de0b6b3a76400008152f35b34610360576040366003190112610360576108026107f8611e9c565b60243590336123ad565b602060405160018152f35b3461036057604036600319011261036057610826611e9c565b60243590335f52600160205260405f2060018060a01b0382165f5260205260405f20549180831061085d5761080292039033612507565b60405162461bcd60e51b815260206004820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f77604482015264207a65726f60d81b6064820152608490fd5b34610360575f36600319011261036057602060405160078152f35b34610360575f3660031901126103605760206040515f8152f35b34610360575f366003190112610360576040515f9060045461090681611f00565b8083526001918083169081156109a05750600114610947575b6109438361092f8187038261202e565b604051918291602083526020830190611e59565b0390f35b60045f90815260209450917f8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19b5b82841061098d5750505081019091019061092f8161091f565b8054858501870152928501928101610974565b610943955061092f93506020915091849260ff191682840152151560051b820101935061091f565b61208d565b34610360576020366003190112610360576004355f526015602052608060405f2060018060a01b036001600160401b03818354169260026001820154910154926040519485528116602085015260a01c1660408301526060820152f35b3461036057604036600319011261036057610a43611eb2565b6004355f52600560205260405f209060018060a01b03165f52602052602060ff60405f2054166040519015158152f35b34610360575f366003190112610360576020600c54604051908152f35b610a99366120d6565b610aa33415612984565b610aab6129b8565b60ff60125460081c16610cf157610ac3831515612a77565b6001600160a01b0391808316610ada811515612a0e565b838316928315610cc45785610b37817f1dfdc691a597a83af2430bd31addf8d2129d012eab8b35de6d696dc91e64852393610b2866038d7ea4c68000610b2160209c612697565b1015612a45565b873303610cb4575b30906123ad565b601154958693610b4685612aac565b601155601054906001600160401b039283831692835f5260168c528460405f2094855460ff811615908115610ca6575b50610c68575b506002929150601054169260405193610b9485611fc9565b8b85528d85019182526040850190815260608501928884528a5f5260158f528060405f2096511660018060a01b0319875416178655600186019251168254916001600160401b0360a01b905160a01b169163ffffffff60e01b16171790555191015560018101610c058482546123a0565b905580546affffffffffffffff000000610c23848360181c16612aba565b60181b166affffffffffffffff0000001991909116179055601054604080516001600160a01b0397909716875260208701939093521693a46001600655604051908152f35b610c759192939550612aba565b1680916001600160401b031916176010555f5260168a52600260405f2092600160ff1985541617845590848d610b7c565b60ff915060081c168f610b76565b610cbf823383612605565b610b30565b60405162461bcd60e51b815260206004820152600560248201526427aba722a960d91b6044820152606490fd5b60405162461bcd60e51b815260206004820152600860248201526715d7d4105554d15160c21b6044820152606490fd5b34610360575f366003190112610360576020600f54604051908152f35b34610360575f366003190112610360576020601454604051908152f35b34610360575f36600319011261036057602060ff600a5416604051908152f35b612073565b34610360576020366003190112610360576001600160a01b03610da1611e9c565b165f525f602052602060405f2054604051908152f35b34610360575f366003190112610360576020601154604051908152f35b34610360575f36600319011261036057602060ff601254166040519015158152f35b5f36600319011261036057610e0b3415612984565b610e136129b8565b6020610e1d615687565b6001600655604051908152f35b604036600319011261036057610e3e611eb2565b610e483415612984565b610e506129b8565b6004355f52601560205260405f2060405190610e6b82611fc9565b80546001600160a01b039081168084526001830154918216602085015260a09190911c6001600160401b031660408401526002919091015460608301521561129e576001600160401b036040820151165f52601660205260405f2080549260ff8460101c161561126d576001600160a01b038116611268575060208201516001600160a01b03165b60208301516001600160a01b039081169082160361123a57610f206001600160401b038560581c16612aba565b6001600160401b03808660181c169116145f1461121c57610f4a6005830154600684015490612ad2565b935b67ffffffffffffffff60581b610f6e605883901c6001600160401b0316612aba565b60581b16906001600160401b0360581b191617825560068201610f928582546123a0565b9055610fa084600e54612ad2565b600e55610faf84600f54612ad2565b600f556004355f908152601560209081526040808320838155600181018490556002018390555163a9059cbb60e01b9181019182526001600160a01b038481166024830152604480830189905282526110829391927f00000000000000000000000000000000000000000000000000000000000000009091169190819061103760648661202e565b6040519461104486612013565b602086527f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c65646020870152519082855af161107c612b4b565b91612b7a565b80519081159182156111fa575b5050156111a2576001600160401b036040840151166040519185835260018060a01b0316917f6443ab78e20802b0a48c253c5a7e05576a8bedd83aec398a8bba1164e7cf6fc3602060043592a4546001600160401b03808260181c169160581c1614611106575b6020826001600655604051908152f35b60406001600160401b03910151165f52601660205260405f20905f82555f60018301555f600283015560086003925f848201555f60048201555f60058201555f60068201555f6007820155018054905f815581611166575b5050906110f6565b8184029184830403610608575f5260205f20908101905b8181101561115e57806111908592612af5565b5f60018201555f60028201550161117d565b60405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b6064820152608490fd5b81925090602091810103126103605760206112159101612b3e565b858061108f565b611234606084015160058401546001850154916128f0565b93610f4c565b60405162461bcd60e51b815260206004820152600660248201526514105653d55560d21b6044820152606490fd5b610ef3565b60405162461bcd60e51b8152602060048201526009602482015268434c41494d41424c4560b81b6044820152606490fd5b60405162461bcd60e51b8152602060048201526007602482015266149154555154d560ca1b6044820152606490fd5b34610360576020366003190112610360576004356001600160401b03808216809203610360576101a0915f52601660205260405f2090815491600181015490600281015460038201546004830154906005840154926007600686015495015495876040519960ff811615158b5260ff8160081c16151560208c015260ff8160101c16151560408c0152818160181c1660608c015260581c1660808a015260a089015260c088015260e08701526101008601526101208501526101408401528060070b61016084015260401c16610180820152f35b34610360575f36600319011261036057602060405166038d7ea4c680008152f35b611e7e565b34610360575f3660031901126103605760206107b2615740565b34610360575f366003190112610360576020600b54604051908152f35b34610360575f366003190112610360576020601354604051908152f35b3461036057604036600319011261036057610802611437611e9c565b335f52600160205260405f2060018060a01b0382165f5260205261146160243560405f20546123a0565b9033612507565b346103605760203660031901126103605760043560ff81168103610360573033036114a257611498602091612cd6565b6040519015158152f35b606460405162461bcd60e51b815260206004820152600460248201526329a2a62360e11b6044820152fd5b34610360575f366003190112610360576040517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b346103605760403660031901126103605761152a611eb2565b336001600160a01b038216036115465761073b9060043561232b565b60405162461bcd60e51b815260206004820152602f60248201527f416363657373436f6e74726f6c3a2063616e206f6e6c792072656e6f756e636560448201526e103937b632b9903337b91039b2b63360891b6064820152608490fd5b3461036057602036600319011261036057600435600754811015610360576115cd61160f91611ec8565b50604051906115e7826115e08184611f38565b038361202e565b60018101549060ff600360028301549201541690604051948594608086526080860190611e59565b9260208501526040840152151560608301520390f35b6020806003193601126103605761163a611e9c565b6116426129b8565b60ff601254166117c6576001600160a01b03168015906116628215612a0e565b61167566038d7ea4c68000341015612a45565b61167d614b6d565b50600254611689612964565b81611768575034925b61169d841515612a77565b6116a934600b546123a0565b600b5561172357826116ba916123a0565b600255805f525f835260405f20828154019055805f5f805160206158ea83398151915285604051868152a360405134815282848201527fdcbc1c05240f31ff3ad067ef1ee35ce4997762752e3a095284754544f4c709d760403392a36001600655604051908152f35b60405162461bcd60e51b815260048101859052601f60248201527f45524332303a206d696e7420746f20746865207a65726f2061646472657373006044820152606490fd5b801561179557600182019081831161060857600181018091116106085761178f91346128f0565b92611692565b60405162461bcd60e51b8152600481018690526009602482015268125394d3d31591539560ba1b6044820152606490fd5b60405162461bcd60e51b81526004810183905260086024820152671117d4105554d15160c21b6044820152606490fd5b34610360575f366003190112610360576020600d54604051908152f35b6060366003190112610360576004358015158091036103605760248035908115158092036103605760443590811515809203610360576118533415612984565b335f9081527f99f2891db7a8db76249871a02b43b68c0bb8f22ef8a3bc1034ae9375ef6b7c3c60209081526040909120547f65d7a28e3265b37a6474929f336521b332c1681b933f6cb9f3376673440d862a9060ff16156118db5750505061ff009060ff62ff00006012549260101b1694169062ffffff1916179160081b1617176012555f80f35b6118e4336126f1565b906040516118f181611fc9565b60428152838101916060368437815115611a4957603083538151600190811015611a3657607860218401536041905b8082116119cd57505061199e57604861059193611973936119829360405195869376020b1b1b2b9b9a1b7b73a3937b61d1030b1b1b7bab73a1604d1b8a860152610538815180928c603789019101611e38565b0103602881018452018261202e565b60405193849362461bcd60e51b85526004850152830190611e59565b606485856040519162461bcd60e51b83528160048401528201525f805160206158aa8339815191526044820152fd5b9091600f81166010811015611a23576f181899199a1a9b1b9c1cb0b131b232b360811b901a6119fc84866126e0565b5360041c918015611a10575f190190611920565b87634e487b7160e01b5f5260116004525ffd5b88634e487b7160e01b5f5260326004525ffd5b86634e487b7160e01b5f5260326004525ffd5b85634e487b7160e01b5f5260326004525ffd5b602036600319011261036057611a723415612984565b611a7a6129b8565b6020610e1d600435612c13565b34610360575f3660031901126103605760ff7f00000000000000000000000000000000000000000000000000000000000000001660ff811161060857602090604051908152f35b3461036057604036600319011261036057600435611aea611eb2565b815f526005602052611b02600160405f20015461218c565b815f52600560205260405f209060018060a01b031690815f5260205260ff60405f20541615611b2d57005b815f52600560205260405f20815f5260205260405f20600160ff1982541617905533917f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d5f80a4005b34610360575f3660031901126103605760206107b261373d565b34610360575f36600319011261036057602060ff60125460101c166040519015158152f35b34610360576020366003190112610360576004355f5260056020526020600160405f200154604051908152f35b3461036057606036600319011261036057610802611bfe611e9c565b611c06611eb2565b60443591611c15833383612605565b6123ad565b34610360575f36600319011261036057602060405160088152f35b34610360575f366003190112610360576020600254604051908152f35b34610360575f366003190112610360576020600754604051908152f35b34610360575f3660031901126103605760206001600160401b0360105416604051908152f35b34610360576020366003190112610360576002546001810180911161060857611cbc612964565b9060018201809211610608576020916107b2916004356127e9565b3461036057604036600319011261036057610802611cf3611e9c565b6024359033612507565b34610360575f366003190112610360576040515f90600354611d1e81611f00565b8083526001918083169081156109a05750600114611d46576109438361092f8187038261202e565b60035f90815260209450917fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b5b828410611d8c5750505081019091019061092f8161091f565b8054858501870152928501928101611d73565b34610360575f3660031901126103605760206001600160401b0360105460401c16604051908152f35b346103605760203660031901126103605760043563ffffffff60e01b811680910361036057602090637965db0b60e01b8114908115611e0d575b506040519015158152f35b6301ffc9a760e01b14905082611e02565b34610360575f3660031901126103605760206107b2612964565b5f5b838110611e495750505f910152565b8181015183820152602001611e3a565b90602091611e7281518092818552858086019101611e38565b601f01601f1916010190565b346103605760203660031901126103605760206107b2600435612697565b600435906001600160a01b038216820361036057565b602435906001600160a01b038216820361036057565b60075481101561061c5760075f5260205f209060021b01905f90565b60085481101561061c5760085f5260205f209060021b01905f90565b90600182811c92168015611f2e575b6020831014611f1a57565b634e487b7160e01b5f52602260045260245ffd5b91607f1691611f0f565b905f9291805491611f4883611f00565b9182825260019384811690815f14611fa65750600114611f69575b50505050565b90919394505f52602092835f2092845f945b838610611f9257505050500101905f808080611f63565b805485870183015294019385908201611f7b565b9294505050602093945060ff191683830152151560051b0101905f808080611f63565b608081019081106001600160401b03821117611fe457604052565b634e487b7160e01b5f52604160045260245ffd5b60a081019081106001600160401b03821117611fe457604052565b604081019081106001600160401b03821117611fe457604052565b90601f801991011681019081106001600160401b03821117611fe457604052565b3461036057602036600319011261036057612068611e9c565b5060206040515f8152f35b34610360575f366003190112610360576020604051818152f35b34610360576040366003190112610360576120a6611eb2565b5060405162461bcd60e51b815260206004820152600760248201526611115413d4d25560ca1b6044820152606490fd5b606090600319011261036057600435906001600160a01b03906024358281168103610360579160443590811681036103605790565b3461036057612119366120d6565b505060405162461bcd60e51b815260206004820152600660248201526552454445454d60d01b604482015260649150fd5b34610360576020366003190112610360576002546001810180911161060857612171612964565b9060018201809211610608576020916107b2916004356128f0565b5f9080825260209060058252604092838120338252835260ff8482205416156121b55750505050565b6121be336126f1565b918451906121cb82611fc9565b6042825284820192606036853782511561231757603084538251906001918210156123175790607860218501536041915b8183116122aa5750505061227b57604861059193869361225f93612250985198899376020b1b1b2b9b9a1b7b73a3937b61d1030b1b1b7bab73a1604d1b8a860152610538815180928c603789019101611e38565b0103602881018752018561202e565b5192839262461bcd60e51b845260048401526024830190611e59565b60648486519062461bcd60e51b825280600483015260248201525f805160206158aa8339815191526044820152fd5b909192600f81166010811015612303576f181899199a1a9b1b9c1cb0b131b232b360811b901a6122da85876126e0565b5360041c9280156122ef575f190191906121fc565b634e487b7160e01b82526011600452602482fd5b634e487b7160e01b83526032600452602483fd5b634e487b7160e01b81526032600452602490fd5b905f918083526005602052604083209160018060a01b03169182845260205260ff60408420541661235b57505050565b8083526005602052604083208284526020526040832060ff1981541690557ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b339380a4565b9190820180921161060857565b6001600160a01b039081169182156124b45716918215612463575f8281528060205260408120549180831061240f57604082825f805160206158ea833981519152958760209652828652038282205586815220818154019055604051908152a3565b60405162461bcd60e51b815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e7420657863656564732062604482015265616c616e636560d01b6064820152608490fd5b60405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b6064820152608490fd5b60405162461bcd60e51b815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604482015264647265737360d81b6064820152608490fd5b6001600160a01b039081169182156125b457169182156125645760207f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591835f526001825260405f20855f5282528060405f2055604051908152a3565b60405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b6064820152608490fd5b60405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608490fd5b9060018060a01b038083165f52600160205260405f209082165f5260205260405f2054925f1984036126375750505050565b80841061265257612649930391612507565b5f808080611f63565b60405162461bcd60e51b815260206004820152601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e63650000006044820152606490fd5b61269f612964565b60018101809111610608576002549060018201809211610608576126c2926128f0565b90565b6001600160401b038111611fe457601f01601f191660200190565b90815181101561061c570160200190565b60405190606082018281106001600160401b03821117611fe457604052602a825260208201604036823782511561061c5760309053815160019081101561061c57607860218401536029905b80821161277e57505061274d5790565b606460405162461bcd60e51b815260206004820152602060248201525f805160206158aa8339815191526044820152fd5b9091600f811660108110156127d5576f181899199a1a9b1b9c1cb0b131b232b360811b901a6127ad84866126e0565b5360041c9180156127c1575f19019061273d565b60245f634e487b7160e01b81526011600452fd5b60245f634e487b7160e01b81526032600452fd5b91906127f68282856128f0565b92821561281357096128055790565b600181018091116106085790565b634e487b7160e01b5f52601260045260245ffd5b90670de0b6b3a7640000905f1982840992828102928380861095039480860395146128e257848311156128a55782910960018219018216809204600280826003021880830282030280830282030280830282030280830282030280830282030280920290030293600183805f03040190848311900302920304170290565b60405162461bcd60e51b81526020600482015260156024820152744d6174683a206d756c446976206f766572666c6f7760581b6044820152606490fd5b505080925015612813570490565b915f1982840992828102928380861095039480860395146128e257848311156128a55782910960018219018216809204600280826003021880830282030280830282030280830282030280830282030280830282030280920290030293600183805f03040190848311900302920304170290565b60025415612980576126c2600b5461297a615740565b906123a0565b5f90565b1561298b57565b60405162461bcd60e51b815260206004820152600560248201526456414c554560d81b6044820152606490fd5b6002600654146129c9576002600655565b60405162461bcd60e51b815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c006044820152606490fd5b15612a1557565b60405162461bcd60e51b81526020600482015260086024820152672922a1a2a4ab22a960c11b6044820152606490fd5b15612a4c57565b60405162461bcd60e51b815260206004820152600360248201526226a4a760e91b6044820152606490fd5b15612a7e57565b60405162461bcd60e51b815260206004820152600660248201526553484152455360d01b6044820152606490fd5b5f1981146106085760010190565b9060016001600160401b038093160191821161060857565b9190820391821161060857565b818110612aea575050565b5f8155600101612adf565b612aff8154611f00565b9081612b09575050565b81601f5f9311600114612b1b5750555b565b908083918252612b3a601f60208420940160051c840160018501612adf565b5555565b5190811515820361036057565b3d15612b75573d90612b5c826126c5565b91612b6a604051938461202e565b82523d5f602084013e565b606090565b91929015612bdc5750815115612b8e575090565b3b15612b975790565b60405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606490fd5b825190915015612bef5750805190602001fd5b60405162461bcd60e51b815260206004820152908190610591906024830190611e59565b5f9060ff60125460101c16612ca6578015612ca057612c59612c53612c4d612c47612c41612c5f9686613789565b8561384a565b846138d2565b8361395a565b826139e1565b90613a69565b90612c6b601354612aac565b601355816014556040518281527f4889d7d38c1d1f265318505a98367d07d41591d6b7f9cc000f37ea92b8311daa60203392a2565b50505f90565b60405162461bcd60e51b81526020600482015260086024820152671417d4105554d15160c21b6044820152606490fd5b60ff16600181146136a05760028114613697576003811461368e5760048114612d1d5760058114612d1457600614612d0c575f90565b6126c26149ec565b506126c26146f9565b50604051612d2a81611ff8565b604051600954815f612d3b83611f00565b808352926001811690811561366f5750600114613630575b612d5f9250038261202e565b81525f6020820152602060408201525f60608201525f60808201525f60405180809363186b216760e01b82526040600483015260126044830152711093d39117d4d510551554d7d093d391115160721b6064830152608060248301526080612dd4825160a06084860152610124850190611e59565b916001600160401b0360208201511660a48501526001600160401b0360408201511660c48501526060810151151560e48501520151151561010483015203816108005afa918215613625575f91829361324e575b505f5b8251811015612edc57612e3e81846146e5565b5190604082015115801590612eb0575b8015612ea4575b612e9a5760808201519161012081015190670de0b6b3a764000091820180921161060857612e89612e9092612e9595612827565b9051614e0b565b612aac565b612e2b565b612e959150612aac565b50608082015115612e55565b5060608201516004811015612ec85760031415612e4e565b634e487b7160e01b5f52602160045260245ffd5b50909180519283516001600160401b038111611fe457612efd600954611f00565b601f81116131f5575b50602094601f8211600114613191579481929394955f92613186575b50508160011b915f199060031b1c1916176009555b81515115612f56575b511590811591612f4e575090565b905051511590565b9190612f60615687565b505f5b600754811015612f9f57806003612f7c612f9a93611ec8565b5001805460ff191690555f6001612f9283611ec8565b500155612aac565b612f63565b50905f5b60085481101561306a57612fd8612fcc612fd3612fbf84611ee4565b5060405192838092611f38565b038261202e565b615863565b90156130185790816003612fee61301394611ec8565b5001600160ff198254161790556001612f928161300a85611ee4565b50015492611ec8565b612fa3565b50600790815460208110613032575b506130139150612aac565b61303b82611ee4565b50600160401b821015611fe45761305e8261301395600161306495019055611ec8565b90615067565b5f613027565b5090915f915b60078054808510156131425760ff600361308987611ec8565b50015416158061312d575b1561311b575f1990808201908111610608576130b26130bc91611ec8565b5061305e87611ec8565b815480156131075701906130cf82611ec8565b6130f4576003816130e05f93612af5565b826001820155826002820155015555613070565b634e487b7160e01b5f525f60045260245ffd5b634e487b7160e01b5f52603160045260245ffd5b50509161312790612aac565b91613070565b50600261313986611ec8565b50015415613094565b505092915061314f6136a9565b7f17e14f5a0c6831022cc1f0f72d2d717ac74633f5193b19f76e18d9711b6d7cec602061317a61373d565b604051908152a1612f40565b015190505f80612f22565b601f1982169560095f5260205f20915f5b8881106131dd575083600195969798106131c5575b505050811b01600955612f37565b01515f1960f88460031b161c191690555f80806131b7565b919260206001819286850151815501940192016131a2565b60095f5261323e907f6e1540171b6c0c960b71a7020d9f60077f6af931a8bbf590da0223dacf75c7af601f840160051c81019160208510613244575b601f0160051c0190612adf565b5f612f06565b9091508190613231565b919092503d8082843e613261818461202e565b6040838281010312613364578251916001600160401b03831161335d57818401601f84860101121561335d578284015161329a8161466b565b936132a8604051958661202e565b818552602085019084870160208460051b838a0101011161336057602081880101915b60208460051b838a0101018310613368575050505060208401516001600160401b038111613364578401916040838287010312613364576040519461330f86612013565b83516001600160401b03811161336057840192828201601f8501121561335d575060209261335094926133489201908481519101614682565b8552016146d1565b602083015290915f612e28565b80fd5b8380fd5b5080fd5b82516001600160401b038111613621576101608984018201888b0103601f1901126136215760405190816101608101106001600160401b036101608401111761360d576101608201604052602081858c010101516001600160401b038111613609578a60206133e09284888d850194010101016146b7565b8252604081858c010101516001600160401b038111613609578a602061340f9284888d850194010101016146b7565b6020830152613424606082868d010101612b3e565b6040830152608081858c010101516004811015613609576060830152898401810160a081810151608085015260c08201519084015260e001516001600160401b0381116136095760a0858c01830182018a8d0103601f190112613609576040519060208184888f61349487611ff8565b01010101516001600160401b038111613605578184888f6134c68f9560206040978401918787878701010101016146b7565b875201010101516001600160401b038111613605578184888f6134fa8f9560206060978401918787878701010101016146b7565b602088015201010101516001600160401b038111613605578184888f6135318f9560206080978401918787878701010101016146b7565b604088015201010101516001600160401b038111613605578184888f6135688f95602060a0978401918787878701010101016146b7565b60608801520101010151906001600160401b0382116136055761016084602097948f938a8f8b9a976135a6938c9b8c938a01948a01010101016146b7565b608082015260c085015288610100936135c4858484840101016145e2565b60e0870152610120946135dc868585850101016145e2565b9087015261014094858484840101015190870152010101519082015281520193019290506132cb565b8980fd5b8780fd5b634e487b7160e01b87526041600452602487fd5b8580fd5b6040513d5f823e3d90fd5b505060095f52816020805f20925f905b8082106136555750612d5f9350820101612d53565b845482870184015260019094019385935090820190613640565b60209250612d5f94915060ff191682840152151560051b820101612d53565b506126c2614b6d565b506126c2613af1565b506126c261422e565b6008545f80600855816136ba575050565b6001600160fe1b038216820361372957600881527ff3f7a9fe364faab93b216da50a3214154f22a0a2b415b23a84c8169e8b636ee3600292831b810192905b8381106137065750505050565b80613712600492612af5565b8360018201558383820155836003820155016136f9565b634e487b7160e01b81526011600452602490fd5b5f905f600754905b818110613750575050565b60ff600361375d83611ec8565b50015416613774575b61376f90612aac565b613745565b9261378161376f91612aac565b939050613766565b8110156126c257604051630e47b40f60e21b8152600160048201526020905f90828160248185305af182918161380f575b506137f55750505f805160206158ca8339815191526001916137da612b4b565b906137ef604051928284938452830190611e59565b0390a290565b909150613800575090565b60018201809211613729575090565b9091508381813d8311613843575b613827818361202e565b8101031261383f5761383890612b3e565b905f6137ba565b8280fd5b503d61381d565b8110156126c257604051630e47b40f60e21b8152600260048201526020905f90828160248185305af182918161389b575b506137f55750505f805160206158ca8339815191526002916137da612b4b565b9091508381813d83116138cb575b6138b3818361202e565b8101031261383f576138c490612b3e565b905f61387b565b503d6138a9565b8110156126c257604051630e47b40f60e21b8152600360048201526020905f90828160248185305af1829181613923575b506137f55750505f805160206158ca8339815191526003916137da612b4b565b9091508381813d8311613953575b61393b818361202e565b8101031261383f5761394c90612b3e565b905f613903565b503d613931565b8110156126c257604051630e47b40f60e21b81526004808201526020905f90828160248185305af18291816139aa575b506137f55750505f805160206158ca8339815191526004916137da612b4b565b9091508381813d83116139da575b6139c2818361202e565b8101031261383f576139d390612b3e565b905f61398a565b503d6139b8565b8110156126c257604051630e47b40f60e21b8152600560048201526020905f90828160248185305af1829181613a32575b506137f55750505f805160206158ca8339815191526005916137da612b4b565b9091508381813d8311613a62575b613a4a818361202e565b8101031261383f57613a5b90612b3e565b905f613a12565b503d613a40565b8110156126c257604051630e47b40f60e21b8152600660048201526020905f90828160248185305af1829181613aba575b506137f55750505f805160206158ca8339815191526006916137da612b4b565b9091508381813d8311613aea575b613ad2818361202e565b8101031261383f57613ae390612b3e565b905f613a9a565b503d613ac8565b6001600160401b03601054165f52601660205260405f20805460ff811615908115614220575b508015614214575b61420f57613b2b614b6d565b50600181015490613b3b82612697565b9182156142085761010061ffff1983541617825582600283015530156141b957305f525f60205260405f20549080821061416957805f923084528360205203604083205580600254036002556040519081525f805160206158ea83398151915260203092a3613bc0600b548381105f1461416257805b818061412b575b505083612ad2565b905f9180613c80575b50613c3e6001827f5d78323b7b570071308d257743c74a9a2401f77d37e9de9f908da12c8917a40b606060076001600160401b0396019786199889815416898916179055600484015415613c62575b60105497878916958695015491604051928352602083015260070b6040820152a2612aba565b1691829116176010555f52601660205260405f20600160ff19825416179055600190565b835462ff000019166201000017845560038401546005850155613c18565b9150925f91600754156140f957613c95615687565b50905f5b6007548110806140f0575b156140ae57613cb281611ec8565b5060028101548015801561408b575b614080578481101561407857905b604051630fb6accf60e21b81529060208280613cf0868530600485016145f0565b03815f6108005af15f928161403c575b50613d1657505050613d1190612aac565b613c99565b613d7e81613d2f856002612fcc959a97989a0154612ad2565b6002820155600c613d41878254612ad2565b9055600d613d508782546123a0565b9055600e613d5f8782546123a0565b905560048b01613d708782546123a0565b905560405192838092611f38565b5f6080604051613d8d81611ff8565b606081528260208201528260408201528260608201520152613def5f60405192613db684611ff8565b8084528660208501528160408501528460070b60608501528160808501526040518093819263a03ffee160e01b8352306004840161545d565b03816108005afa5f9181614018575b50613f70575b506008890154600160401b811015611fe457806001613e2c920160088c015560088b0161461a565b6130f45781518051906001600160401b038211611fe457613e5782613e518554611f00565b85614633565b602090601f8311600114613f075760029392915f9183613efc575b50508160011b915f199060031b1c19161781555b602083015160018201550190604081015182546001600160401b03606084015160401b93608082811b91015160801b16938160401b169216906001600160401b0360c01b1617171790558560070b8160070b13613ef2575b50613d1191613eec91612ad2565b92612aac565b9450613d11613ede565b015190505f80613e72565b90835f5260205f20915f5b601f1985168110613f58575091839160019360029695601f19811610613f40575b505050811b018155613e86565b01515f1960f88460031b161c191690555f8080613f33565b91926020600181928685015181550194019201613f12565b9790969195945f5b60408a015180518210156140095790613f9481613fb3936146e5565b518960070b602082015160070b1480613fe5575b613fb8575b50612aac565b613f78565b606081015160208c0152805160070b60408c01526080908101516001600160401b0316908b01525f613fad565b506001600160401b036080820151166001600160401b0360808d0151161115613fa8565b5050949591969097505f613e04565b6140359192503d805f833e61402d818361202e565b8101906152fa565b905f613dfe565b9092506020813d602011614070575b816140586020938361202e565b8101031261036057614069906145e2565b915f613d00565b3d915061404b565b508390613ccf565b5050613d1190612aac565b5060076140a76040516140a281612fcc8188611f38565b6155cb565b1015613cc1565b509391906140bf5790613c3e613bc9565b60405162461bcd60e51b81526020600482015260096024820152681111531151d055115160ba1b6044820152606490fd5b50821515613ca4565b60405162461bcd60e51b815260206004820152600a60248201526956414c494441544f525360b01b6044820152606490fd5b61413491612ad2565b600b5561414381600e546123a0565b600e5561415281600f546123a0565b600f558060038401555f81613bb8565b8390613bb1565b60405162461bcd60e51b815260206004820152602260248201527f45524332303a206275726e20616d6f756e7420657863656564732062616c616e604482015261636560f01b6064820152608490fd5b60405162461bcd60e51b815260206004820152602160248201527f45524332303a206275726e2066726f6d20746865207a65726f206164647265736044820152607360f81b6064820152608490fd5b5050505f90565b505f90565b50600181015415613b1f565b60ff915060081c165f613b17565b6001600160401b036010805482604091821c16935b825493808616908086168210156145d7575f93828552602091601683528186209081549860ff9960089a80828d1c16159182156145cb575b505080156145bf575b6145775750600782018181541642106145445780548281861c16156144d9575054831c164311156144a9579497600197860196899691875b89548c1015614354578b6142d96142d3828d61461a565b506154a1565b90614308575b506143029160016142f36142fc938e61461a565b500154906123a0565b9b612aac565b9a6142bc565b9b50908a908a9c614319848461461a565b50600101548110614302946142fc946001936142f393614340575b505093505050916142df565b8461434b848461461a565b5001555f614334565b929598509295995092959760048501918254808210614477575b50501561444857906143f77f73f330dd18a8f4760be8fa04b392ba243a76dcbe07487724e846182d63dbfafa97986144139354906143aa6157be565b9050808210156144425750805b600d6143c4828254612ad2565b90556143dc826143d7600e938454612ad2565b6123a0565b9055600f6143eb8282546123a0565b905560038601546123a0565b60058501908155845462ff000019166201000017909455612aba565b815467ffffffffffffffff60401b191690841b67ffffffffffffffff60401b16179055549051908152a2600190565b906143b7565b50815467ffffffffffffffff60401b1916931b67ffffffffffffffff60401b1692909217909155509192915050565b8161448191612ad2565b908355600d614491828254612ad2565b90556144a0600e918254612ad2565b90555f8061436e565b50855467ffffffffffffffff60401b191697901b67ffffffffffffffff60401b1696909617909355909350915050565b919599509396507f5e49965d9b92858198b2c5bab7c3a3fa70fcaf004a265239d97b798b708646ce9792959991506001600160401b0360401b6001600160401b0360401b19918282438a1b169116178555835492871b16911617905554821c169051908152a2600190565b5050865467ffffffffffffffff60401b19169890911b67ffffffffffffffff60401b169790971790945550909350915050565b9350975050935085929691506001600160401b0360401b61459783612aba565b67ffffffffffffffff60401b1990921691851b16178455168381146127c15760010193614243565b50600483015415614284565b8b1c1690505f8061427b565b505093505050505f90565b51908160070b820361036057565b9392916146159060409260018060a01b03168652606060208701526060860190611f38565b930152565b805482101561061c575f52600360205f20910201905f90565b9190601f811161464257505050565b612b19925f5260205f20906020601f840160051c8301931061324457601f0160051c0190612adf565b6001600160401b038111611fe45760051b60200190565b9092919261468f816126c5565b9161469d604051938461202e565b829482845282820111610360576020612b19930190611e38565b9080601f830112156103605781516126c292602001614682565b51906001600160401b038216820361036057565b805182101561061c5760209160051b010190565b6002806007541061420f5761470c615687565b506007545f80805b83811061499e57508115908115614992575b506142085790614735826150b4565b915f94919490819482938390600c54915b8381106148de5750505050159081156148d5575b5080156148cc575b6148c357808210156148bb5750915b66038d7ea4c6800083106148b35761478b61479291611ec8565b5091611ec8565b50916040516354b826f560e01b815230600482015260806024820152602081806147d46147c26084830188611f38565b82810360031901604484015288611f38565b85606483015203815f6108005af15f9181614878575b506147f85750505050505f90565b61486261485494867f2a3f2dce4ae3c46ff8fd3dbcf9cac1ca39da18b59d0ac57b2047d061ec99c1cb97860161482f868254612ad2565b9055810161483e8582546123a0565b9055604051958695608087526080870190611f38565b908582036020870152611f38565b91604084015260070b60608301520390a1600190565b90916020823d82116148ab575b816148926020938361202e565b8101031261335d57506148a4906145e2565b905f6147ea565b3d9150614885565b505050505f90565b905091614771565b50505050505f90565b50828414614762565b9050155f61475a565b60ff60036148eb83611ec8565b5001541615614989576148fd81611ec8565b5061490e83600180930154866128f0565b908c898161491b86611ec8565b50015484119182614969575b505061493e575b505061493990612aac565b614746565b929650985095509361493961496187998c6149588a611ec8565b50015490612ad2565b96905f61492e565b61498191925061497886611ec8565b50015484612ad2565b11898e614927565b61493990612aac565b9050600c54105f614726565b60ff60036149ab83611ec8565b500154166149c2575b6149bd90612aac565b614714565b90916149e46149d96149bd9260016142f386611ec8565b93866142f385611ec8565b9190506149b4565b600254158015614b5b575b61298057614a03615687565b505f80600754905b818110614b1c575050801561420f57614a34600c5491614a2e600b5480946123a0565b906151f6565b909291156142085780614b175750805b80821015614b0f5750905b8115612ca057614a5e90611ec8565b50906040516353266bbb60e01b815260208180614a80858730600485016145f0565b03815f6108005af15f9181614ad4575b50614a9c575050505f90565b15612ca0576002614acc92614ab383600b54612ad2565b600b5501614ac28282546123a0565b9055600c546123a0565b600c55600190565b90916020823d8211614b07575b81614aee6020938361202e565b8101031261335d5750614b0090612b3e565b905f614a90565b3d9150614ae1565b905090614a4f565b614a44565b60ff6003614b2983611ec8565b50015416614b40575b614b3b90612aac565b614a0b565b91614b53614b3b9160016142f386611ec8565b929050614b32565b5066038d7ea4c68000600b54106149f7565b600754158015614cff575b612980576040516370a0823160e01b8082523060048301527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906020908184602481865afa938415613625575f94614cd0575b5063ffffffff6007541660405190632efe8a5f60e01b8252306004830152602482015282816044815f6108015af15f9181614c99575b50614c185750505050505f90565b156148b357819060246040518095819382523060048301525afa908115613625575f91614c6d575b509050818111614c505750505f90565b614c6591614c5d91612ad2565b600b546123a0565b600b55600190565b82813d8311614c92575b614c81818361202e565b8101031261335d575051805f614c40565b503d614c77565b90918482813d8311614cc9575b614cb0818361202e565b8101031261335d5750614cc290612b3e565b905f614c0a565b503d614ca6565b90938282813d8311614cf8575b614ce7818361202e565b8101031261335d575051925f614bd4565b503d614cdd565b50614d08615687565b15614b78565b909291926130f45782519283516001600160401b038111611fe457614d3781613e518554611f00565b6020601f8211600114614da1579181606092600394612b1997985f92614d96575b50508160011b915f1990861b1c19161784555b60208101516001850155604081015160028501550151151591019060ff801983541691151516179055565b015190505f80614d58565b601f19821695845f52815f20965f5b818110614df3575092612b199697600395936001938360609710614ddc575b505050811b018455614d6b565b01515f1983881b60f8161c191690555f8080614dcf565b83830151895560019098019760209384019301614db0565b908015614f8d575f5b600854811015614e8357614e4483612fcc614e31612fbf85611ee4565b6020815191012090602081519101201490565b614e5657614e5190612aac565b614e14565b909150614e6281611ee4565b50906001809201548311614e7557505050565b614e7e90611ee4565b500155565b509060085460ff600a54168110614f4c5750600854915f831561231757600881527ff3f7a9fe364faab93b216da50a3214154f22a0a2b415b23a84c8169e8b636ee454600194855b818110614f125750508211614ee05750505050565b61264993614f0c9260405194614ef586611fc9565b855260208501525f60408501526060840152611ee4565b90614d0e565b8287614f1d83611ee4565b50015410614f34575b614f2f90612aac565b614ecb565b9250905084614f4283611ee4565b5001549082614f26565b9160405191614f5a83611fc9565b825260208201525f604082015260016060820152600160401b821015611fe457614f0c826001612b199401600855611ee4565b5050565b90808214614f8d57614fa38154611f00565b906001600160401b038211611fe4578190614fc882614fc28654611f00565b86614633565b5f90601f8311600114614ffc575f92614ff1575b50508160011b915f199060031b1c1916179055565b015490505f80614fdc565b81526020808220858352818320935090601f1985169083905b82821061504e575050908460019594939210615036575b505050811b019055565b01545f1960f88460031b161c191690555f808061502c565b8495819295850154815560018091019601940190615015565b906130f457818103615077575050565b600360ff818461508a612b199686614f91565b600181015460018601556002810154600286015501541691019060ff801983541691151516179055565b600754915f5b83811061519857505f809381938290600c54915b8381106150de5750505050929190565b60ff60036150eb83611ec8565b500154161561518f576150fd81611ec8565b5061510e83600180930154866128f0565b61511783611ec8565b509160028093015466038d7ea4c68000830190818411610608571180615179575b61514e575b50505061514990612aac565b6150ce565b92995089985091955061514991615170916151688a611ec8565b500154612ad2565b96905f8061513d565b5089615189838561516888611ec8565b11615138565b61514990612aac565b60ff60036151a883969596611ec8565b5001541615806151e0575b6151c8576151c090612aac565b9291926150ba565b9250905060026151d783611ec8565b50015460019291565b5060026151ec82611ec8565b50015415156151b3565b90915f809381938291600754925b83811061525a57505050811561521b575b50929190565b5f5b81811061522a5750615215565b60ff600361523783611ec8565b5001541661524d5761524890612aac565b61521d565b6001955093505f92915050565b60ff600361526783611ec8565b50015416156152f15761527981611ec8565b5061528a84600180930154856128f0565b61529383611ec8565b50916002809301548211806152dc575b6152b9575b5050506152b490612aac565b615204565b9299508998509195506152b4916152d3916149588a611ec8565b96905f806152a8565b50896152eb8461497887611ec8565b116152a3565b6152b490612aac565b90602080838303126103605782516001600160401b039384821161036057019260609283858203126103605760409283519585870187811085821117611fe4578552805184811161036057836153519183016146b7565b875281810151848111610360578361536a9183016146b7565b82880152848101519084821161036057019180601f84011215610360578251936153938561466b565b966153a08751988961202e565b858852838801928460c080980287010195818711610360578501935b8685106153d157505050505050505082015290565b878583031261036057885190888201908282108683111761544957899288928c526153fb886145e2565b81526154088389016145e2565b838201528b8801518c82015285880151868201526080615429818a016146d1565b9082015260a061543a818a016145e2565b908201528152019401936153bc565b60245f634e487b7160e01b81526041600452fd5b6001600160a01b0390911681526040602082018190526126c292910190611e59565b6001600160a01b0390911681526040602082018190526126c292910190611f38565b6040805163a03ffee160e01b81525f939290918483806154c585306004840161547f565b03816108005afa8593816155af575b506154e1575b5050508190565b90919293805b8285015180518210156155a057816154fe916146e5565b5160028501546001600160401b036080918181841c1680159283159485615591575b5050508161557e575b81615567575b50811561555f575b5061554e57506155478391612aac565b90506154e7565b606001516001969095509350505050565b90505f615537565b90506020830151600791871c820b910b145f61552f565b9050825160079082820b910b1490615529565b860151161492505f8080615520565b505093925050505f80806154da565b6155c49194503d8088833e61402d818361202e565b925f6154d4565b5f6155eb916040518093819263a03ffee160e01b8352306004840161545d565b03816108005afa5f918161560c575b5061560457505f90565b604001515190565b61562091923d8091833e61402d818361202e565b905f6155fa565b9190916040818403126103605780519260208201516001600160401b03928382116103605701604081830312610360576040519261566484612013565b81519081116103605760209261567b9183016146b7565b83520151602082015290565b5f905f805b600754811015615738576156bf826156a383611ec8565b506040518093819263120bba7360e11b8352306004840161547f565b03816108005afa839181615712575b506156e9575080826002612f926156e494611ec8565b61568c565b6156e49194602061570c92018051600261570289611ec8565b50015551906123a0565b93612aac565b61572f9192503d8086833e615727818361202e565b810190615627565b9050905f6156ce565b505081600c55565b5f905f80600754915b82811061575557505050565b615762826156a383611ec8565b03816108005afa8391816157a0575b50615786575b5061578190612aac565b615749565b61578191956020615799920151906123a0565b9490615777565b6157b59192503d8086833e615727818361202e565b9050905f615771565b6040516370a0823160e01b81523060048201526020816024817f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165afa908115613625575f91615832575b50615821600b54600f54906123a0565b80821115612ca0576126c291612ad2565b906020823d821161585b575b8161584b6020938361202e565b8101031261335d5750515f615811565b3d915061583e565b60075491905f5b83811061587b57505090505f905f90565b61588e82612fcc614e31612fbf85611ec8565b6158a05761589b90612aac565b61586a565b9250506001919056fe537472696e67733a20686578206c656e67746820696e73756666696369656e74f1190c18d28d5d68156de3ae0f68fd157173bc96def1c9c2fae201841639d0adddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa2646970667358221220800b05ff46a6147d3ec02ecf5b96df769de94c6b17f9db608c34d6c952fb8b5b64736f6c63430008140033", + "bytecode": "0x60c060409080825234620006315762006641803803809162000022828562000635565b8339810160808282031262000631576200003c8262000659565b916020906200004d82820162000659565b8582015190916001600160401b0391828111620006315785620000729183016200068a565b94606082015183811162000631576200008c92016200068a565b9060018060a01b0380841691865181811162000531576003908154906001998a83811c9316801562000626575b8a84101462000612578190601f93848111620005bf575b508a9084831160011462000551575f9262000545575b50505f1982851b1c1916908a1b1782555b8551928311620005315760049586548a81811c9116801562000526575b8a82101462000513579081838695949311620004bc575b508991841160011462000455575f9362000449575b505082891b925f19911b1c19161783555b6200015c82620006f2565b901562000440575b60a052816080528560065560ff19968588600a541617600a55866012551692831562000415573b15620003e55783829188519283809263313ce56760e01b82525afa908115620003db575f9162000395575b5060ff6012911603620003615750906018915f8052600590818352865f20815f52835260ff875f205416156200032d575b7f97667070c54ef182b0f5858b034beac1b6f3089aa2d3188bb1e8929f4fa9b929805f52828452875f20825f52845260ff885f20541615620002f8575b507f65d7a28e3265b37a6474929f336521b332c1681b933f6cb9f3376673440d862a91825f52808452875f20825f52845260ff885f20541615620002c3575b5050506801000000000000000160018060801b03196011541617601155825f5252825f209182541617905551615e699081620007b882396080518181816110c9015281816115b1015281816150cc0152615d03015260a05181611b970152f35b825f528352865f20815f528352865f20858782541617905533915f80516020620066218339815191525f80a45f808062000263565b805f52828452875f20825f528452875f2086888254161790558133915f80516020620066218339815191525f80a45f62000224565b5f8052818352865f20815f528352865f20858782541617905533815f5f80516020620066218339815191528180a4620001e7565b855162461bcd60e51b8152908101839052600e60248201526d41535345545f444543494d414c5360901b6044820152606490fd5b8481813d8311620003d3575b620003ad818362000635565b81010312620003cf57519060ff82168203620003cc575060ff620001b6565b80fd5b5080fd5b503d620003a1565b87513d5f823e3d90fd5b865162461bcd60e51b8152808301859052600a60248201526941535345545f434f444560b01b6044820152606490fd5b875162461bcd60e51b8152808401869052600560248201526420a226a4a760d91b6044820152606490fd5b50601262000164565b015191505f8062000140565b91908a9450601f19841692885f528a5f20935f5b8c828210620004a557505085116200048b575b50505050811b01835562000151565b01519060f8845f19921b161c191690555f8080806200047c565b8385015187558e9890960195938401930162000469565b9091929350875f52895f208380870160051c8201928c881062000509575b918d918897969594930160051c01915b828110620004fa5750506200012b565b5f81558796508d9101620004ea565b92508192620004da565b602288634e487b7160e01b5f525260245ffd5b90607f169062000114565b634e487b7160e01b5f52604160045260245ffd5b015190505f80620000e6565b5f8681528c81208e9550929190601f198516908e5b8282106200059e575050841162000586575b505050811b018255620000f7565b01515f1983871b60f8161c191690555f808062000578565b91929395968291958786015181550195019301908e95949392918e62000566565b909150845f528a5f208480850160051c8201928d861062000608575b918e91869594930160051c01915b828110620005f9575050620000d0565b5f81558594508e9101620005e9565b92508192620005db565b634e487b7160e01b5f52602260045260245ffd5b92607f1692620000b9565b5f80fd5b601f909101601f19168101906001600160401b038211908210176200053157604052565b51906001600160a01b03821682036200063157565b6001600160401b0381116200053157601f01601f191660200190565b919080601f840112156200063157825190620006a6826200066e565b91620006b6604051938462000635565b80835260209182828701011162000631575f5b818110620006de5750825f9394955001015290565b8581018301518482018401528201620006c9565b6040805163313ce56760e01b60208201908152600482529293929181016001600160401b038111828210176200053157604052515f9384928392916001600160a01b03165afa3d15620007ae573d906200074c826200066e565b916200075c604051938462000635565b82523d84602084013e5b80620007a1575b62000778575b508190565b6020818051810103126200079d576020015160ff811162000773576001925060ff1690565b8280fd5b506020815110156200076d565b6060906200076656fe60806040526004361015610011575f80fd5b5f3560e01c806301e1d11414611eff57806301ffc9a714611ea957806303e5fa8814611e8057806306fdde0314611dde57806307a2d13a14611491578063095ea7b314611db85780630a28a47714611d765780630a763da114611d505780630f43a67714611d3357806318160ddd14611d165780631f9acd7e14610e2f57806323b872dd14611cde578063248a9ca314611cb1578063291cdb8c14611c8c5780632d9414b514611c725780632f2ff15d14611bca578063313ce56714611b8357806332145f9014611b58578063331c138c146118e257806333a73916146118c557806333bb7f91146116f457806335aa2e441461167257806336568abe146115e057806338d52e0f1461159c578063391ed03c1461153757806339509351146114ea578063402d267d146106af5780634221b2c2146114cd5780634864b09d146114b0578063499bafcc146114965780634cdad506146114915780634f7b901314611470578063559904cb1461139c578063588f134314610eed5780635ac22fd214610ec657806360da3e8314610ea45780636637e38c14610e885780636a84a98514610e6b5780636c68e40314610e2f5780636e553f6514610a2757806370a0823114610e34578063714897df14610e2f5780637316a19614610e0f57806377397cdd14610df25780637aed3cbb14610dd55780637d41c86e14610b4457806380d04de814610b275780638ce7a27f14610b0a5780638df8280014610ad257806391d1485414610a89578063937b258114610a2c57806394bf804d14610a2757806395d89b4114610944578063a217fddf1461092a578063a41fcdc3146105e8578063a457c2d714610887578063a6e93b471461086b578063a9059cbb1461083a578063ac44da5214610818578063b3d7f6b9146107ba578063b460af94146107b5578063ba087652146107b5578063c63d75b6146106af578063c6e6f592146104df578063ce96cb77146106af578063cfdbdb0214610799578063d3892a1a14610710578063d547741f146106d1578063d77bb97c146106b4578063d905777e146106af578063dd62ed3e14610662578063e63ab1e914610628578063e9f2838e14610603578063ec7552cb146105e8578063ecdcce24146104e4578063ef8b30f7146104df578063f5b541a6146104a55763f7ac2a5214610373575f80fd5b60208060031936011261047057600435906001600160401b039081831161047057366023840112156104705782600401359180831161047057602493600590368686841b83010111610470576103c93415612bc9565b6103d161229c565b83851161047b575f5b8581106103e357005b8681841b83010135908482168092036104705760405191639ec4d36360e01b83523060048401528883015260026044830152608060648301525f6084830152858260a4815f6108055af1610441575b61043c9150612cf1565b6103da565b8582813d8311610474575b6104568183612158565b810103126104705761046a61043c92612d83565b50610432565b5f80fd5b503d61044c565b60648483886040519262461bcd60e51b845260048401528201526408482a886960db1b6044820152fd5b34610470575f3660031901126104705760206040517f97667070c54ef182b0f5858b034beac1b6f3089aa2d3188bb1e8929f4fa9b9298152f35b61225a565b60203660031901126104705760043560ff8116809103610470576105083415612bc9565b61051061229c565b801515806105dd575b156105b0576020817fe28c6c3955ed05a471a85214f10fe95cb125572d2aadeb008a7c5992533984969260ff19600a541617600a55610556613902565b61056160095461200f565b80610571575b50604051908152a1005b601f811160011461058857505f6009555b83610567565b5f90600982526105a6601f858420920160051c820160018301612d24565b8160095555610582565b60405162461bcd60e51b815260206004820152600560248201526410d3d5539560da1b6044820152606490fd5b506020811115610519565b34610470575f36600319011261047057602060405160078152f35b34610470575f36600319011261047057602060ff60135460081c166040519015158152f35b34610470575f3660031901126104705760206040517f65d7a28e3265b37a6474929f336521b332c1681b933f6cb9f3376673440d862a8152f35b346104705760403660031901126104705761067b611f7d565b610683611f93565b9060018060a01b038091165f52600160205260405f2091165f52602052602060405f2054604051908152f35b612179565b34610470575f366003190112610470576020600f54604051908152f35b346104705760403660031901126104705761070e6004356106f0611f93565b90805f526005602052610709600160405f200154612475565b61257e565b005b6020366003190112610470576004356107293415612bc9565b61073161229c565b611388811161076b576020817f96e02ba2ef5107e698b6e6f9ebbd4debf3d30ac1c704d2b7bbe7ee39085eaaca92600b55604051908152a1005b60405162461bcd60e51b8152602060048201526006602482015265212aa32322a960d11b6044820152606490fd5b34610470575f3660031901126104705760206040516113888152f35b61221b565b34610470576020366003190112610470576107d3612ba9565b60018101809111610804576002549060018201809211610804576020916107fc91600435612a3c565b604051908152f35b634e487b7160e01b5f52601160045260245ffd5b34610470575f366003190112610470576020604051670de0b6b3a76400008152f35b3461047057604036600319011261047057610860610856611f7d565b6024359033612600565b602060405160018152f35b34610470575f366003190112610470576020604051610ce48152f35b34610470576040366003190112610470576108a0611f7d565b60243590335f52600160205260405f2060018060a01b0382165f5260205260405f2054918083106108d7576108609203903361275a565b60405162461bcd60e51b815260206004820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f77604482015264207a65726f60d81b6064820152608490fd5b34610470575f3660031901126104705760206040515f8152f35b34610470575f366003190112610470576040515f906004546109658161200f565b8083526001918083169081156109ff57506001146109a6575b6109a28361098e81870382612158565b604051918291602083526020830190611f3a565b0390f35b60045f90815260209450917f8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19b5b8284106109ec5750505081019091019061098e8161097e565b80548585018701529285019281016109d3565b6109a2955061098e93506020915091849260ff191682840152151560051b820101935061097e565b61219d565b34610470576020366003190112610470576004355f526017602052608060405f2060018060a01b036001600160401b03818354169260026001820154910154926040519485528116602085015260a01c1660408301526060820152f35b3461047057604036600319011261047057610aa2611f93565b6004355f52600560205260405f209060018060a01b03165f52602052602060ff60405f2054166040519015158152f35b602036600319011261047057610ae83415612bc9565b610af0612bfd565b6020610afd600435612f24565b6001600655604051908152f35b34610470575f366003190112610470576020600b54604051908152f35b34610470575f366003190112610470576020600d54604051908152f35b610b4d366121e6565b610b573415612bc9565b610b5f612bfd565b60ff60135460081c16610da557610b77831515612cbc565b6001600160a01b0391808316610b8e811515612c53565b838316928315610d785785610beb817f1dfdc691a597a83af2430bd31addf8d2129d012eab8b35de6d696dc91e64852393610bdc66038d7ea4c68000610bd560209c6128ea565b1015612c8a565b873303610d68575b3090612600565b601254958693610bfa85612cf1565b601255601154906001600160401b039283831692835f5260188c528460405f2094855460ff811615908115610d5a575b50610d1c575b506002929150601154169260405193610c48856120d8565b8b85528d85019182526040850190815260608501928884528a5f5260178f528060405f2096511660018060a01b0319875416178655600186019251168254916001600160401b0360a01b905160a01b169163ffffffff60e01b16171790555191015560018101610cb98482546125f3565b905580546affffffffffffffff000000610cd7848360181c16612cff565b60181b166affffffffffffffff0000001991909116179055601154604080516001600160a01b0397909716875260208701939093521693a46001600655604051908152f35b610d299192939550612cff565b1680916001600160401b031916176011555f5260188a52600260405f2092600160ff1985541617845590848d610c30565b60ff915060081c168f610c2a565b610d73823383612858565b610be4565b60405162461bcd60e51b815260206004820152600560248201526427aba722a960d91b6044820152606490fd5b60405162461bcd60e51b815260206004820152600860248201526715d7d4105554d15160c21b6044820152606490fd5b34610470575f366003190112610470576020601054604051908152f35b34610470575f366003190112610470576020601554604051908152f35b34610470575f36600319011261047057602060ff600a5416604051908152f35b611fa9565b34610470576020366003190112610470576001600160a01b03610e55611f7d565b165f525f602052602060405f2054604051908152f35b34610470575f366003190112610470576020601254604051908152f35b34610470575f3660031901126104705760206040516127108152f35b34610470575f36600319011261047057602060ff601354166040519015158152f35b5f36600319011261047057610edb3415612bc9565b610ee3612bfd565b6020610afd615bb7565b604036600319011261047057610f01611f93565b610f0b3415612bc9565b610f13612bfd565b6004355f52601760205260405f2060405190610f2e826120d8565b80546001600160a01b039081168084526001830154918216602085015260a09190911c6001600160401b031660408401526002919091015460608301521561136d576001600160401b036040820151165f52601860205260405f2080549260ff8460101c161561133c576001600160a01b038116611337575060208201516001600160a01b03165b60208301516001600160a01b039081169082160361130957610fe36001600160401b038560581c16612cff565b6001600160401b03808660181c169116145f146112eb5761100d6007830154600884015490612d17565b935b67ffffffffffffffff60581b611031605883901c6001600160401b0316612cff565b60581b16906001600160401b0360581b1916178255600882016110558582546125f3565b905561106384600f54612d17565b600f5561107284601054612d17565b6010556004355f908152601760209081526040808320838155600181018490556002018390555163a9059cbb60e01b9181019182526001600160a01b038481166024830152604480830189905282526111459391927f0000000000000000000000000000000000000000000000000000000000000000909116919081906110fa606486612158565b6040519461110786612122565b602086527f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c65646020870152519082855af161113f612d90565b91612dbf565b80519081159182156112c9575b505015611271576001600160401b036040840151166040519185835260018060a01b0316917f6443ab78e20802b0a48c253c5a7e05576a8bedd83aec398a8bba1164e7cf6fc3602060043592a4546001600160401b03808260181c169160581c16146111c9575b6020826001600655604051908152f35b60406001600160401b03910151165f52601860205260405f20905f82555f60018301555f6002830155600a6003925f848201555f60048201555f60058201555f60068201555f60078201555f60088201555f6009820155018054905f815581611235575b5050906111b9565b8184029184830403610804575f5260205f20908101905b8181101561122d578061125f8592612d3a565b5f60018201555f60028201550161124c565b60405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b6064820152608490fd5b81925090602091810103126104705760206112e49101612d83565b8580611152565b61130360608401516007840154600185015491612b28565b9361100f565b60405162461bcd60e51b815260206004820152600660248201526514105653d55560d21b6044820152606490fd5b610fb6565b60405162461bcd60e51b8152602060048201526009602482015268434c41494d41424c4560b81b6044820152606490fd5b60405162461bcd60e51b8152602060048201526007602482015266149154555154d560ca1b6044820152606490fd5b34610470576020366003190112610470576004356001600160401b03808216809203610470576101a0915f52601860205260405f2090815491600181015490600281015460038201546004830154906007840154926009600886015495015495876040519960ff811615158b5260ff8160081c16151560208c015260ff8160101c16151560408c0152818160181c1660608c015260581c1660808a015260a089015260c088015260e08701526101008601526101208501526101408401528060070b61016084015260401c16610180820152f35b34610470575f36600319011261047057602060405166038d7ea4c680008152f35b611f5f565b34610470575f3660031901126104705760206107fc615c6a565b34610470575f366003190112610470576020600c54604051908152f35b34610470575f366003190112610470576020601454604051908152f35b3461047057604036600319011261047057610860611506611f7d565b335f52600160205260405f2060018060a01b0382165f5260205261153060243560405f20546125f3565b903361275a565b346104705760203660031901126104705760043560ff811681036104705730330361157157611567602091612f48565b6040519015158152f35b606460405162461bcd60e51b815260206004820152600460248201526329a2a62360e11b6044820152fd5b34610470575f366003190112610470576040517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b34610470576040366003190112610470576115f9611f93565b336001600160a01b038216036116155761070e9060043561257e565b60405162461bcd60e51b815260206004820152602f60248201527f416363657373436f6e74726f6c3a2063616e206f6e6c792072656e6f756e636560448201526e103937b632b9903337b91039b2b63360891b6064820152608490fd5b34610470576020366003190112610470576004356007548110156104705761169c6116de91611fc3565b50604051906116b6826116af8184612047565b0383612158565b60018101549060ff600360028301549201541690604051948594608086526080860190611f3a565b9260208501526040840152151560608301520390f35b60208060031936011261047057611709611f7d565b611711612bfd565b60ff60135416611895576001600160a01b03168015906117318215612c53565b61174466038d7ea4c68000341015612c8a565b61174c6150a7565b50600254611758612ba9565b81611837575034925b61176c841515612cbc565b61177834600c546125f3565b600c556117f25782611789916125f3565b600255805f525f835260405f20828154019055805f5f80516020615e1483398151915285604051868152a360405134815282848201527fdcbc1c05240f31ff3ad067ef1ee35ce4997762752e3a095284754544f4c709d760403392a36001600655604051908152f35b60405162461bcd60e51b815260048101859052601f60248201527f45524332303a206d696e7420746f20746865207a65726f2061646472657373006044820152606490fd5b801561186457600182019081831161080457600181018091116108045761185e9134612b28565b92611761565b60405162461bcd60e51b8152600481018690526009602482015268125394d3d31591539560ba1b6044820152606490fd5b60405162461bcd60e51b81526004810183905260086024820152671117d4105554d15160c21b6044820152606490fd5b34610470575f366003190112610470576020600e54604051908152f35b6060366003190112610470576004358015158091036104705760248035908115158092036104705760443590811515809203610470576119223415612bc9565b335f9081527f99f2891db7a8db76249871a02b43b68c0bb8f22ef8a3bc1034ae9375ef6b7c3c60209081526040909120547f65d7a28e3265b37a6474929f336521b332c1681b933f6cb9f3376673440d862a9060ff16156119aa5750505061ff009060ff62ff00006013549260101b1694169062ffffff1916179160081b1617176013555f80f35b6119b333612944565b906040516119c0816120d8565b60428152838101916060368437815115611b4557603083538151600190811015611b3257607860218401536041905b808211611ac9575050611a9a576048611a9693611a6b93611a7a9360405195869376020b1b1b2b9b9a1b7b73a3937b61d1030b1b1b7bab73a1604d1b8a860152611a42815180928c603789019101611f19565b8401917001034b99036b4b9b9b4b733903937b6329607d1b603784015251809386840190611f19565b01036028810184520182612158565b60405193849362461bcd60e51b85526004850152830190611f3a565b0390fd5b606485856040519162461bcd60e51b83528160048401528201525f80516020615dd48339815191526044820152fd5b9091600f81166010811015611b1f576f181899199a1a9b1b9c1cb0b131b232b360811b901a611af88486612933565b5360041c918015611b0c575f1901906119ef565b87634e487b7160e01b5f5260116004525ffd5b88634e487b7160e01b5f5260326004525ffd5b86634e487b7160e01b5f5260326004525ffd5b85634e487b7160e01b5f5260326004525ffd5b602036600319011261047057611b6e3415612bc9565b611b76612bfd565b6020610afd600435612e58565b34610470575f3660031901126104705760ff7f00000000000000000000000000000000000000000000000000000000000000001660ff811161080457602090604051908152f35b3461047057604036600319011261047057600435611be6611f93565b815f526005602052611bfe600160405f200154612475565b815f52600560205260405f209060018060a01b031690815f5260205260ff60405f20541615611c2957005b815f52600560205260405f20815f5260205260405f20600160ff1982541617905533917f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d5f80a4005b34610470575f3660031901126104705760206107fc613996565b34610470575f36600319011261047057602060ff60135460101c166040519015158152f35b34610470576020366003190112610470576004355f5260056020526020600160405f200154604051908152f35b3461047057606036600319011261047057610860611cfa611f7d565b611d02611f93565b60443591611d11833383612858565b612600565b34610470575f366003190112610470576020600254604051908152f35b34610470575f366003190112610470576020600754604051908152f35b34610470575f3660031901126104705760206001600160401b0360115416604051908152f35b34610470576020366003190112610470576002546001810180911161080457611d9d612ba9565b9060018201809211610804576020916107fc91600435612a3c565b3461047057604036600319011261047057610860611dd4611f7d565b602435903361275a565b34610470575f366003190112610470576040515f90600354611dff8161200f565b8083526001918083169081156109ff5750600114611e27576109a28361098e81870382612158565b60035f90815260209450917fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b5b828410611e6d5750505081019091019061098e8161097e565b8054858501870152928501928101611e54565b34610470575f3660031901126104705760206001600160401b0360115460401c16604051908152f35b346104705760203660031901126104705760043563ffffffff60e01b811680910361047057602090637965db0b60e01b8114908115611eee575b506040519015158152f35b6301ffc9a760e01b14905082611ee3565b34610470575f3660031901126104705760206107fc612ba9565b5f5b838110611f2a5750505f910152565b8181015183820152602001611f1b565b90602091611f5381518092818552858086019101611f19565b601f01601f1916010190565b346104705760203660031901126104705760206107fc6004356128ea565b600435906001600160a01b038216820361047057565b602435906001600160a01b038216820361047057565b34610470575f366003190112610470576020604051818152f35b600754811015611fdf5760075f5260205f209060021b01905f90565b634e487b7160e01b5f52603260045260245ffd5b600854811015611fdf5760085f5260205f209060021b01905f90565b90600182811c9216801561203d575b602083101461202957565b634e487b7160e01b5f52602260045260245ffd5b91607f169161201e565b905f92918054916120578361200f565b9182825260019384811690815f146120b55750600114612078575b50505050565b90919394505f52602092835f2092845f945b8386106120a157505050500101905f808080612072565b80548587018301529401938590820161208a565b9294505050602093945060ff191683830152151560051b0101905f808080612072565b608081019081106001600160401b038211176120f357604052565b634e487b7160e01b5f52604160045260245ffd5b60a081019081106001600160401b038211176120f357604052565b604081019081106001600160401b038211176120f357604052565b60c081019081106001600160401b038211176120f357604052565b90601f801991011681019081106001600160401b038211176120f357604052565b3461047057602036600319011261047057612192611f7d565b5060206040515f8152f35b34610470576040366003190112610470576121b6611f93565b5060405162461bcd60e51b815260206004820152600760248201526611115413d4d25560ca1b6044820152606490fd5b606090600319011261047057600435906001600160a01b03906024358281168103610470579160443590811681036104705790565b3461047057612229366121e6565b505060405162461bcd60e51b815260206004820152600660248201526552454445454d60d01b604482015260649150fd5b34610470576020366003190112610470576002546001810180911161080457612281612ba9565b9060018201809211610804576020916107fc91600435612b28565b335f9081527fe790de7705c8ebaa80068cd4fc0a095afd63ddb3e1cbffe9ca6f4baedbd7b739602090815260408083205490927f97667070c54ef182b0f5858b034beac1b6f3089aa2d3188bb1e8929f4fa9b9299160ff16156122ff5750505050565b61230833612944565b91845190612315826120d8565b6042825284820192606036853782511561246157603084538251906001918210156124615790607860218501536041915b8183116123f4575050506123c5576048611a969386936123a99361239a985198899376020b1b1b2b9b9a1b7b73a3937b61d1030b1b1b7bab73a1604d1b8a860152611a42815180928c603789019101611f19565b01036028810187520185612158565b5192839262461bcd60e51b845260048401526024830190611f3a565b60648486519062461bcd60e51b825280600483015260248201525f80516020615dd48339815191526044820152fd5b909192600f8116601081101561244d576f181899199a1a9b1b9c1cb0b131b232b360811b901a6124248587612933565b5360041c928015612439575f19019190612346565b634e487b7160e01b82526011600452602482fd5b634e487b7160e01b83526032600452602483fd5b634e487b7160e01b81526032600452602490fd5b5f9080825260209060058252604092838120338252835260ff84822054161561249e5750505050565b6124a733612944565b918451906124b4826120d8565b6042825284820192606036853782511561246157603084538251906001918210156124615790607860218501536041915b818311612539575050506123c5576048611a969386936123a99361239a985198899376020b1b1b2b9b9a1b7b73a3937b61d1030b1b1b7bab73a1604d1b8a860152611a42815180928c603789019101611f19565b909192600f8116601081101561244d576f181899199a1a9b1b9c1cb0b131b232b360811b901a6125698587612933565b5360041c928015612439575f190191906124e5565b905f918083526005602052604083209160018060a01b03169182845260205260ff6040842054166125ae57505050565b8083526005602052604083208284526020526040832060ff1981541690557ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b339380a4565b9190820180921161080457565b6001600160a01b0390811691821561270757169182156126b6575f8281528060205260408120549180831061266257604082825f80516020615e14833981519152958760209652828652038282205586815220818154019055604051908152a3565b60405162461bcd60e51b815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e7420657863656564732062604482015265616c616e636560d01b6064820152608490fd5b60405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b6064820152608490fd5b60405162461bcd60e51b815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604482015264647265737360d81b6064820152608490fd5b6001600160a01b0390811691821561280757169182156127b75760207f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591835f526001825260405f20855f5282528060405f2055604051908152a3565b60405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b6064820152608490fd5b60405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608490fd5b9060018060a01b038083165f52600160205260405f209082165f5260205260405f2054925f19840361288a5750505050565b8084106128a55761289c93039161275a565b5f808080612072565b60405162461bcd60e51b815260206004820152601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e63650000006044820152606490fd5b6128f2612ba9565b600181018091116108045760025490600182018092116108045761291592612b28565b90565b6001600160401b0381116120f357601f01601f191660200190565b908151811015611fdf570160200190565b60405190606082018281106001600160401b038211176120f357604052602a8252602082016040368237825115611fdf57603090538151600190811015611fdf57607860218401536029905b8082116129d15750506129a05790565b606460405162461bcd60e51b815260206004820152602060248201525f80516020615dd48339815191526044820152fd5b9091600f81166010811015612a28576f181899199a1a9b1b9c1cb0b131b232b360811b901a612a008486612933565b5360041c918015612a14575f190190612990565b60245f634e487b7160e01b81526011600452fd5b60245f634e487b7160e01b81526032600452fd5b9190612a49828285612b28565b928215612a665709612a585790565b600181018091116108045790565b634e487b7160e01b5f52601260045260245ffd5b8115612a66570490565b905f1981830981830291828083109203918083039214612b1d576127109082821115612ae0577fbc01a36e2eb1c432ca57a786c226809d495182a9930be0ded288ce703afb7e91940990828211900360fc1b910360041c170290565b60405162461bcd60e51b81526020600482015260156024820152744d6174683a206d756c446976206f766572666c6f7760581b6044820152606490fd5b505061271091500490565b915f198284099282810292838086109503948086039514612b9c5784831115612ae05782910960018219018216809204600280826003021880830282030280830282030280830282030280830282030280830282030280920290030293600183805f03040190848311900302920304170290565b5050906129159250612a7a565b60025415612bc557612915600c54612bbf615c6a565b906125f3565b5f90565b15612bd057565b60405162461bcd60e51b815260206004820152600560248201526456414c554560d81b6044820152606490fd5b600260065414612c0e576002600655565b60405162461bcd60e51b815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c006044820152606490fd5b15612c5a57565b60405162461bcd60e51b81526020600482015260086024820152672922a1a2a4ab22a960c11b6044820152606490fd5b15612c9157565b60405162461bcd60e51b815260206004820152600360248201526226a4a760e91b6044820152606490fd5b15612cc357565b60405162461bcd60e51b815260206004820152600660248201526553484152455360d01b6044820152606490fd5b5f1981146108045760010190565b9060016001600160401b038093160191821161080457565b9190820391821161080457565b818110612d2f575050565b5f8155600101612d24565b612d44815461200f565b9081612d4e575050565b81601f5f9311600114612d605750555b565b908083918252612d7f601f60208420940160051c840160018501612d24565b5555565b5190811515820361047057565b3d15612dba573d90612da182612918565b91612daf6040519384612158565b82523d5f602084013e565b606090565b91929015612e215750815115612dd3575090565b3b15612ddc5790565b60405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606490fd5b825190915015612e345750805190602001fd5b60405162461bcd60e51b815260206004820152908190611a96906024830190611f3a565b5f9060ff60135460101c16612ef4578015612eee57612ea7612ea1612e9b612e95612e8f612e89612ead97876139e2565b86613aa3565b85613b2b565b84613bb3565b83613c3a565b82613cc2565b90613d4a565b90612eb9601454612cf1565b601455816015556040518281527f4889d7d38c1d1f265318505a98367d07d41591d6b7f9cc000f37ea92b8311daa60203392a2565b50505f90565b60405162461bcd60e51b81526020600482015260086024820152671417d4105554d15160c21b6044820152606490fd5b5f908015612eee57612f42612f3c61291593836139e2565b82613aa3565b90613b2b565b60ff16600181146138f957600281146138f057600381146138e757600481146138de5760058114612f975760068114612f8e57600714612f86575f90565b612915614e3f565b50612915614b40565b50604051612fa481612107565b604051600954815f612fb58361200f565b80835292600181169081156138bf5750600114613880575b612fd992500382612158565b81525f6020820152602060408201525f60608201525f60808201525f60405180809363186b216760e01b82526040600483015260126044830152711093d39117d4d510551554d7d093d391115160721b606483015260806024830152608061304e825160a06084860152610124850190611f3a565b916001600160401b0360208201511660a48501526001600160401b0360408201511660c48501526060810151151560e48501520151151561010483015203816108005afa918215613875575f91829361349e575b505f5b825181101561312c576130b88184614b2c565b51906040820151158015906130fe575b6130e7929080156130f2575b6130ec576130e29051615345565b612cf1565b6130a5565b50612cf1565b506080810151156130d4565b5060608201519160048310156131185791600314156130c8565b634e487b7160e01b5f52602160045260245ffd5b50909180519283516001600160401b0381116120f35761314d60095461200f565b601f8111613445575b50602094601f82116001146133e1579481929394955f926133d6575b50508160011b915f199060031b1c1916176009555b815151156131a6575b51159081159161319e575090565b905051511590565b91906131b0615bb7565b505f5b6007548110156131ef578060036131cc6131ea93611fc3565b5001805460ff191690555f60016131e283611fc3565b500155612cf1565b6131b3565b50905f5b6008548110156132ba5761322861321c61322361320f84611ff3565b5060405192838092612047565b0382612158565b615d8d565b9015613268579081600361323e61326394611fc3565b5001600160ff1982541617905560016131e28161325a85611ff3565b50015492611fc3565b6131f3565b50600790815460208110613282575b506132639150612cf1565b61328b82611ff3565b50600160401b8210156120f3576132ae826132639560016132b495019055611fc3565b906155b7565b5f613277565b5090915f915b60078054808510156133925760ff60036132d987611fc3565b50015416158061337d575b1561336b575f19908082019081116108045761330261330c91611fc3565b506132ae87611fc3565b8154801561335757019061331f82611fc3565b613344576003816133305f93612d3a565b8260018201558260028201550155556132c0565b634e487b7160e01b5f525f60045260245ffd5b634e487b7160e01b5f52603160045260245ffd5b50509161337790612cf1565b916132c0565b50600261338986611fc3565b500154156132e4565b505092915061339f613902565b7f17e14f5a0c6831022cc1f0f72d2d717ac74633f5193b19f76e18d9711b6d7cec60206133ca613996565b604051908152a1613190565b015190505f80613172565b601f1982169560095f5260205f20915f5b88811061342d57508360019596979810613415575b505050811b01600955613187565b01515f1960f88460031b161c191690555f8080613407565b919260206001819286850151815501940192016133f2565b60095f5261348e907f6e1540171b6c0c960b71a7020d9f60077f6af931a8bbf590da0223dacf75c7af601f840160051c81019160208510613494575b601f0160051c0190612d24565b5f613156565b9091508190613481565b919092503d8082843e6134b18184612158565b60408382810103126135b4578251916001600160401b0383116135ad57818401601f8486010112156135ad57828401516134ea81614ab2565b936134f86040519586612158565b818552602085019084870160208460051b838a010101116135b057602081880101915b60208460051b838a01010183106135b8575050505060208401516001600160401b0381116135b45784019160408382870103126135b4576040519461355f86612122565b83516001600160401b0381116135b057840192828201601f850112156135ad57506020926135a094926135989201908481519101614ac9565b855201614b18565b602083015290915f6130a2565b80fd5b8380fd5b5080fd5b82516001600160401b038111613871576101608984018201888b0103601f1901126138715760405190816101608101106001600160401b036101608401111761385d576101608201604052602081858c010101516001600160401b038111613859578a60206136309284888d85019401010101614afe565b8252604081858c010101516001600160401b038111613859578a602061365f9284888d85019401010101614afe565b6020830152613674606082868d010101612d83565b6040830152608081858c010101516004811015613859576060830152898401810160a081810151608085015260c08201519084015260e001516001600160401b0381116138595760a0858c01830182018a8d0103601f190112613859576040519060208184888f6136e487612107565b01010101516001600160401b038111613855578184888f6137168f956020604097840191878787870101010101614afe565b875201010101516001600160401b038111613855578184888f61374a8f956020606097840191878787870101010101614afe565b602088015201010101516001600160401b038111613855578184888f6137818f956020608097840191878787870101010101614afe565b604088015201010101516001600160401b038111613855578184888f6137b88f95602060a097840191878787870101010101614afe565b60608801520101010151906001600160401b0382116138555761016084602097948f938a8f8b9a976137f6938c9b8c938a01948a0101010101614afe565b608082015260c0850152886101009361381485848484010101614475565b60e08701526101209461382c86858585010101614475565b90870152610140948584848401010151908701520101015190820152815201930192905061351b565b8980fd5b8780fd5b634e487b7160e01b87526041600452602487fd5b8580fd5b6040513d5f823e3d90fd5b505060095f52816020805f20925f905b8082106138a55750612fd99350820101612fcd565b845482870184015260019094019385935090820190613890565b60209250612fd994915060ff191682840152151560051b820101612fcd565b506129156150a7565b506129156149d5565b50612915613dd2565b506129156140a8565b6008545f8060085581613913575050565b6001600160fe1b038216820361398257600881527ff3f7a9fe364faab93b216da50a3214154f22a0a2b415b23a84c8169e8b636ee3600292831b810192905b83811061395f5750505050565b8061396b600492612d3a565b836001820155838382015583600382015501613952565b634e487b7160e01b81526011600452602490fd5b5f905f600754905b8181106139a9575050565b60ff60036139b683611fc3565b500154166139cd575b6139c890612cf1565b61399e565b926139da6139c891612cf1565b9390506139bf565b81101561291557604051630e47b40f60e21b8152600160048201526020905f90828160248185305af1829181613a68575b50613a4e5750505f80516020615df4833981519152600191613a33612d90565b90613a48604051928284938452830190611f3a565b0390a290565b909150613a59575090565b60018201809211613982575090565b9091508381813d8311613a9c575b613a808183612158565b81010312613a9857613a9190612d83565b905f613a13565b8280fd5b503d613a76565b81101561291557604051630e47b40f60e21b8152600260048201526020905f90828160248185305af1829181613af4575b50613a4e5750505f80516020615df4833981519152600291613a33612d90565b9091508381813d8311613b24575b613b0c8183612158565b81010312613a9857613b1d90612d83565b905f613ad4565b503d613b02565b81101561291557604051630e47b40f60e21b8152600360048201526020905f90828160248185305af1829181613b7c575b50613a4e5750505f80516020615df4833981519152600391613a33612d90565b9091508381813d8311613bac575b613b948183612158565b81010312613a9857613ba590612d83565b905f613b5c565b503d613b8a565b81101561291557604051630e47b40f60e21b81526004808201526020905f90828160248185305af1829181613c03575b50613a4e5750505f80516020615df4833981519152600491613a33612d90565b9091508381813d8311613c33575b613c1b8183612158565b81010312613a9857613c2c90612d83565b905f613be3565b503d613c11565b81101561291557604051630e47b40f60e21b8152600560048201526020905f90828160248185305af1829181613c8b575b50613a4e5750505f80516020615df4833981519152600591613a33612d90565b9091508381813d8311613cbb575b613ca38183612158565b81010312613a9857613cb490612d83565b905f613c6b565b503d613c99565b81101561291557604051630e47b40f60e21b8152600660048201526020905f90828160248185305af1829181613d13575b50613a4e5750505f80516020615df4833981519152600691613a33612d90565b9091508381813d8311613d43575b613d2b8183612158565b81010312613a9857613d3c90612d83565b905f613cf3565b503d613d21565b81101561291557604051630e47b40f60e21b8152600760048201526020905f90828160248185305af1829181613d9b575b50613a4e5750505f80516020615df4833981519152600791613a33612d90565b9091508381813d8311613dcb575b613db38183612158565b81010312613a9857613dc490612d83565b905f613d7b565b503d613da9565b6001600160401b039081601154165f90815260209060188252604091828220805460ff811615908115614082575b508015614076575b61406e57613e146150a7565b5060018101805496613e25886128ea565b80156140635761010061ffff1985541617845580600285015530156140155730865285855286862054898110613fc65795898198999a601898308452838952038a832055806002540360025589519081525f80516020615e14833981519152873092a3613ea8600c548281105f14613fbf57805b8180613f88575b505082612d17565b92879380613f6f575b5090613f279160098601956001600160401b0319968781541687871617905560048101541580613f63575b613f44575b507f5d78323b7b570071308d257743c74a9a2401f77d37e9de9f908da12c8917a40b60608b601154978789169586955492519283528b83015260070b8d820152a2612cff565b16918291161760115583525220600160ff19825416179055600190565b6201000062ff000019825416178155600760038201549101555f613ee1565b50600681015415613edc565b613f2792919450613f8090866144fe565b939091613eb1565b613f9191612d17565b600c55613fa081600f546125f3565b600f55613faf816010546125f3565b6010558060038701555f81613ea0565b8290613e99565b875162461bcd60e51b815260048101879052602260248201527f45524332303a206275726e20616d6f756e7420657863656564732062616c616e604482015261636560f01b6064820152608490fd5b865162461bcd60e51b815260048101869052602160248201527f45524332303a206275726e2066726f6d20746865207a65726f206164647265736044820152607360f81b6064820152608490fd5b509396505050505050565b509093505050565b50600181015415613e08565b60ff915060081c165f613e00565b6001600160401b038091169081146108045760010190565b6001600160401b0360119182549282604094851c169260019484955b828454169483881695861015614444575f958087526020601881528488209687549460ff9586808260081c1615918215614437575b50506144085750889960068901546143765760098901805489811642106143f45789818a1c1615614388575054871c88164311156143765760019b8a600a8b019c5b8d54821015614272578a8e9f8f908b8f6141578f9488906144ad565b509360028501908154938460c01c1661426157614173866159d1565b9790156141e6575050505050506001019081548082106141a3575b50505061419b8d92612cf1565b919e5061413b565b816141ad91612d17565b915560048d016141be828254612d17565b9055600e6141cd828254612d17565b90556141dc600f918254612d17565b90555f808061418e565b9091938096939599975042921c1611155f14614251576001015460ff60c01b19909316600160c01b17905560048101805461419b949392600592909161422d908590612d17565b90550161423b8282546125f3565b905561424a600e918254612d17565b9055612cf1565b50505050915061419b8d92612cf1565b5050505093905061419b9150612cf1565b959b969c50509195509a97959691971561435c57505050966142f17f73f330dd18a8f4760be8fa04b392ba243a76dcbe07487724e846182d63dbfafa96979860058501908154916142c1615ce8565b808410156143565750825b83818110614337575b505060106142e48482546125f3565b90555560038501546125f3565b60078401908155835462ff000019166201000017909355815467ffffffffffffffff60401b191690841b67ffffffffffffffff60401b16179055549051908152a2600190565b61434091612d17565b61434d600f918254612d17565b90555f836142d5565b926142cc565b925094935096955061436f919450614090565b95946140c4565b9a9398509650505061436f9150614090565b99509293955095985098507f5e49965d9b92858198b2c5bab7c3a3fa70fcaf004a265239d97b798b708646ce9299506001600160401b0360401b956001600160401b0360401b19978888438c1b16911617809255885191891c168152a28454931b169116179055600190565b50509a9398509650505061436f9150614090565b999a945096505050945085614421575b61436f90614090565b955061436f61442f87612cff565b969050614418565b60101c169050865f6140f9565b845467ffffffffffffffff60401b19169690921b67ffffffffffffffff60401b1695909517909255505f9350915050565b51908160070b820361047057565b9392916144a89060409260018060a01b03168652606060208701526060860190612047565b930152565b8054821015611fdf575f52600360205f20910201905f90565b9190601f81116144d557505050565b612d5e925f5260205f20906020601f840160051c8301931061349457601f0160051c0190612d24565b91905f90600754156149a35760165443146149915761451b615bb7565b505f925f5b600754811080614988575b1561495a5761453981611fc3565b5060028101805496871561494d575060019660076145666040516145618161321c8189612047565b615afb565b1015614941578581101561493957915b604051630fb6accf60e21b8152916020838061459787863060048501614483565b03815f6108005af15f93816148fd575b506145be57505050506145b990612cf1565b614520565b61321c91816145d586614621949a97989a54612d17565b9055600d6145e4878254612d17565b9055600e6145f38782546125f3565b9055600f6146028782546125f3565b905560048b016146138782546125f3565b905560405192838092612047565b5f60a06040516146308161213d565b60608152826020820152826040820152826060820152826080820152015261469e5f6040519261465f8461213d565b8084528660208501528160408501528460070b60608501528160808501528160a08501526040518093819263a03ffee160e01b8352306004840161598d565b03816108005afa5f91816148d9575b50614838575b50600a890154600160401b8110156120f35780896146da9201600a8c0155600a8b016144ad565b6133445781518051906001600160401b0382116120f357614705826146ff855461200f565b856144c6565b602090601f83116001146147d25760029392915f91836147c7575b50505f19600383901b1c1916908b1b1781555b60208301518a82015501906040810151908254606082015160401b906001600160401b038060801b608085015160801b169360a060ff60c01b910151151560c01b1694169066ffffffffffffff60c81b1617906001600160401b0360401b1617171790558560070b8160070b136147bd575b506145b9916147b391612d17565b9243601655612cf1565b94506145b96147a5565b015190505f80614720565b92918b91835f5260205f20905f5b601f1984168110614820575060029583601f19811610614808575b505050811b018155614733565b01515f1960f88460031b161c191690555f80806147fb565b8187015183558e9490920191602091820191016147e0565b885f815b614848575b50506146b3565b60408301519081518110156148d3576148648161488293614b2c565b518660070b602082015160070b14806148b5575b6148885750612cf1565b8161483c565b60808160606001600160401b039301516020890152805160070b604089015201511660808601525f6130ec565b506001600160401b0380608083015116906080880151161115614878565b50614841565b6148f69192503d805f833e6148ee8183612158565b81019061584a565b905f6146ad565b9093506020813d602011614931575b8161491960209383612158565b810103126104705761492a90614475565b925f6145a7565b3d915061490c565b508491614576565b5050506145b990612cf1565b965050506145b990612cf1565b50929391938115159081614980575b501561497757600690910155565b5060065f910155565b90505f614969565b5082151561452b565b60068401555060099091015460070b90565b60405162461bcd60e51b815260206004820152600a60248201526956414c494441544f525360b01b6044820152606490fd5b6016544314612bc5576001600160401b0360119081549181604093841c16925b8282541683851690811015614aa95781905f90815260186020522093845460ff808260081c1615918215614a9c575b50508015614a90575b614a79575050506009614a446006840154846144fe565b92019182549160078284820b910b13614a61575b50505050600190565b16906001600160401b0319161790555f808080614a58565b8391929450614a8790614090565b939190506149f5565b50600685015415614a2d565b60101c1690505f80614a24565b50505050505f90565b6001600160401b0381116120f35760051b60200190565b90929192614ad681612918565b91614ae46040519384612158565b829482845282820111610470576020612d5e930190611f19565b9080601f8301121561047057815161291592602001614ac9565b51906001600160401b038216820361047057565b8051821015611fdf5760209160051b010190565b60028060075410614e3a57614b53615bb7565b506007545f80805b838110614de357508115908115614dd7575b50614dd05790614b7c82615604565b915f94919490819482938390600d54915b838110614d1c575050505015908115614d13575b508015614d0a575b614aa95780821015614d025750915b66038d7ea4c680008310614cfa57614bd2614bd991611fc3565b5091611fc3565b50916040516354b826f560e01b81523060048201526080602482015260208180614c1b614c096084830188612047565b82810360031901604484015288612047565b85606483015203815f6108005af15f9181614cbf575b50614c3f5750505050505f90565b614ca9614c9b94867f2a3f2dce4ae3c46ff8fd3dbcf9cac1ca39da18b59d0ac57b2047d061ec99c1cb978601614c76868254612d17565b90558101614c858582546125f3565b9055604051958695608087526080870190612047565b908582036020870152612047565b91604084015260070b60608301520390a1600190565b90916020823d8211614cf2575b81614cd960209383612158565b810103126135ad5750614ceb90614475565b905f614c31565b3d9150614ccc565b505050505f90565b905091614bb8565b50828414614ba9565b9050155f614ba1565b60ff6003614d2983611fc3565b5001541615614dc757614d3b81611fc3565b50614d4c8360018093015486612b28565b908c8981614d5986611fc3565b50015484119182614da7575b5050614d7c575b5050614d7790612cf1565b614b8d565b9296509850955093614d77614d9f87998c614d968a611fc3565b50015490612d17565b96905f614d6c565b614dbf919250614db686611fc3565b50015484612d17565b11898e614d65565b614d7790612cf1565b5050505f90565b9050600d54105f614b6d565b60ff6003614df083611fc3565b50015416614e07575b614e0290612cf1565b614b5b565b9091614e32614e27614e02926001614e1e86611fc3565b500154906125f3565b9386614e1e85611fc3565b919050614df9565b505f90565b600254158015615095575b612bc557614e56615bb7565b50600d54600c5490614e6882826125f3565b9082600b5480615043575b50505f908180600754905b818110614ffa5750508015614aa957614e9a85614ea0936125f3565b90615746565b90949115614aa95780614ff55750805b80821015614fed5750925b8315614cfa57614eca90611fc3565b50916127108281018091116108045761270f830190811161080457614f0492614ef291612a7a565b610ce480821115614fe5575090612a84565b916002820192835481811015614aa957614f1d91612d17565b80821015614fdd5750905b8115614dd057602082614f519260405193849283926353266bbb60e01b84523060048501614483565b03815f6108005af15f9181614fa2575b50614f6d575050505f90565b15612eee57614f9a91614f8282600c54612d17565b600c55614f908282546125f3565b9055600d546125f3565b600d55600190565b90916020823d8211614fd5575b81614fbc60209383612158565b810103126135ad5750614fce90612d83565b905f614f61565b3d9150614faf565b905090614f28565b905090612a84565b905092614ebb565b614eb0565b60ff600361500783611fc3565b5001541661501e575b61501990612cf1565b614e7e565b939161503b615035615019926001614e1e89611fc3565b93612cf1565b949050615010565b909193506127106150548285612a84565b918409615075575b80821115614cfa5761506d91612d17565b915f80614e73565b600181018091111561505c57634e487b7160e01b5f52601160045260245ffd5b5066038d7ea4c68000600c5410614e4a565b600754158015615239575b612bc5576040516370a0823160e01b8082523060048301527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906020908184602481865afa938415613875575f9461520a575b5063ffffffff6007541660405190632efe8a5f60e01b8252306004830152602482015282816044815f6108015af15f91816151d3575b506151525750505050505f90565b15614cfa57819060246040518095819382523060048301525afa908115613875575f916151a7575b50905081811161518a5750505f90565b61519f9161519791612d17565b600c546125f3565b600c55600190565b82813d83116151cc575b6151bb8183612158565b810103126135ad575051805f61517a565b503d6151b1565b90918482813d8311615203575b6151ea8183612158565b810103126135ad57506151fc90612d83565b905f615144565b503d6151e0565b90938282813d8311615232575b6152218183612158565b810103126135ad575051925f61510e565b503d615217565b50615242615bb7565b156150b2565b909291926133445782519283516001600160401b0381116120f357615271816146ff855461200f565b6020601f82116001146152db579181606092600394612d5e97985f926152d0575b50508160011b915f1990861b1c19161784555b60208101516001850155604081015160028501550151151591019060ff801983541691151516179055565b015190505f80615292565b601f19821695845f52815f20965f5b81811061532d575092612d5e9697600395936001938360609710615316575b505050811b0184556152a5565b01515f1983881b60f8161c191690555f8080615309565b838301518955600190980197602093840193016152ea565b5f5b6008548110156153bf576153778261321c61536461320f85611ff3565b6020815191012090602081519101201490565b6153895761538490612cf1565b615347565b905061539481611ff3565b5060018091015491670de0b6b3a76400009283116153b157505050565b6153ba90611ff3565b500155565b5060085460ff600a5416811061549357505f90600854801561244d57600883527ff3f7a9fe364faab93b216da50a3214154f22a0a2b415b23a84c8169e8b636ee4546001939091845b818110615459575050670de0b6b3a76400009182116154275750505050565b61289c93615453926040519461543c866120d8565b855260208501525f60408501526060840152611ff3565b90615248565b838661546483611ff3565b5001541061547b575b61547690612cf1565b615408565b915091508361548982611ff3565b500154918161546d565b90604051906154a1826120d8565b8152670de0b6b3a764000060208201525f604082015260016060820152600160401b8210156120f357615453826001612d5e9401600855611ff3565b908082146155b3576154ef815461200f565b906001600160401b0382116120f35781906155148261550e865461200f565b866144c6565b5f90601f8311600114615548575f9261553d575b50508160011b915f199060031b1c1916179055565b015490505f80615528565b81526020808220858352818320935090601f1985169083905b82821061559a575050908460019594939210615582575b505050811b019055565b01545f1960f88460031b161c191690555f8080615578565b8495819295850154815560018091019601940190615561565b5050565b90613344578181036155c7575050565b600360ff81846155da612d5e96866154dd565b600181015460018601556002810154600286015501541691019060ff801983541691151516179055565b600754915f5b8381106156e857505f809381938290600d54915b83811061562e5750505050929190565b60ff600361563b83611fc3565b50015416156156df5761564d81611fc3565b5061565e8360018093015486612b28565b61566783611fc3565b509160028093015466038d7ea4c680008301908184116108045711806156c9575b61569e575b50505061569990612cf1565b61561e565b929950899850919550615699916156c0916156b88a611fc3565b500154612d17565b96905f8061568d565b50896156d983856156b888611fc3565b11615688565b61569990612cf1565b60ff60036156f883969596611fc3565b500154161580615730575b6157185761571090612cf1565b92919261560a565b92509050600261572783611fc3565b50015460019291565b50600261573c82611fc3565b5001541515615703565b90915f809381938291600754925b8381106157aa57505050811561576b575b50929190565b5f5b81811061577a5750615765565b60ff600361578783611fc3565b5001541661579d5761579890612cf1565b61576d565b6001955093505f92915050565b60ff60036157b783611fc3565b5001541615615841576157c981611fc3565b506157da8460018093015485612b28565b6157e383611fc3565b509160028093015482118061582c575b615809575b50505061580490612cf1565b615754565b9299508998509195506158049161582391614d968a611fc3565b96905f806157f8565b508961583b84614db687611fc3565b116157f3565b61580490612cf1565b60209081818403126104705780516001600160401b0391828211610470570192606092838583031261047057604092835195858701878110838211176120f3578552805182811161047057846158a1918301614afe565b87528281015182811161047057846158ba918301614afe565b838801528481015191821161047057019082601f83011215610470578151926158e284614ab2565b956158ef86519788612158565b848752828701918360c080970286010194818611610470578401925b85841061591f575050505050505082015290565b86848303126104705784879189516159368161213d565b61593f87614475565b815261594c838801614475565b838201528a8701518b8201528587015186820152608061596d818901614b18565b9082015260a061597e818901614475565b9082015281520193019261590b565b6001600160a01b03909116815260406020820181905261291592910190611f3a565b6001600160a01b03909116815260406020820181905261291592910190612047565b6040805163a03ffee160e01b81525f939290918483806159f58530600484016159af565b03816108005afa859381615adf575b50615a11575b5050508190565b90919293805b828501518051821015615ad05781615a2e91614b2c565b5160028501546001600160401b036080918181841c1680159283159485615ac1575b50505081615aae575b81615a97575b508115615a8f575b50615a7e5750615a778391612cf1565b9050615a17565b606001516001969095509350505050565b90505f615a67565b90506020830151600791871c820b910b145f615a5f565b9050825160079082820b910b1490615a59565b860151161492505f8080615a50565b505093925050505f8080615a0a565b615af49194503d8088833e6148ee8183612158565b925f615a04565b5f615b1b916040518093819263a03ffee160e01b8352306004840161598d565b03816108005afa5f9181615b3c575b50615b3457505f90565b604001515190565b615b5091923d8091833e6148ee8183612158565b905f615b2a565b9190916040818403126104705780519260208201516001600160401b039283821161047057016040818303126104705760405192615b9484612122565b815190811161047057602092615bab918301614afe565b83520151602082015290565b5f905f805b600754811015615c6257615bef82615bd383611fc3565b506040518093819263120bba7360e11b835230600484016159af565b03816108005afa839181615c3c575b50615c195750808260026131e2615c1494611fc3565b615bbc565b615c1491946020615035920180516002615c3289611fc3565b50015551906125f3565b615c599192503d8086833e615c518183612158565b810190615b57565b9050905f615bfe565b505081600d55565b5f905f80600754915b828110615c7f57505050565b615c8c82615bd383611fc3565b03816108005afa839181615cca575b50615cb0575b50615cab90612cf1565b615c73565b615cab91956020615cc3920151906125f3565b9490615ca1565b615cdf9192503d8086833e615c518183612158565b9050905f615c9b565b6040516370a0823160e01b81523060048201526020816024817f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165afa908115613875575f91615d5c575b50615d4b600c54601054906125f3565b80821115612eee5761291591612d17565b906020823d8211615d85575b81615d7560209383612158565b810103126135ad5750515f615d3b565b3d9150615d68565b60075491905f5b838110615da557505090505f905f90565b615db88261321c61536461320f85611fc3565b615dca57615dc590612cf1565b615d94565b9250506001919056fe537472696e67733a20686578206c656e67746820696e73756666696369656e74f1190c18d28d5d68156de3ae0f68fd157173bc96def1c9c2fae201841639d0adddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa26469706673582212205ab468d1ee0dd993e1ea54c589722f70487615c71efb3d5b9957a7c28c2b000864736f6c634300081400332f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d", + "deployedBytecode": "0x60806040526004361015610011575f80fd5b5f3560e01c806301e1d11414611eff57806301ffc9a714611ea957806303e5fa8814611e8057806306fdde0314611dde57806307a2d13a14611491578063095ea7b314611db85780630a28a47714611d765780630a763da114611d505780630f43a67714611d3357806318160ddd14611d165780631f9acd7e14610e2f57806323b872dd14611cde578063248a9ca314611cb1578063291cdb8c14611c8c5780632d9414b514611c725780632f2ff15d14611bca578063313ce56714611b8357806332145f9014611b58578063331c138c146118e257806333a73916146118c557806333bb7f91146116f457806335aa2e441461167257806336568abe146115e057806338d52e0f1461159c578063391ed03c1461153757806339509351146114ea578063402d267d146106af5780634221b2c2146114cd5780634864b09d146114b0578063499bafcc146114965780634cdad506146114915780634f7b901314611470578063559904cb1461139c578063588f134314610eed5780635ac22fd214610ec657806360da3e8314610ea45780636637e38c14610e885780636a84a98514610e6b5780636c68e40314610e2f5780636e553f6514610a2757806370a0823114610e34578063714897df14610e2f5780637316a19614610e0f57806377397cdd14610df25780637aed3cbb14610dd55780637d41c86e14610b4457806380d04de814610b275780638ce7a27f14610b0a5780638df8280014610ad257806391d1485414610a89578063937b258114610a2c57806394bf804d14610a2757806395d89b4114610944578063a217fddf1461092a578063a41fcdc3146105e8578063a457c2d714610887578063a6e93b471461086b578063a9059cbb1461083a578063ac44da5214610818578063b3d7f6b9146107ba578063b460af94146107b5578063ba087652146107b5578063c63d75b6146106af578063c6e6f592146104df578063ce96cb77146106af578063cfdbdb0214610799578063d3892a1a14610710578063d547741f146106d1578063d77bb97c146106b4578063d905777e146106af578063dd62ed3e14610662578063e63ab1e914610628578063e9f2838e14610603578063ec7552cb146105e8578063ecdcce24146104e4578063ef8b30f7146104df578063f5b541a6146104a55763f7ac2a5214610373575f80fd5b60208060031936011261047057600435906001600160401b039081831161047057366023840112156104705782600401359180831161047057602493600590368686841b83010111610470576103c93415612bc9565b6103d161229c565b83851161047b575f5b8581106103e357005b8681841b83010135908482168092036104705760405191639ec4d36360e01b83523060048401528883015260026044830152608060648301525f6084830152858260a4815f6108055af1610441575b61043c9150612cf1565b6103da565b8582813d8311610474575b6104568183612158565b810103126104705761046a61043c92612d83565b50610432565b5f80fd5b503d61044c565b60648483886040519262461bcd60e51b845260048401528201526408482a886960db1b6044820152fd5b34610470575f3660031901126104705760206040517f97667070c54ef182b0f5858b034beac1b6f3089aa2d3188bb1e8929f4fa9b9298152f35b61225a565b60203660031901126104705760043560ff8116809103610470576105083415612bc9565b61051061229c565b801515806105dd575b156105b0576020817fe28c6c3955ed05a471a85214f10fe95cb125572d2aadeb008a7c5992533984969260ff19600a541617600a55610556613902565b61056160095461200f565b80610571575b50604051908152a1005b601f811160011461058857505f6009555b83610567565b5f90600982526105a6601f858420920160051c820160018301612d24565b8160095555610582565b60405162461bcd60e51b815260206004820152600560248201526410d3d5539560da1b6044820152606490fd5b506020811115610519565b34610470575f36600319011261047057602060405160078152f35b34610470575f36600319011261047057602060ff60135460081c166040519015158152f35b34610470575f3660031901126104705760206040517f65d7a28e3265b37a6474929f336521b332c1681b933f6cb9f3376673440d862a8152f35b346104705760403660031901126104705761067b611f7d565b610683611f93565b9060018060a01b038091165f52600160205260405f2091165f52602052602060405f2054604051908152f35b612179565b34610470575f366003190112610470576020600f54604051908152f35b346104705760403660031901126104705761070e6004356106f0611f93565b90805f526005602052610709600160405f200154612475565b61257e565b005b6020366003190112610470576004356107293415612bc9565b61073161229c565b611388811161076b576020817f96e02ba2ef5107e698b6e6f9ebbd4debf3d30ac1c704d2b7bbe7ee39085eaaca92600b55604051908152a1005b60405162461bcd60e51b8152602060048201526006602482015265212aa32322a960d11b6044820152606490fd5b34610470575f3660031901126104705760206040516113888152f35b61221b565b34610470576020366003190112610470576107d3612ba9565b60018101809111610804576002549060018201809211610804576020916107fc91600435612a3c565b604051908152f35b634e487b7160e01b5f52601160045260245ffd5b34610470575f366003190112610470576020604051670de0b6b3a76400008152f35b3461047057604036600319011261047057610860610856611f7d565b6024359033612600565b602060405160018152f35b34610470575f366003190112610470576020604051610ce48152f35b34610470576040366003190112610470576108a0611f7d565b60243590335f52600160205260405f2060018060a01b0382165f5260205260405f2054918083106108d7576108609203903361275a565b60405162461bcd60e51b815260206004820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f77604482015264207a65726f60d81b6064820152608490fd5b34610470575f3660031901126104705760206040515f8152f35b34610470575f366003190112610470576040515f906004546109658161200f565b8083526001918083169081156109ff57506001146109a6575b6109a28361098e81870382612158565b604051918291602083526020830190611f3a565b0390f35b60045f90815260209450917f8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19b5b8284106109ec5750505081019091019061098e8161097e565b80548585018701529285019281016109d3565b6109a2955061098e93506020915091849260ff191682840152151560051b820101935061097e565b61219d565b34610470576020366003190112610470576004355f526017602052608060405f2060018060a01b036001600160401b03818354169260026001820154910154926040519485528116602085015260a01c1660408301526060820152f35b3461047057604036600319011261047057610aa2611f93565b6004355f52600560205260405f209060018060a01b03165f52602052602060ff60405f2054166040519015158152f35b602036600319011261047057610ae83415612bc9565b610af0612bfd565b6020610afd600435612f24565b6001600655604051908152f35b34610470575f366003190112610470576020600b54604051908152f35b34610470575f366003190112610470576020600d54604051908152f35b610b4d366121e6565b610b573415612bc9565b610b5f612bfd565b60ff60135460081c16610da557610b77831515612cbc565b6001600160a01b0391808316610b8e811515612c53565b838316928315610d785785610beb817f1dfdc691a597a83af2430bd31addf8d2129d012eab8b35de6d696dc91e64852393610bdc66038d7ea4c68000610bd560209c6128ea565b1015612c8a565b873303610d68575b3090612600565b601254958693610bfa85612cf1565b601255601154906001600160401b039283831692835f5260188c528460405f2094855460ff811615908115610d5a575b50610d1c575b506002929150601154169260405193610c48856120d8565b8b85528d85019182526040850190815260608501928884528a5f5260178f528060405f2096511660018060a01b0319875416178655600186019251168254916001600160401b0360a01b905160a01b169163ffffffff60e01b16171790555191015560018101610cb98482546125f3565b905580546affffffffffffffff000000610cd7848360181c16612cff565b60181b166affffffffffffffff0000001991909116179055601154604080516001600160a01b0397909716875260208701939093521693a46001600655604051908152f35b610d299192939550612cff565b1680916001600160401b031916176011555f5260188a52600260405f2092600160ff1985541617845590848d610c30565b60ff915060081c168f610c2a565b610d73823383612858565b610be4565b60405162461bcd60e51b815260206004820152600560248201526427aba722a960d91b6044820152606490fd5b60405162461bcd60e51b815260206004820152600860248201526715d7d4105554d15160c21b6044820152606490fd5b34610470575f366003190112610470576020601054604051908152f35b34610470575f366003190112610470576020601554604051908152f35b34610470575f36600319011261047057602060ff600a5416604051908152f35b611fa9565b34610470576020366003190112610470576001600160a01b03610e55611f7d565b165f525f602052602060405f2054604051908152f35b34610470575f366003190112610470576020601254604051908152f35b34610470575f3660031901126104705760206040516127108152f35b34610470575f36600319011261047057602060ff601354166040519015158152f35b5f36600319011261047057610edb3415612bc9565b610ee3612bfd565b6020610afd615bb7565b604036600319011261047057610f01611f93565b610f0b3415612bc9565b610f13612bfd565b6004355f52601760205260405f2060405190610f2e826120d8565b80546001600160a01b039081168084526001830154918216602085015260a09190911c6001600160401b031660408401526002919091015460608301521561136d576001600160401b036040820151165f52601860205260405f2080549260ff8460101c161561133c576001600160a01b038116611337575060208201516001600160a01b03165b60208301516001600160a01b039081169082160361130957610fe36001600160401b038560581c16612cff565b6001600160401b03808660181c169116145f146112eb5761100d6007830154600884015490612d17565b935b67ffffffffffffffff60581b611031605883901c6001600160401b0316612cff565b60581b16906001600160401b0360581b1916178255600882016110558582546125f3565b905561106384600f54612d17565b600f5561107284601054612d17565b6010556004355f908152601760209081526040808320838155600181018490556002018390555163a9059cbb60e01b9181019182526001600160a01b038481166024830152604480830189905282526111459391927f0000000000000000000000000000000000000000000000000000000000000000909116919081906110fa606486612158565b6040519461110786612122565b602086527f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c65646020870152519082855af161113f612d90565b91612dbf565b80519081159182156112c9575b505015611271576001600160401b036040840151166040519185835260018060a01b0316917f6443ab78e20802b0a48c253c5a7e05576a8bedd83aec398a8bba1164e7cf6fc3602060043592a4546001600160401b03808260181c169160581c16146111c9575b6020826001600655604051908152f35b60406001600160401b03910151165f52601860205260405f20905f82555f60018301555f6002830155600a6003925f848201555f60048201555f60058201555f60068201555f60078201555f60088201555f6009820155018054905f815581611235575b5050906111b9565b8184029184830403610804575f5260205f20908101905b8181101561122d578061125f8592612d3a565b5f60018201555f60028201550161124c565b60405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b6064820152608490fd5b81925090602091810103126104705760206112e49101612d83565b8580611152565b61130360608401516007840154600185015491612b28565b9361100f565b60405162461bcd60e51b815260206004820152600660248201526514105653d55560d21b6044820152606490fd5b610fb6565b60405162461bcd60e51b8152602060048201526009602482015268434c41494d41424c4560b81b6044820152606490fd5b60405162461bcd60e51b8152602060048201526007602482015266149154555154d560ca1b6044820152606490fd5b34610470576020366003190112610470576004356001600160401b03808216809203610470576101a0915f52601860205260405f2090815491600181015490600281015460038201546004830154906007840154926009600886015495015495876040519960ff811615158b5260ff8160081c16151560208c015260ff8160101c16151560408c0152818160181c1660608c015260581c1660808a015260a089015260c088015260e08701526101008601526101208501526101408401528060070b61016084015260401c16610180820152f35b34610470575f36600319011261047057602060405166038d7ea4c680008152f35b611f5f565b34610470575f3660031901126104705760206107fc615c6a565b34610470575f366003190112610470576020600c54604051908152f35b34610470575f366003190112610470576020601454604051908152f35b3461047057604036600319011261047057610860611506611f7d565b335f52600160205260405f2060018060a01b0382165f5260205261153060243560405f20546125f3565b903361275a565b346104705760203660031901126104705760043560ff811681036104705730330361157157611567602091612f48565b6040519015158152f35b606460405162461bcd60e51b815260206004820152600460248201526329a2a62360e11b6044820152fd5b34610470575f366003190112610470576040517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b34610470576040366003190112610470576115f9611f93565b336001600160a01b038216036116155761070e9060043561257e565b60405162461bcd60e51b815260206004820152602f60248201527f416363657373436f6e74726f6c3a2063616e206f6e6c792072656e6f756e636560448201526e103937b632b9903337b91039b2b63360891b6064820152608490fd5b34610470576020366003190112610470576004356007548110156104705761169c6116de91611fc3565b50604051906116b6826116af8184612047565b0383612158565b60018101549060ff600360028301549201541690604051948594608086526080860190611f3a565b9260208501526040840152151560608301520390f35b60208060031936011261047057611709611f7d565b611711612bfd565b60ff60135416611895576001600160a01b03168015906117318215612c53565b61174466038d7ea4c68000341015612c8a565b61174c6150a7565b50600254611758612ba9565b81611837575034925b61176c841515612cbc565b61177834600c546125f3565b600c556117f25782611789916125f3565b600255805f525f835260405f20828154019055805f5f80516020615e1483398151915285604051868152a360405134815282848201527fdcbc1c05240f31ff3ad067ef1ee35ce4997762752e3a095284754544f4c709d760403392a36001600655604051908152f35b60405162461bcd60e51b815260048101859052601f60248201527f45524332303a206d696e7420746f20746865207a65726f2061646472657373006044820152606490fd5b801561186457600182019081831161080457600181018091116108045761185e9134612b28565b92611761565b60405162461bcd60e51b8152600481018690526009602482015268125394d3d31591539560ba1b6044820152606490fd5b60405162461bcd60e51b81526004810183905260086024820152671117d4105554d15160c21b6044820152606490fd5b34610470575f366003190112610470576020600e54604051908152f35b6060366003190112610470576004358015158091036104705760248035908115158092036104705760443590811515809203610470576119223415612bc9565b335f9081527f99f2891db7a8db76249871a02b43b68c0bb8f22ef8a3bc1034ae9375ef6b7c3c60209081526040909120547f65d7a28e3265b37a6474929f336521b332c1681b933f6cb9f3376673440d862a9060ff16156119aa5750505061ff009060ff62ff00006013549260101b1694169062ffffff1916179160081b1617176013555f80f35b6119b333612944565b906040516119c0816120d8565b60428152838101916060368437815115611b4557603083538151600190811015611b3257607860218401536041905b808211611ac9575050611a9a576048611a9693611a6b93611a7a9360405195869376020b1b1b2b9b9a1b7b73a3937b61d1030b1b1b7bab73a1604d1b8a860152611a42815180928c603789019101611f19565b8401917001034b99036b4b9b9b4b733903937b6329607d1b603784015251809386840190611f19565b01036028810184520182612158565b60405193849362461bcd60e51b85526004850152830190611f3a565b0390fd5b606485856040519162461bcd60e51b83528160048401528201525f80516020615dd48339815191526044820152fd5b9091600f81166010811015611b1f576f181899199a1a9b1b9c1cb0b131b232b360811b901a611af88486612933565b5360041c918015611b0c575f1901906119ef565b87634e487b7160e01b5f5260116004525ffd5b88634e487b7160e01b5f5260326004525ffd5b86634e487b7160e01b5f5260326004525ffd5b85634e487b7160e01b5f5260326004525ffd5b602036600319011261047057611b6e3415612bc9565b611b76612bfd565b6020610afd600435612e58565b34610470575f3660031901126104705760ff7f00000000000000000000000000000000000000000000000000000000000000001660ff811161080457602090604051908152f35b3461047057604036600319011261047057600435611be6611f93565b815f526005602052611bfe600160405f200154612475565b815f52600560205260405f209060018060a01b031690815f5260205260ff60405f20541615611c2957005b815f52600560205260405f20815f5260205260405f20600160ff1982541617905533917f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d5f80a4005b34610470575f3660031901126104705760206107fc613996565b34610470575f36600319011261047057602060ff60135460101c166040519015158152f35b34610470576020366003190112610470576004355f5260056020526020600160405f200154604051908152f35b3461047057606036600319011261047057610860611cfa611f7d565b611d02611f93565b60443591611d11833383612858565b612600565b34610470575f366003190112610470576020600254604051908152f35b34610470575f366003190112610470576020600754604051908152f35b34610470575f3660031901126104705760206001600160401b0360115416604051908152f35b34610470576020366003190112610470576002546001810180911161080457611d9d612ba9565b9060018201809211610804576020916107fc91600435612a3c565b3461047057604036600319011261047057610860611dd4611f7d565b602435903361275a565b34610470575f366003190112610470576040515f90600354611dff8161200f565b8083526001918083169081156109ff5750600114611e27576109a28361098e81870382612158565b60035f90815260209450917fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b5b828410611e6d5750505081019091019061098e8161097e565b8054858501870152928501928101611e54565b34610470575f3660031901126104705760206001600160401b0360115460401c16604051908152f35b346104705760203660031901126104705760043563ffffffff60e01b811680910361047057602090637965db0b60e01b8114908115611eee575b506040519015158152f35b6301ffc9a760e01b14905082611ee3565b34610470575f3660031901126104705760206107fc612ba9565b5f5b838110611f2a5750505f910152565b8181015183820152602001611f1b565b90602091611f5381518092818552858086019101611f19565b601f01601f1916010190565b346104705760203660031901126104705760206107fc6004356128ea565b600435906001600160a01b038216820361047057565b602435906001600160a01b038216820361047057565b34610470575f366003190112610470576020604051818152f35b600754811015611fdf5760075f5260205f209060021b01905f90565b634e487b7160e01b5f52603260045260245ffd5b600854811015611fdf5760085f5260205f209060021b01905f90565b90600182811c9216801561203d575b602083101461202957565b634e487b7160e01b5f52602260045260245ffd5b91607f169161201e565b905f92918054916120578361200f565b9182825260019384811690815f146120b55750600114612078575b50505050565b90919394505f52602092835f2092845f945b8386106120a157505050500101905f808080612072565b80548587018301529401938590820161208a565b9294505050602093945060ff191683830152151560051b0101905f808080612072565b608081019081106001600160401b038211176120f357604052565b634e487b7160e01b5f52604160045260245ffd5b60a081019081106001600160401b038211176120f357604052565b604081019081106001600160401b038211176120f357604052565b60c081019081106001600160401b038211176120f357604052565b90601f801991011681019081106001600160401b038211176120f357604052565b3461047057602036600319011261047057612192611f7d565b5060206040515f8152f35b34610470576040366003190112610470576121b6611f93565b5060405162461bcd60e51b815260206004820152600760248201526611115413d4d25560ca1b6044820152606490fd5b606090600319011261047057600435906001600160a01b03906024358281168103610470579160443590811681036104705790565b3461047057612229366121e6565b505060405162461bcd60e51b815260206004820152600660248201526552454445454d60d01b604482015260649150fd5b34610470576020366003190112610470576002546001810180911161080457612281612ba9565b9060018201809211610804576020916107fc91600435612b28565b335f9081527fe790de7705c8ebaa80068cd4fc0a095afd63ddb3e1cbffe9ca6f4baedbd7b739602090815260408083205490927f97667070c54ef182b0f5858b034beac1b6f3089aa2d3188bb1e8929f4fa9b9299160ff16156122ff5750505050565b61230833612944565b91845190612315826120d8565b6042825284820192606036853782511561246157603084538251906001918210156124615790607860218501536041915b8183116123f4575050506123c5576048611a969386936123a99361239a985198899376020b1b1b2b9b9a1b7b73a3937b61d1030b1b1b7bab73a1604d1b8a860152611a42815180928c603789019101611f19565b01036028810187520185612158565b5192839262461bcd60e51b845260048401526024830190611f3a565b60648486519062461bcd60e51b825280600483015260248201525f80516020615dd48339815191526044820152fd5b909192600f8116601081101561244d576f181899199a1a9b1b9c1cb0b131b232b360811b901a6124248587612933565b5360041c928015612439575f19019190612346565b634e487b7160e01b82526011600452602482fd5b634e487b7160e01b83526032600452602483fd5b634e487b7160e01b81526032600452602490fd5b5f9080825260209060058252604092838120338252835260ff84822054161561249e5750505050565b6124a733612944565b918451906124b4826120d8565b6042825284820192606036853782511561246157603084538251906001918210156124615790607860218501536041915b818311612539575050506123c5576048611a969386936123a99361239a985198899376020b1b1b2b9b9a1b7b73a3937b61d1030b1b1b7bab73a1604d1b8a860152611a42815180928c603789019101611f19565b909192600f8116601081101561244d576f181899199a1a9b1b9c1cb0b131b232b360811b901a6125698587612933565b5360041c928015612439575f190191906124e5565b905f918083526005602052604083209160018060a01b03169182845260205260ff6040842054166125ae57505050565b8083526005602052604083208284526020526040832060ff1981541690557ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b339380a4565b9190820180921161080457565b6001600160a01b0390811691821561270757169182156126b6575f8281528060205260408120549180831061266257604082825f80516020615e14833981519152958760209652828652038282205586815220818154019055604051908152a3565b60405162461bcd60e51b815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e7420657863656564732062604482015265616c616e636560d01b6064820152608490fd5b60405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b6064820152608490fd5b60405162461bcd60e51b815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604482015264647265737360d81b6064820152608490fd5b6001600160a01b0390811691821561280757169182156127b75760207f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591835f526001825260405f20855f5282528060405f2055604051908152a3565b60405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b6064820152608490fd5b60405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608490fd5b9060018060a01b038083165f52600160205260405f209082165f5260205260405f2054925f19840361288a5750505050565b8084106128a55761289c93039161275a565b5f808080612072565b60405162461bcd60e51b815260206004820152601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e63650000006044820152606490fd5b6128f2612ba9565b600181018091116108045760025490600182018092116108045761291592612b28565b90565b6001600160401b0381116120f357601f01601f191660200190565b908151811015611fdf570160200190565b60405190606082018281106001600160401b038211176120f357604052602a8252602082016040368237825115611fdf57603090538151600190811015611fdf57607860218401536029905b8082116129d15750506129a05790565b606460405162461bcd60e51b815260206004820152602060248201525f80516020615dd48339815191526044820152fd5b9091600f81166010811015612a28576f181899199a1a9b1b9c1cb0b131b232b360811b901a612a008486612933565b5360041c918015612a14575f190190612990565b60245f634e487b7160e01b81526011600452fd5b60245f634e487b7160e01b81526032600452fd5b9190612a49828285612b28565b928215612a665709612a585790565b600181018091116108045790565b634e487b7160e01b5f52601260045260245ffd5b8115612a66570490565b905f1981830981830291828083109203918083039214612b1d576127109082821115612ae0577fbc01a36e2eb1c432ca57a786c226809d495182a9930be0ded288ce703afb7e91940990828211900360fc1b910360041c170290565b60405162461bcd60e51b81526020600482015260156024820152744d6174683a206d756c446976206f766572666c6f7760581b6044820152606490fd5b505061271091500490565b915f198284099282810292838086109503948086039514612b9c5784831115612ae05782910960018219018216809204600280826003021880830282030280830282030280830282030280830282030280830282030280920290030293600183805f03040190848311900302920304170290565b5050906129159250612a7a565b60025415612bc557612915600c54612bbf615c6a565b906125f3565b5f90565b15612bd057565b60405162461bcd60e51b815260206004820152600560248201526456414c554560d81b6044820152606490fd5b600260065414612c0e576002600655565b60405162461bcd60e51b815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c006044820152606490fd5b15612c5a57565b60405162461bcd60e51b81526020600482015260086024820152672922a1a2a4ab22a960c11b6044820152606490fd5b15612c9157565b60405162461bcd60e51b815260206004820152600360248201526226a4a760e91b6044820152606490fd5b15612cc357565b60405162461bcd60e51b815260206004820152600660248201526553484152455360d01b6044820152606490fd5b5f1981146108045760010190565b9060016001600160401b038093160191821161080457565b9190820391821161080457565b818110612d2f575050565b5f8155600101612d24565b612d44815461200f565b9081612d4e575050565b81601f5f9311600114612d605750555b565b908083918252612d7f601f60208420940160051c840160018501612d24565b5555565b5190811515820361047057565b3d15612dba573d90612da182612918565b91612daf6040519384612158565b82523d5f602084013e565b606090565b91929015612e215750815115612dd3575090565b3b15612ddc5790565b60405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606490fd5b825190915015612e345750805190602001fd5b60405162461bcd60e51b815260206004820152908190611a96906024830190611f3a565b5f9060ff60135460101c16612ef4578015612eee57612ea7612ea1612e9b612e95612e8f612e89612ead97876139e2565b86613aa3565b85613b2b565b84613bb3565b83613c3a565b82613cc2565b90613d4a565b90612eb9601454612cf1565b601455816015556040518281527f4889d7d38c1d1f265318505a98367d07d41591d6b7f9cc000f37ea92b8311daa60203392a2565b50505f90565b60405162461bcd60e51b81526020600482015260086024820152671417d4105554d15160c21b6044820152606490fd5b5f908015612eee57612f42612f3c61291593836139e2565b82613aa3565b90613b2b565b60ff16600181146138f957600281146138f057600381146138e757600481146138de5760058114612f975760068114612f8e57600714612f86575f90565b612915614e3f565b50612915614b40565b50604051612fa481612107565b604051600954815f612fb58361200f565b80835292600181169081156138bf5750600114613880575b612fd992500382612158565b81525f6020820152602060408201525f60608201525f60808201525f60405180809363186b216760e01b82526040600483015260126044830152711093d39117d4d510551554d7d093d391115160721b606483015260806024830152608061304e825160a06084860152610124850190611f3a565b916001600160401b0360208201511660a48501526001600160401b0360408201511660c48501526060810151151560e48501520151151561010483015203816108005afa918215613875575f91829361349e575b505f5b825181101561312c576130b88184614b2c565b51906040820151158015906130fe575b6130e7929080156130f2575b6130ec576130e29051615345565b612cf1565b6130a5565b50612cf1565b506080810151156130d4565b5060608201519160048310156131185791600314156130c8565b634e487b7160e01b5f52602160045260245ffd5b50909180519283516001600160401b0381116120f35761314d60095461200f565b601f8111613445575b50602094601f82116001146133e1579481929394955f926133d6575b50508160011b915f199060031b1c1916176009555b815151156131a6575b51159081159161319e575090565b905051511590565b91906131b0615bb7565b505f5b6007548110156131ef578060036131cc6131ea93611fc3565b5001805460ff191690555f60016131e283611fc3565b500155612cf1565b6131b3565b50905f5b6008548110156132ba5761322861321c61322361320f84611ff3565b5060405192838092612047565b0382612158565b615d8d565b9015613268579081600361323e61326394611fc3565b5001600160ff1982541617905560016131e28161325a85611ff3565b50015492611fc3565b6131f3565b50600790815460208110613282575b506132639150612cf1565b61328b82611ff3565b50600160401b8210156120f3576132ae826132639560016132b495019055611fc3565b906155b7565b5f613277565b5090915f915b60078054808510156133925760ff60036132d987611fc3565b50015416158061337d575b1561336b575f19908082019081116108045761330261330c91611fc3565b506132ae87611fc3565b8154801561335757019061331f82611fc3565b613344576003816133305f93612d3a565b8260018201558260028201550155556132c0565b634e487b7160e01b5f525f60045260245ffd5b634e487b7160e01b5f52603160045260245ffd5b50509161337790612cf1565b916132c0565b50600261338986611fc3565b500154156132e4565b505092915061339f613902565b7f17e14f5a0c6831022cc1f0f72d2d717ac74633f5193b19f76e18d9711b6d7cec60206133ca613996565b604051908152a1613190565b015190505f80613172565b601f1982169560095f5260205f20915f5b88811061342d57508360019596979810613415575b505050811b01600955613187565b01515f1960f88460031b161c191690555f8080613407565b919260206001819286850151815501940192016133f2565b60095f5261348e907f6e1540171b6c0c960b71a7020d9f60077f6af931a8bbf590da0223dacf75c7af601f840160051c81019160208510613494575b601f0160051c0190612d24565b5f613156565b9091508190613481565b919092503d8082843e6134b18184612158565b60408382810103126135b4578251916001600160401b0383116135ad57818401601f8486010112156135ad57828401516134ea81614ab2565b936134f86040519586612158565b818552602085019084870160208460051b838a010101116135b057602081880101915b60208460051b838a01010183106135b8575050505060208401516001600160401b0381116135b45784019160408382870103126135b4576040519461355f86612122565b83516001600160401b0381116135b057840192828201601f850112156135ad57506020926135a094926135989201908481519101614ac9565b855201614b18565b602083015290915f6130a2565b80fd5b8380fd5b5080fd5b82516001600160401b038111613871576101608984018201888b0103601f1901126138715760405190816101608101106001600160401b036101608401111761385d576101608201604052602081858c010101516001600160401b038111613859578a60206136309284888d85019401010101614afe565b8252604081858c010101516001600160401b038111613859578a602061365f9284888d85019401010101614afe565b6020830152613674606082868d010101612d83565b6040830152608081858c010101516004811015613859576060830152898401810160a081810151608085015260c08201519084015260e001516001600160401b0381116138595760a0858c01830182018a8d0103601f190112613859576040519060208184888f6136e487612107565b01010101516001600160401b038111613855578184888f6137168f956020604097840191878787870101010101614afe565b875201010101516001600160401b038111613855578184888f61374a8f956020606097840191878787870101010101614afe565b602088015201010101516001600160401b038111613855578184888f6137818f956020608097840191878787870101010101614afe565b604088015201010101516001600160401b038111613855578184888f6137b88f95602060a097840191878787870101010101614afe565b60608801520101010151906001600160401b0382116138555761016084602097948f938a8f8b9a976137f6938c9b8c938a01948a0101010101614afe565b608082015260c0850152886101009361381485848484010101614475565b60e08701526101209461382c86858585010101614475565b90870152610140948584848401010151908701520101015190820152815201930192905061351b565b8980fd5b8780fd5b634e487b7160e01b87526041600452602487fd5b8580fd5b6040513d5f823e3d90fd5b505060095f52816020805f20925f905b8082106138a55750612fd99350820101612fcd565b845482870184015260019094019385935090820190613890565b60209250612fd994915060ff191682840152151560051b820101612fcd565b506129156150a7565b506129156149d5565b50612915613dd2565b506129156140a8565b6008545f8060085581613913575050565b6001600160fe1b038216820361398257600881527ff3f7a9fe364faab93b216da50a3214154f22a0a2b415b23a84c8169e8b636ee3600292831b810192905b83811061395f5750505050565b8061396b600492612d3a565b836001820155838382015583600382015501613952565b634e487b7160e01b81526011600452602490fd5b5f905f600754905b8181106139a9575050565b60ff60036139b683611fc3565b500154166139cd575b6139c890612cf1565b61399e565b926139da6139c891612cf1565b9390506139bf565b81101561291557604051630e47b40f60e21b8152600160048201526020905f90828160248185305af1829181613a68575b50613a4e5750505f80516020615df4833981519152600191613a33612d90565b90613a48604051928284938452830190611f3a565b0390a290565b909150613a59575090565b60018201809211613982575090565b9091508381813d8311613a9c575b613a808183612158565b81010312613a9857613a9190612d83565b905f613a13565b8280fd5b503d613a76565b81101561291557604051630e47b40f60e21b8152600260048201526020905f90828160248185305af1829181613af4575b50613a4e5750505f80516020615df4833981519152600291613a33612d90565b9091508381813d8311613b24575b613b0c8183612158565b81010312613a9857613b1d90612d83565b905f613ad4565b503d613b02565b81101561291557604051630e47b40f60e21b8152600360048201526020905f90828160248185305af1829181613b7c575b50613a4e5750505f80516020615df4833981519152600391613a33612d90565b9091508381813d8311613bac575b613b948183612158565b81010312613a9857613ba590612d83565b905f613b5c565b503d613b8a565b81101561291557604051630e47b40f60e21b81526004808201526020905f90828160248185305af1829181613c03575b50613a4e5750505f80516020615df4833981519152600491613a33612d90565b9091508381813d8311613c33575b613c1b8183612158565b81010312613a9857613c2c90612d83565b905f613be3565b503d613c11565b81101561291557604051630e47b40f60e21b8152600560048201526020905f90828160248185305af1829181613c8b575b50613a4e5750505f80516020615df4833981519152600591613a33612d90565b9091508381813d8311613cbb575b613ca38183612158565b81010312613a9857613cb490612d83565b905f613c6b565b503d613c99565b81101561291557604051630e47b40f60e21b8152600660048201526020905f90828160248185305af1829181613d13575b50613a4e5750505f80516020615df4833981519152600691613a33612d90565b9091508381813d8311613d43575b613d2b8183612158565b81010312613a9857613d3c90612d83565b905f613cf3565b503d613d21565b81101561291557604051630e47b40f60e21b8152600760048201526020905f90828160248185305af1829181613d9b575b50613a4e5750505f80516020615df4833981519152600791613a33612d90565b9091508381813d8311613dcb575b613db38183612158565b81010312613a9857613dc490612d83565b905f613d7b565b503d613da9565b6001600160401b039081601154165f90815260209060188252604091828220805460ff811615908115614082575b508015614076575b61406e57613e146150a7565b5060018101805496613e25886128ea565b80156140635761010061ffff1985541617845580600285015530156140155730865285855286862054898110613fc65795898198999a601898308452838952038a832055806002540360025589519081525f80516020615e14833981519152873092a3613ea8600c548281105f14613fbf57805b8180613f88575b505082612d17565b92879380613f6f575b5090613f279160098601956001600160401b0319968781541687871617905560048101541580613f63575b613f44575b507f5d78323b7b570071308d257743c74a9a2401f77d37e9de9f908da12c8917a40b60608b601154978789169586955492519283528b83015260070b8d820152a2612cff565b16918291161760115583525220600160ff19825416179055600190565b6201000062ff000019825416178155600760038201549101555f613ee1565b50600681015415613edc565b613f2792919450613f8090866144fe565b939091613eb1565b613f9191612d17565b600c55613fa081600f546125f3565b600f55613faf816010546125f3565b6010558060038701555f81613ea0565b8290613e99565b875162461bcd60e51b815260048101879052602260248201527f45524332303a206275726e20616d6f756e7420657863656564732062616c616e604482015261636560f01b6064820152608490fd5b865162461bcd60e51b815260048101869052602160248201527f45524332303a206275726e2066726f6d20746865207a65726f206164647265736044820152607360f81b6064820152608490fd5b509396505050505050565b509093505050565b50600181015415613e08565b60ff915060081c165f613e00565b6001600160401b038091169081146108045760010190565b6001600160401b0360119182549282604094851c169260019484955b828454169483881695861015614444575f958087526020601881528488209687549460ff9586808260081c1615918215614437575b50506144085750889960068901546143765760098901805489811642106143f45789818a1c1615614388575054871c88164311156143765760019b8a600a8b019c5b8d54821015614272578a8e9f8f908b8f6141578f9488906144ad565b509360028501908154938460c01c1661426157614173866159d1565b9790156141e6575050505050506001019081548082106141a3575b50505061419b8d92612cf1565b919e5061413b565b816141ad91612d17565b915560048d016141be828254612d17565b9055600e6141cd828254612d17565b90556141dc600f918254612d17565b90555f808061418e565b9091938096939599975042921c1611155f14614251576001015460ff60c01b19909316600160c01b17905560048101805461419b949392600592909161422d908590612d17565b90550161423b8282546125f3565b905561424a600e918254612d17565b9055612cf1565b50505050915061419b8d92612cf1565b5050505093905061419b9150612cf1565b959b969c50509195509a97959691971561435c57505050966142f17f73f330dd18a8f4760be8fa04b392ba243a76dcbe07487724e846182d63dbfafa96979860058501908154916142c1615ce8565b808410156143565750825b83818110614337575b505060106142e48482546125f3565b90555560038501546125f3565b60078401908155835462ff000019166201000017909355815467ffffffffffffffff60401b191690841b67ffffffffffffffff60401b16179055549051908152a2600190565b61434091612d17565b61434d600f918254612d17565b90555f836142d5565b926142cc565b925094935096955061436f919450614090565b95946140c4565b9a9398509650505061436f9150614090565b99509293955095985098507f5e49965d9b92858198b2c5bab7c3a3fa70fcaf004a265239d97b798b708646ce9299506001600160401b0360401b956001600160401b0360401b19978888438c1b16911617809255885191891c168152a28454931b169116179055600190565b50509a9398509650505061436f9150614090565b999a945096505050945085614421575b61436f90614090565b955061436f61442f87612cff565b969050614418565b60101c169050865f6140f9565b845467ffffffffffffffff60401b19169690921b67ffffffffffffffff60401b1695909517909255505f9350915050565b51908160070b820361047057565b9392916144a89060409260018060a01b03168652606060208701526060860190612047565b930152565b8054821015611fdf575f52600360205f20910201905f90565b9190601f81116144d557505050565b612d5e925f5260205f20906020601f840160051c8301931061349457601f0160051c0190612d24565b91905f90600754156149a35760165443146149915761451b615bb7565b505f925f5b600754811080614988575b1561495a5761453981611fc3565b5060028101805496871561494d575060019660076145666040516145618161321c8189612047565b615afb565b1015614941578581101561493957915b604051630fb6accf60e21b8152916020838061459787863060048501614483565b03815f6108005af15f93816148fd575b506145be57505050506145b990612cf1565b614520565b61321c91816145d586614621949a97989a54612d17565b9055600d6145e4878254612d17565b9055600e6145f38782546125f3565b9055600f6146028782546125f3565b905560048b016146138782546125f3565b905560405192838092612047565b5f60a06040516146308161213d565b60608152826020820152826040820152826060820152826080820152015261469e5f6040519261465f8461213d565b8084528660208501528160408501528460070b60608501528160808501528160a08501526040518093819263a03ffee160e01b8352306004840161598d565b03816108005afa5f91816148d9575b50614838575b50600a890154600160401b8110156120f35780896146da9201600a8c0155600a8b016144ad565b6133445781518051906001600160401b0382116120f357614705826146ff855461200f565b856144c6565b602090601f83116001146147d25760029392915f91836147c7575b50505f19600383901b1c1916908b1b1781555b60208301518a82015501906040810151908254606082015160401b906001600160401b038060801b608085015160801b169360a060ff60c01b910151151560c01b1694169066ffffffffffffff60c81b1617906001600160401b0360401b1617171790558560070b8160070b136147bd575b506145b9916147b391612d17565b9243601655612cf1565b94506145b96147a5565b015190505f80614720565b92918b91835f5260205f20905f5b601f1984168110614820575060029583601f19811610614808575b505050811b018155614733565b01515f1960f88460031b161c191690555f80806147fb565b8187015183558e9490920191602091820191016147e0565b885f815b614848575b50506146b3565b60408301519081518110156148d3576148648161488293614b2c565b518660070b602082015160070b14806148b5575b6148885750612cf1565b8161483c565b60808160606001600160401b039301516020890152805160070b604089015201511660808601525f6130ec565b506001600160401b0380608083015116906080880151161115614878565b50614841565b6148f69192503d805f833e6148ee8183612158565b81019061584a565b905f6146ad565b9093506020813d602011614931575b8161491960209383612158565b810103126104705761492a90614475565b925f6145a7565b3d915061490c565b508491614576565b5050506145b990612cf1565b965050506145b990612cf1565b50929391938115159081614980575b501561497757600690910155565b5060065f910155565b90505f614969565b5082151561452b565b60068401555060099091015460070b90565b60405162461bcd60e51b815260206004820152600a60248201526956414c494441544f525360b01b6044820152606490fd5b6016544314612bc5576001600160401b0360119081549181604093841c16925b8282541683851690811015614aa95781905f90815260186020522093845460ff808260081c1615918215614a9c575b50508015614a90575b614a79575050506009614a446006840154846144fe565b92019182549160078284820b910b13614a61575b50505050600190565b16906001600160401b0319161790555f808080614a58565b8391929450614a8790614090565b939190506149f5565b50600685015415614a2d565b60101c1690505f80614a24565b50505050505f90565b6001600160401b0381116120f35760051b60200190565b90929192614ad681612918565b91614ae46040519384612158565b829482845282820111610470576020612d5e930190611f19565b9080601f8301121561047057815161291592602001614ac9565b51906001600160401b038216820361047057565b8051821015611fdf5760209160051b010190565b60028060075410614e3a57614b53615bb7565b506007545f80805b838110614de357508115908115614dd7575b50614dd05790614b7c82615604565b915f94919490819482938390600d54915b838110614d1c575050505015908115614d13575b508015614d0a575b614aa95780821015614d025750915b66038d7ea4c680008310614cfa57614bd2614bd991611fc3565b5091611fc3565b50916040516354b826f560e01b81523060048201526080602482015260208180614c1b614c096084830188612047565b82810360031901604484015288612047565b85606483015203815f6108005af15f9181614cbf575b50614c3f5750505050505f90565b614ca9614c9b94867f2a3f2dce4ae3c46ff8fd3dbcf9cac1ca39da18b59d0ac57b2047d061ec99c1cb978601614c76868254612d17565b90558101614c858582546125f3565b9055604051958695608087526080870190612047565b908582036020870152612047565b91604084015260070b60608301520390a1600190565b90916020823d8211614cf2575b81614cd960209383612158565b810103126135ad5750614ceb90614475565b905f614c31565b3d9150614ccc565b505050505f90565b905091614bb8565b50828414614ba9565b9050155f614ba1565b60ff6003614d2983611fc3565b5001541615614dc757614d3b81611fc3565b50614d4c8360018093015486612b28565b908c8981614d5986611fc3565b50015484119182614da7575b5050614d7c575b5050614d7790612cf1565b614b8d565b9296509850955093614d77614d9f87998c614d968a611fc3565b50015490612d17565b96905f614d6c565b614dbf919250614db686611fc3565b50015484612d17565b11898e614d65565b614d7790612cf1565b5050505f90565b9050600d54105f614b6d565b60ff6003614df083611fc3565b50015416614e07575b614e0290612cf1565b614b5b565b9091614e32614e27614e02926001614e1e86611fc3565b500154906125f3565b9386614e1e85611fc3565b919050614df9565b505f90565b600254158015615095575b612bc557614e56615bb7565b50600d54600c5490614e6882826125f3565b9082600b5480615043575b50505f908180600754905b818110614ffa5750508015614aa957614e9a85614ea0936125f3565b90615746565b90949115614aa95780614ff55750805b80821015614fed5750925b8315614cfa57614eca90611fc3565b50916127108281018091116108045761270f830190811161080457614f0492614ef291612a7a565b610ce480821115614fe5575090612a84565b916002820192835481811015614aa957614f1d91612d17565b80821015614fdd5750905b8115614dd057602082614f519260405193849283926353266bbb60e01b84523060048501614483565b03815f6108005af15f9181614fa2575b50614f6d575050505f90565b15612eee57614f9a91614f8282600c54612d17565b600c55614f908282546125f3565b9055600d546125f3565b600d55600190565b90916020823d8211614fd5575b81614fbc60209383612158565b810103126135ad5750614fce90612d83565b905f614f61565b3d9150614faf565b905090614f28565b905090612a84565b905092614ebb565b614eb0565b60ff600361500783611fc3565b5001541661501e575b61501990612cf1565b614e7e565b939161503b615035615019926001614e1e89611fc3565b93612cf1565b949050615010565b909193506127106150548285612a84565b918409615075575b80821115614cfa5761506d91612d17565b915f80614e73565b600181018091111561505c57634e487b7160e01b5f52601160045260245ffd5b5066038d7ea4c68000600c5410614e4a565b600754158015615239575b612bc5576040516370a0823160e01b8082523060048301527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906020908184602481865afa938415613875575f9461520a575b5063ffffffff6007541660405190632efe8a5f60e01b8252306004830152602482015282816044815f6108015af15f91816151d3575b506151525750505050505f90565b15614cfa57819060246040518095819382523060048301525afa908115613875575f916151a7575b50905081811161518a5750505f90565b61519f9161519791612d17565b600c546125f3565b600c55600190565b82813d83116151cc575b6151bb8183612158565b810103126135ad575051805f61517a565b503d6151b1565b90918482813d8311615203575b6151ea8183612158565b810103126135ad57506151fc90612d83565b905f615144565b503d6151e0565b90938282813d8311615232575b6152218183612158565b810103126135ad575051925f61510e565b503d615217565b50615242615bb7565b156150b2565b909291926133445782519283516001600160401b0381116120f357615271816146ff855461200f565b6020601f82116001146152db579181606092600394612d5e97985f926152d0575b50508160011b915f1990861b1c19161784555b60208101516001850155604081015160028501550151151591019060ff801983541691151516179055565b015190505f80615292565b601f19821695845f52815f20965f5b81811061532d575092612d5e9697600395936001938360609710615316575b505050811b0184556152a5565b01515f1983881b60f8161c191690555f8080615309565b838301518955600190980197602093840193016152ea565b5f5b6008548110156153bf576153778261321c61536461320f85611ff3565b6020815191012090602081519101201490565b6153895761538490612cf1565b615347565b905061539481611ff3565b5060018091015491670de0b6b3a76400009283116153b157505050565b6153ba90611ff3565b500155565b5060085460ff600a5416811061549357505f90600854801561244d57600883527ff3f7a9fe364faab93b216da50a3214154f22a0a2b415b23a84c8169e8b636ee4546001939091845b818110615459575050670de0b6b3a76400009182116154275750505050565b61289c93615453926040519461543c866120d8565b855260208501525f60408501526060840152611ff3565b90615248565b838661546483611ff3565b5001541061547b575b61547690612cf1565b615408565b915091508361548982611ff3565b500154918161546d565b90604051906154a1826120d8565b8152670de0b6b3a764000060208201525f604082015260016060820152600160401b8210156120f357615453826001612d5e9401600855611ff3565b908082146155b3576154ef815461200f565b906001600160401b0382116120f35781906155148261550e865461200f565b866144c6565b5f90601f8311600114615548575f9261553d575b50508160011b915f199060031b1c1916179055565b015490505f80615528565b81526020808220858352818320935090601f1985169083905b82821061559a575050908460019594939210615582575b505050811b019055565b01545f1960f88460031b161c191690555f8080615578565b8495819295850154815560018091019601940190615561565b5050565b90613344578181036155c7575050565b600360ff81846155da612d5e96866154dd565b600181015460018601556002810154600286015501541691019060ff801983541691151516179055565b600754915f5b8381106156e857505f809381938290600d54915b83811061562e5750505050929190565b60ff600361563b83611fc3565b50015416156156df5761564d81611fc3565b5061565e8360018093015486612b28565b61566783611fc3565b509160028093015466038d7ea4c680008301908184116108045711806156c9575b61569e575b50505061569990612cf1565b61561e565b929950899850919550615699916156c0916156b88a611fc3565b500154612d17565b96905f8061568d565b50896156d983856156b888611fc3565b11615688565b61569990612cf1565b60ff60036156f883969596611fc3565b500154161580615730575b6157185761571090612cf1565b92919261560a565b92509050600261572783611fc3565b50015460019291565b50600261573c82611fc3565b5001541515615703565b90915f809381938291600754925b8381106157aa57505050811561576b575b50929190565b5f5b81811061577a5750615765565b60ff600361578783611fc3565b5001541661579d5761579890612cf1565b61576d565b6001955093505f92915050565b60ff60036157b783611fc3565b5001541615615841576157c981611fc3565b506157da8460018093015485612b28565b6157e383611fc3565b509160028093015482118061582c575b615809575b50505061580490612cf1565b615754565b9299508998509195506158049161582391614d968a611fc3565b96905f806157f8565b508961583b84614db687611fc3565b116157f3565b61580490612cf1565b60209081818403126104705780516001600160401b0391828211610470570192606092838583031261047057604092835195858701878110838211176120f3578552805182811161047057846158a1918301614afe565b87528281015182811161047057846158ba918301614afe565b838801528481015191821161047057019082601f83011215610470578151926158e284614ab2565b956158ef86519788612158565b848752828701918360c080970286010194818611610470578401925b85841061591f575050505050505082015290565b86848303126104705784879189516159368161213d565b61593f87614475565b815261594c838801614475565b838201528a8701518b8201528587015186820152608061596d818901614b18565b9082015260a061597e818901614475565b9082015281520193019261590b565b6001600160a01b03909116815260406020820181905261291592910190611f3a565b6001600160a01b03909116815260406020820181905261291592910190612047565b6040805163a03ffee160e01b81525f939290918483806159f58530600484016159af565b03816108005afa859381615adf575b50615a11575b5050508190565b90919293805b828501518051821015615ad05781615a2e91614b2c565b5160028501546001600160401b036080918181841c1680159283159485615ac1575b50505081615aae575b81615a97575b508115615a8f575b50615a7e5750615a778391612cf1565b9050615a17565b606001516001969095509350505050565b90505f615a67565b90506020830151600791871c820b910b145f615a5f565b9050825160079082820b910b1490615a59565b860151161492505f8080615a50565b505093925050505f8080615a0a565b615af49194503d8088833e6148ee8183612158565b925f615a04565b5f615b1b916040518093819263a03ffee160e01b8352306004840161598d565b03816108005afa5f9181615b3c575b50615b3457505f90565b604001515190565b615b5091923d8091833e6148ee8183612158565b905f615b2a565b9190916040818403126104705780519260208201516001600160401b039283821161047057016040818303126104705760405192615b9484612122565b815190811161047057602092615bab918301614afe565b83520151602082015290565b5f905f805b600754811015615c6257615bef82615bd383611fc3565b506040518093819263120bba7360e11b835230600484016159af565b03816108005afa839181615c3c575b50615c195750808260026131e2615c1494611fc3565b615bbc565b615c1491946020615035920180516002615c3289611fc3565b50015551906125f3565b615c599192503d8086833e615c518183612158565b810190615b57565b9050905f615bfe565b505081600d55565b5f905f80600754915b828110615c7f57505050565b615c8c82615bd383611fc3565b03816108005afa839181615cca575b50615cb0575b50615cab90612cf1565b615c73565b615cab91956020615cc3920151906125f3565b9490615ca1565b615cdf9192503d8086833e615c518183612158565b9050905f615c9b565b6040516370a0823160e01b81523060048201526020816024817f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165afa908115613875575f91615d5c575b50615d4b600c54601054906125f3565b80821115612eee5761291591612d17565b906020823d8211615d85575b81615d7560209383612158565b810103126135ad5750515f615d3b565b3d9150615d68565b60075491905f5b838110615da557505090505f905f90565b615db88261321c61536461320f85611fc3565b615dca57615dc590612cf1565b615d94565b9250506001919056fe537472696e67733a20686578206c656e67746820696e73756666696369656e74f1190c18d28d5d68156de3ae0f68fd157173bc96def1c9c2fae201841639d0adddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa26469706673582212205ab468d1ee0dd993e1ea54c589722f70487615c71efb3d5b9957a7c28c2b000864736f6c63430008140033", "linkReferences": {}, "deployedLinkReferences": {}, "immutableReferences": { - "1303": [ + "5373": [ { "length": 32, - "start": 4102 + "start": 4297 }, { "length": 32, - "start": 5346 + "start": 5553 }, { "length": 32, - "start": 19346 + "start": 20684 }, { "length": 32, - "start": 22489 + "start": 23811 } ], - "1305": [ + "5375": [ { "length": 32, - "start": 6811 + "start": 7063 } ] }, "inputSourceName": "project/solidity/StakedBondVault.sol", - "buildInfoId": "solc-0_8_20-71039e9761b7c4a1eae03c7cf3a8dfe85787de84" + "buildInfoId": "solc-0_8_20-9b3fc071809e2f176717002a42fe371adef5278c" } \ No newline at end of file diff --git a/contracts/solidity/StakedBondVault.sol b/contracts/solidity/StakedBondVault.sol index cb4d8c03..3d08818b 100644 --- a/contracts/solidity/StakedBondVault.sol +++ b/contracts/solidity/StakedBondVault.sol @@ -9,6 +9,7 @@ import "@openzeppelin/contracts/utils/math/Math.sol"; import "./precompiles/distribution/DistributionI.sol"; import "./precompiles/staking/StakingI.sol"; +import "./precompiles/gov/IGov.sol"; contract StakedBondVault is ERC4626, AccessControl, ReentrancyGuard { using Math for uint256; @@ -17,20 +18,39 @@ contract StakedBondVault is ERC4626, AccessControl, ReentrancyGuard { bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE"); bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE"); - address public constant NATIVE_WERC20 = 0xD4949664cD82660AaE99bEdc034a0deA8A0bd517; uint256 public constant MIN_OPERATION_ASSETS = 1e15; uint256 public constant MAX_VALIDATORS = 32; - uint256 public constant DEFAULT_TARGET_VALIDATOR_COUNT = 8; + // DEFAULT_TARGET_VALIDATOR_COUNT takes the full active set (up to MAX_VALIDATORS) so stake + // spreads across every bonded validator instead of piling onto a top-N subset. + uint256 public constant DEFAULT_TARGET_VALIDATOR_COUNT = MAX_VALIDATORS; uint256 public constant VALIDATOR_SCAN_PAGE_LIMIT = 32; uint256 public constant SCORE_SCALE = 1e18; + // MAX_UNBONDING_ENTRIES mirrors the staking module's per-(delegator,validator) MaxEntries. + // Governance must not set the staking MaxEntries below this, or a batch's undelegation can + // under-fill until entries mature. The undelegate call is wrapped in try/catch, so it + // degrades rather than reverting if that happens. uint256 public constant MAX_UNBONDING_ENTRIES = 7; + uint256 public constant BPS_DENOM = 10000; + // MAX_DELEGATION_BPS_PER_VALIDATOR caps how much of NAV can sit with any single validator, + // keeping each below the one-third Byzantine threshold. When only a few validators exist the + // effective cap is raised to at least an equal share so funds are never stranded. + uint256 public constant MAX_DELEGATION_BPS_PER_VALIDATOR = 3300; + // MAX_SLASH_BUFFER_BPS bounds the operator-configurable slash-exposure buffer. + uint256 public constant MAX_SLASH_BUFFER_BPS = 5000; + // MAX_ABSTAIN_BATCH bounds the number of proposals voted on in one voteAbstain call. + uint256 private constant MAX_ABSTAIN_BATCH = 32; uint8 private constant STEP_SETTLE = 1; uint8 private constant STEP_PROCESS = 2; - uint8 private constant STEP_HARVEST = 3; - uint8 private constant STEP_REFRESH_VALIDATORS = 4; - uint8 private constant STEP_REBALANCE = 5; - uint8 private constant STEP_STAKE = 6; + uint8 private constant STEP_RESUME = 3; + uint8 private constant STEP_HARVEST = 4; + uint8 private constant STEP_REFRESH_VALIDATORS = 5; + uint8 private constant STEP_REBALANCE = 6; + uint8 private constant STEP_STAKE = 7; + // POKE_STEP_COUNT is how many pipeline steps a full poke runs. MaxOps must be at least this + // for every step to get a turn (checked chain-side in EVMSchedulerParams.Validate). Keep it + // in sync with the STEP_* constants above. + uint8 public constant POKE_STEP_COUNT = 7; struct ValidatorTarget { string operatorAddress; @@ -52,6 +72,9 @@ contract StakedBondVault is ERC4626, AccessControl, ReentrancyGuard { int64 creationHeight; int64 completionTime; uint64 unbondingId; + // settled marks an entry whose matured proceeds have already been moved into the + // batch's maturedAssets bucket, so a later sync won't count it twice. + bool settled; } struct WithdrawalBatch { @@ -64,6 +87,14 @@ contract StakedBondVault is ERC4626, AccessControl, ReentrancyGuard { uint256 estimatedAssets; uint256 liquidAssets; uint256 unbondingAssets; + // maturedAssets accumulates this batch's own realized (post-slash) unbonding proceeds + // as its entries complete, so settlement draws from a per-batch bucket instead of the + // shared unaccounted-liquid pool. That keeps one batch from spending another's proceeds. + uint256 maturedAssets; + // pendingUndelegation is principal still waiting to be undelegated because the + // validators were at their per-validator unbonding-entry cap. _resumeUndelegations + // drains it across later pokes. + uint256 pendingUndelegation; uint256 settledAssets; uint256 claimedAssets; int64 maturityTime; @@ -76,6 +107,10 @@ contract StakedBondVault is ERC4626, AccessControl, ReentrancyGuard { bytes private validatorScanNextKey; uint8 public targetValidatorCount = uint8(DEFAULT_TARGET_VALIDATOR_COUNT); + // slashBufferBps is the fraction of NAV kept idle (un-delegated) to limit slash exposure. + // The operator sets it; it defaults to 0, and production should give it a non-zero value + // (e.g. 500 for 5%). + uint256 public slashBufferBps; uint256 public totalIdleLiquid; uint256 public totalDelegated; uint256 public totalWithdrawalUnbonding; @@ -89,11 +124,19 @@ contract StakedBondVault is ERC4626, AccessControl, ReentrancyGuard { bool public pokePaused; uint256 public pokeCount; uint256 public lastPokeOps; + // _lastUndelegationBlock is the block in which the vault last created an unbonding entry. + // Undelegation is limited to one batch per block, and this lives in storage so the limit + // holds across separate txs in the same block: poke() and settle() are both permissionless + // and the scheduler also pokes in EndBlock. If two undelegations hit the same validator in + // one block, the staking module merges them into a single unbonding entry, which breaks + // per-batch slash attribution (double-counting and reserve underflow). + uint256 private _lastUndelegationBlock; mapping(uint256 => WithdrawalRequest) public withdrawalRequests; mapping(uint64 => WithdrawalBatch) private withdrawalBatches; event ValidatorPolicyUpdated(uint8 targetValidatorCount); + event SlashBufferUpdated(uint256 bps); event ValidatorSelectionUpdated(uint256 count); event ValidatorRebalanced(string srcValidator, string dstValidator, uint256 amount, int64 completionTime); event WithdrawalRequested(uint256 indexed requestId, uint64 indexed batchId, address indexed owner, address receiver, uint256 shares); @@ -114,11 +157,17 @@ contract StakedBondVault is ERC4626, AccessControl, ReentrancyGuard { _; } - constructor(address admin, string memory name_, string memory symbol_) + // werc20 is the chain's native ERC20 (the WERC20 precompile), passed in per environment + // instead of hardcoded: production and local use 0xEeee...EEeE, tests use whatever address + // their genesis registers. We check it has code and 18 decimals so a wrong or unregistered + // address reverts at deploy time rather than leaving a broken vault. + constructor(address admin, address werc20, string memory name_, string memory symbol_) ERC20(name_, symbol_) - ERC4626(IERC20Metadata(NATIVE_WERC20)) + ERC4626(IERC20Metadata(werc20)) { require(admin != address(0), "ADMIN"); + require(werc20.code.length > 0, "ASSET_CODE"); + require(IERC20Metadata(werc20).decimals() == 18, "ASSET_DECIMALS"); _grantRole(DEFAULT_ADMIN_ROLE, admin); _grantRole(OPERATOR_ROLE, admin); _grantRole(PAUSER_ROLE, admin); @@ -257,6 +306,7 @@ contract StakedBondVault is ERC4626, AccessControl, ReentrancyGuard { } ops = _tryPokeStep(STEP_SETTLE, maxOps, ops); ops = _tryPokeStep(STEP_PROCESS, maxOps, ops); + ops = _tryPokeStep(STEP_RESUME, maxOps, ops); ops = _tryPokeStep(STEP_HARVEST, maxOps, ops); ops = _tryPokeStep(STEP_REFRESH_VALIDATORS, maxOps, ops); ops = _tryPokeStep(STEP_REBALANCE, maxOps, ops); @@ -266,6 +316,18 @@ contract StakedBondVault is ERC4626, AccessControl, ReentrancyGuard { emit Poked(msg.sender, ops); } + // settle runs the withdrawal settle/process/resume steps regardless of pokePaused, so + // pausing the scheduler can never leave pending withdrawal claims stuck. Permissionless + // and bounded by maxOps. + function settle(uint256 maxOps) external payable noNativeValue nonReentrant returns (uint256 ops) { + if (maxOps == 0) { + return 0; + } + ops = _tryPokeStep(STEP_SETTLE, maxOps, ops); + ops = _tryPokeStep(STEP_PROCESS, maxOps, ops); + ops = _tryPokeStep(STEP_RESUME, maxOps, ops); + } + function pokeStep(uint8 step) external onlySelf returns (bool) { if (step == STEP_SETTLE) { return _settleMatureBatch(); @@ -273,6 +335,9 @@ contract StakedBondVault is ERC4626, AccessControl, ReentrancyGuard { if (step == STEP_PROCESS) { return _processCurrentBatch(); } + if (step == STEP_RESUME) { + return _resumeUndelegations(); + } if (step == STEP_HARVEST) { return _harvest(); } @@ -302,6 +367,28 @@ contract StakedBondVault is ERC4626, AccessControl, ReentrancyGuard { emit ValidatorPolicyUpdated(count); } + // setSlashBufferBps sets the fraction of NAV kept un-delegated as a slash buffer, capped at + // MAX_SLASH_BUFFER_BPS. It defaults to 0; production should set it non-zero. + function setSlashBufferBps(uint256 bps) external payable noNativeValue onlyRole(OPERATOR_ROLE) { + require(bps <= MAX_SLASH_BUFFER_BPS, "BUFFER"); + slashBufferBps = bps; + emit SlashBufferUpdated(bps); + } + + // voteAbstain casts an abstain vote from the vault on each supplied governance proposal. + // The vault's bonded deposits count toward its validators' voting power, and the standard + // tally casts a non-voting delegator's power the way its validator votes. So the vault has + // to abstain explicitly, or the pooled deposits would swing governance outcomes. The list of + // open proposal ids comes from an off-chain keeper to keep the contract under the EIP-170 + // code-size limit; re-voting abstain during the voting period is a no-op. The abstaining + // stake still counts toward quorum, which is a known trade-off. + function voteAbstain(uint64[] calldata proposalIds) external payable noNativeValue onlyRole(OPERATOR_ROLE) { + require(proposalIds.length <= MAX_ABSTAIN_BATCH, "BATCH"); + for (uint256 i = 0; i < proposalIds.length; i++) { + try GOV_CONTRACT.vote(address(this), proposalIds[i], VoteOption.Abstain, "") {} catch {} + } + } + function withdrawalBatch(uint64 batchId) external view @@ -404,7 +491,9 @@ contract StakedBondVault is ERC4626, AccessControl, ReentrancyGuard { maturity = _undelegateForBatch(batch, remaining); } batch.maturityTime = maturity; - if (batch.unbondingAssets == 0) { + // A batch is claimable right away only if it's fully liquid: nothing unbonding and + // nothing left to undelegate. Otherwise it settles later in _settleMatureBatch. + if (batch.unbondingAssets == 0 && batch.pendingUndelegation == 0) { batch.claimable = true; batch.settledAssets = batch.liquidAssets; } @@ -416,54 +505,106 @@ contract StakedBondVault is ERC4626, AccessControl, ReentrancyGuard { } function _settleMatureBatch() internal returns (bool) { + // One batch never blocks another. A batch that isn't ready yet (still unbonding, still + // has pending undelegation, or waiting on the maturity confirmation) is skipped rather + // than returned on, so it can't freeze claims for later batches. The settlement cursor + // only moves past the leading run of already-claimable batches, so earlier unsettled + // batches get re-scanned next time. + uint64 cursor = nextSettlementBatchId; + bool cursorAdvancing = true; for (uint64 batchId = nextSettlementBatchId; batchId < currentBatchId; batchId++) { WithdrawalBatch storage batch = withdrawalBatches[batchId]; - if (!batch.processed || batch.claimable || batch.unbondingAssets == 0) { - nextSettlementBatchId = batchId + 1; + if (!batch.processed || batch.claimable) { + if (cursorAdvancing) { + cursor = batchId + 1; + } continue; } + cursorAdvancing = false; + + // Wait until this batch's full principal is unbonding (nothing left to resume). + if (batch.pendingUndelegation != 0) { + continue; + } + // Wait until the latest unbonding entry has matured... if (block.timestamp < uint256(uint64(batch.maturityTime))) { - nextSettlementBatchId = batchId; - return false; + continue; } + // ...then require a one-block confirmation before settling. if (batch.firstMatureBlock == 0) { batch.firstMatureBlock = uint64(block.number); - nextSettlementBatchId = batchId; emit WithdrawalBatchMatured(batchId, batch.firstMatureBlock); + nextSettlementBatchId = cursor; return true; } if (block.number <= batch.firstMatureBlock) { - nextSettlementBatchId = batchId; - return false; + continue; } + // Move each entry's realized (post-slash) proceeds into this batch's own + // maturedAssets bucket, and finalize only once every entry has completed. + // + // Known corner case: a slash that lands during the unbonding window is only picked + // up here, at maturity, by which point the entry has already left the staking queue, + // so maturedAssets can still hold the pre-slash amount. The min(maturedAssets, + // _unaccountedLiquid()) clamp below limits a single settling batch to what actually + // arrived, but when several batches or harvest proceeds share the unaccounted pool + // the loss can still spread across them. A full fix would ratchet each entry while + // it's still pending, before maturity; that's deferred because it means reconciling + // how matured native proceeds re-enter the WERC20-denominated accounting. bool complete = _syncBatchUnbonding(batch); if (!complete) { - nextSettlementBatchId = batchId; - return false; + continue; } - uint256 expected = batch.unbondingAssets; - uint256 matured = _min(expected, _unaccountedLiquid()); - totalWithdrawalUnbonding -= expected; - totalReservedAssets = totalReservedAssets - expected + matured; + // Draw only this batch's matured proceeds, clamped to what has actually arrived in + // the contract. A fully-slashed batch settles at its liquid portion so users can + // still claim it rather than having it stranded. + uint256 want = batch.maturedAssets; + uint256 matured = _min(want, _unaccountedLiquid()); + if (matured < want) { + // Past maturity the funds should already be here; if they aren't, drop the + // shortfall from reserves so the accounting invariant stays exact. + totalReservedAssets -= (want - matured); + } totalLiquidReserved += matured; + batch.maturedAssets = 0; batch.settledAssets = batch.liquidAssets + matured; batch.claimable = true; - nextSettlementBatchId = batchId + 1; + nextSettlementBatchId = cursor; emit WithdrawalBatchSettled(batchId, batch.settledAssets); return true; } + nextSettlementBatchId = cursor; return false; } + // _undelegateForBatch queues undelegations to cover assetsNeeded, one per validator. It + // never reverts on a shortfall (that would freeze the whole batch). Any principal it can't + // undelegate right now, because the validators are at their per-validator unbonding-entry + // cap, goes into batch.pendingUndelegation for _resumeUndelegations to drain over later + // pokes. If no delegation is left anywhere, the remainder can't be met, so the batch settles + // for less (redeemers share the shortfall) instead of getting stuck. function _undelegateForBatch(WithdrawalBatch storage batch, uint256 assetsNeeded) internal returns (int64 maturity) { require(validators.length != 0, "VALIDATORS"); + // Limit undelegation to one batch per block. If the vault already created an unbonding + // entry this block, defer the rest via pendingUndelegation so two batches never + // undelegate the same validator in the same block; the staking module would merge those + // into one entry and break per-batch slash attribution. + if (_lastUndelegationBlock == block.number) { + batch.pendingUndelegation = assetsNeeded; + return batch.maturityTime; + } _syncDelegations(); uint256 remaining = assetsNeeded; + bool anyDelegationLeft = false; for (uint256 i = 0; i < validators.length && remaining != 0; i++) { ValidatorTarget storage target = validators[i]; - if (target.delegated == 0 || _unbondingEntryCount(target.operatorAddress) >= MAX_UNBONDING_ENTRIES) { + if (target.delegated == 0) { + continue; + } + anyDelegationLeft = true; + if (_unbondingEntryCount(target.operatorAddress) >= MAX_UNBONDING_ENTRIES) { continue; } uint256 amount = _min(target.delegated, remaining); @@ -478,11 +619,36 @@ contract StakedBondVault is ERC4626, AccessControl, ReentrancyGuard { maturity = completion; } remaining -= amount; + _lastUndelegationBlock = block.number; } catch { continue; } } - require(remaining == 0, "DELEGATED"); + // Keep the remainder for a retry only if there's still delegation that could be + // undelegated once entries free up; otherwise clear it, since it can't be met. + batch.pendingUndelegation = (remaining != 0 && anyDelegationLeft) ? remaining : 0; + } + + // _resumeUndelegations drains one batch's outstanding pendingUndelegation per poke (and at + // most one undelegating batch per block, see _lastUndelegationBlock), extending the batch's + // maturity to cover the newly queued entries. + function _resumeUndelegations() internal returns (bool) { + if (_lastUndelegationBlock == block.number) { + return false; + } + for (uint64 batchId = nextSettlementBatchId; batchId < currentBatchId; batchId++) { + WithdrawalBatch storage batch = withdrawalBatches[batchId]; + if (!batch.processed || batch.claimable || batch.pendingUndelegation == 0) { + continue; + } + uint256 need = batch.pendingUndelegation; + int64 maturity = _undelegateForBatch(batch, need); + if (maturity > batch.maturityTime) { + batch.maturityTime = maturity; + } + return true; + } + return false; } function _refreshValidatorSelection() internal returns (bool) { @@ -499,8 +665,10 @@ contract StakedBondVault is ERC4626, AccessControl, ReentrancyGuard { if (validator.jailed || validator.status != BondStatus.Bonded || validator.tokens == 0) { continue; } - uint256 score = validator.tokens.mulDiv(SCORE_SCALE, SCORE_SCALE + validator.commission, Math.Rounding.Down); - _insertScanCandidate(validator.operatorAddress, score); + // Weight every eligible validator equally, so stake spreads across the whole active + // set instead of piling onto the highest-stake or lowest-commission ones. _stakeIdle + // caps concentration per validator on top of this. + _insertScanCandidate(validator.operatorAddress, SCORE_SCALE); } validatorScanNextKey = page.nextKey; if (page.nextKey.length == 0) { @@ -546,25 +714,54 @@ contract StakedBondVault is ERC4626, AccessControl, ReentrancyGuard { return false; } _syncDelegations(); + + uint256 nav = totalDelegated + totalIdleLiquid; + // Hold back the slash buffer from what we're willing to stake. Zero by default. + uint256 stakeable = totalIdleLiquid; + if (slashBufferBps != 0) { + uint256 buffer = nav.mulDiv(slashBufferBps, BPS_DENOM, Math.Rounding.Up); + if (totalIdleLiquid <= buffer) { + return false; + } + stakeable = totalIdleLiquid - buffer; + } + uint256 totalScore; + uint256 numSelected; for (uint256 i = 0; i < validators.length; i++) { if (validators[i].selected) { totalScore += validators[i].score; + numSelected++; } } if (totalScore == 0) { return false; } - (bool ok, uint256 destIndex, uint256 deficit) = _stakeDestination(totalScore, totalDelegated + totalIdleLiquid); + (bool ok, uint256 destIndex, uint256 deficit) = _stakeDestination(totalScore, totalDelegated + stakeable); if (!ok) { return false; } - uint256 amount = _min(totalIdleLiquid, deficit == 0 ? totalIdleLiquid : deficit); + uint256 amount = _min(stakeable, deficit == 0 ? stakeable : deficit); if (amount == 0) { return false; } + + // Apply the per-validator delegation cap. It's raised to at least an equal share + // (ceil of 1/numSelected) so it won't strand funds when few validators are available; + // with many validators it settles at MAX_DELEGATION_BPS_PER_VALIDATOR. ValidatorTarget storage dst = validators[destIndex]; + uint256 equalShareBps = (BPS_DENOM + numSelected - 1) / numSelected; + uint256 capBps = equalShareBps > MAX_DELEGATION_BPS_PER_VALIDATOR ? equalShareBps : MAX_DELEGATION_BPS_PER_VALIDATOR; + uint256 cap = nav.mulDiv(capBps, BPS_DENOM, Math.Rounding.Down); + if (dst.delegated >= cap) { + return false; + } + amount = _min(amount, cap - dst.delegated); + if (amount == 0) { + return false; + } + try STAKING_CONTRACT.delegate(address(this), dst.operatorAddress, amount) returns (bool success) { if (!success) { return false; @@ -738,7 +935,8 @@ contract StakedBondVault is ERC4626, AccessControl, ReentrancyGuard { expectedAssets: amount, creationHeight: 0, completionTime: completion, - unbondingId: 0 + unbondingId: 0, + settled: false }); try STAKING_CONTRACT.unbondingDelegation(address(this), validatorAddress) returns (UnbondingDelegationOutput memory output) { for (uint256 i = 0; i < output.entries.length; i++) { @@ -752,24 +950,44 @@ contract StakedBondVault is ERC4626, AccessControl, ReentrancyGuard { } catch {} } + // _syncBatchUnbonding reconciles a batch's unbonding entries against the staking module, + // moving each entry's realized (post-slash) proceeds into the batch's own maturedAssets + // bucket as it completes. Returns true only once every entry has been resolved. function _syncBatchUnbonding(WithdrawalBatch storage batch) internal returns (bool complete) { complete = true; - uint256 syncedExpected; for (uint256 i = 0; i < batch.unbondingEntries.length; i++) { - (bool pending, uint256 currentBalance) = _pendingEntryBalance(batch.unbondingEntries[i]); + UnbondingEntry storage entry = batch.unbondingEntries[i]; + if (entry.settled) { + continue; + } + (bool pending, uint256 currentBalance) = _pendingEntryBalance(entry); if (pending) { - complete = false; - if (currentBalance < batch.unbondingEntries[i].expectedAssets) { - batch.unbondingEntries[i].expectedAssets = currentBalance; + // Still unbonding: if a slash reduced the balance, ratchet the expected amount + // down and take the loss out of this batch and the global reserves, so it + // doesn't spread onto other batches. + if (currentBalance < entry.expectedAssets) { + uint256 slashLoss = entry.expectedAssets - currentBalance; + entry.expectedAssets = currentBalance; + batch.unbondingAssets -= slashLoss; + totalWithdrawalUnbonding -= slashLoss; + totalReservedAssets -= slashLoss; } + complete = false; + } else if (uint256(uint64(entry.completionTime)) <= block.timestamp) { + // Gone from the unbonding queue after maturity: the proceeds have reached the + // vault. Move this entry's realized amount into the batch's matured bucket. + uint256 realized = entry.expectedAssets; + entry.settled = true; + batch.unbondingAssets -= realized; + batch.maturedAssets += realized; + totalWithdrawalUnbonding -= realized; + // totalReservedAssets doesn't change: the amount stays reserved for this batch, + // it's just reclassified from unbonding to matured. + } else { + // Gone but not yet mature, so this is a transient lookup miss, not a completion. + // Treat it as still pending so it's neither credited nor lost. + complete = false; } - syncedExpected += batch.unbondingEntries[i].expectedAssets; - } - if (syncedExpected < batch.unbondingAssets) { - uint256 reduction = batch.unbondingAssets - syncedExpected; - batch.unbondingAssets = syncedExpected; - totalWithdrawalUnbonding -= reduction; - totalReservedAssets -= reduction; } } diff --git a/evmd/app.go b/evmd/app.go index 2b706887..4972fc27 100644 --- a/evmd/app.go +++ b/evmd/app.go @@ -503,6 +503,9 @@ func NewExampleApp( app.GovKeeper, app.SlashingKeeper, appCodec, + // PoA staking guard: only the configured community-pool vault may delegate via + // the staking precompile; validator creation and unjail are blocked. + precompiletypes.WithStakingGuard(app.stakingGuardAllowlist), ), ) @@ -847,6 +850,7 @@ func (app *EVMD) setAnteHandler(txConfig client.TxConfig, maxGasWanted uint64) { MaxTxGasWanted: maxGasWanted, DynamicFeeChecker: true, PendingTxListener: app.onPendingTx, + StakingGuardAllowlist: app.stakingGuardAllowlist, } if err := options.Validate(); err != nil { panic(err) @@ -855,6 +859,19 @@ func (app *EVMD) setAnteHandler(txConfig client.TxConfig, maxGasWanted uint64) { app.SetAnteHandler(evmante.NewAnteHandler(options)) } +// stakingGuardAllowlist implements stakingguard.StakingPolicyFunc. Enforcement kicks in once +// the community-pool vault is configured as the EVM scheduler's target contract. From then on +// only that vault may use the staking precompile's delegation family, and validator creation +// and unjail are blocked. Before the vault is set (default or test genesis) the guard stays +// off. The vendored PoA ante blocks Cosmos-path delegation on its own regardless. +func (app *EVMD) stakingGuardAllowlist(ctx sdk.Context) (allowed []sdk.AccAddress, enforced bool) { + target := app.EVMKeeper.GetParams(ctx).Scheduler.TargetContract + if target == "" || !common.IsHexAddress(target) { + return nil, false + } + return []sdk.AccAddress{sdk.AccAddress(common.HexToAddress(target).Bytes())}, true +} + func (app *EVMD) onPendingTx(hash common.Hash) { for _, listener := range app.pendingTxListeners { listener(hash) diff --git a/evmd/tests/integration/precompiles/staking/precompile_staking_test.go b/evmd/tests/integration/precompiles/staking/precompile_staking_test.go index 13ee8b22..31a6c294 100644 --- a/evmd/tests/integration/precompiles/staking/precompile_staking_test.go +++ b/evmd/tests/integration/precompiles/staking/precompile_staking_test.go @@ -11,6 +11,11 @@ import ( testapp "github.com/cosmos/evm/testutil/app" ) +// The PoA staking guard is off in these suites because their genesis configures no +// community-pool vault (EVM scheduler target), so the staking precompile behaves as upstream. +// The guard's own enforcement (delegation limited to the vault, validator creation and unjail +// blocked) is covered by the x/vm StakedBondVault suite, +// TestStakingGuardBlocksNonVaultDelegationViaPrecompile, and the stakingguard unit tests. func TestStakingPrecompileTestSuite(t *testing.T) { create := testapp.ToEvmAppCreator[evm.StakingPrecompileApp](integration.CreateEvmd, "evm.StakingPrecompileApp") s := staking.NewPrecompileTestSuite(create) diff --git a/precompiles/types/defaults.go b/precompiles/types/defaults.go index b477eb84..126b372a 100644 --- a/precompiles/types/defaults.go +++ b/precompiles/types/defaults.go @@ -7,6 +7,7 @@ import ( evmaddress "github.com/cosmos/evm/encoding/address" ibcutils "github.com/cosmos/evm/ibc" cmn "github.com/cosmos/evm/precompiles/common" + "github.com/cosmos/evm/stakingguard" erc20Keeper "github.com/cosmos/evm/x/erc20/keeper" transferkeeper "github.com/cosmos/evm/x/ibc/transfer/keeper" channelkeeper "github.com/cosmos/ibc-go/v10/modules/core/04-channel/keeper" @@ -28,6 +29,10 @@ type Optionals struct { AddressCodec address.Codec // used by gov/staking ValidatorAddrCodec address.Codec // used by slashing ConsensusAddrCodec address.Codec // used by slashing + // StakingGuardAllowlist, when set, applies the PoA staking policy to the staking and + // slashing precompiles: only allowlisted delegators may delegate, and validator creation + // and unjail are blocked. A nil value (the default) leaves the precompiles unguarded. + StakingGuardAllowlist stakingguard.StakingPolicyFunc } func defaultOptionals() Optionals { @@ -58,6 +63,14 @@ func WithConsensusAddrCodec(codec address.Codec) Option { } } +// WithStakingGuard enables PoA staking enforcement on the staking and slashing +// precompiles using the given allowlist (read from live state at call time). +func WithStakingGuard(allowed stakingguard.StakingPolicyFunc) Option { + return func(opts *Optionals) { + opts.StakingGuardAllowlist = allowed + } +} + const bech32PrecompileBaseGas = 6_000 // DefaultStaticPrecompiles returns the list of all available static precompiled contracts from Cosmos EVM. diff --git a/precompiles/types/static_precompiles.go b/precompiles/types/static_precompiles.go index f4a9914d..07de86ef 100644 --- a/precompiles/types/static_precompiles.go +++ b/precompiles/types/static_precompiles.go @@ -20,6 +20,7 @@ import ( slashingprecompile "github.com/cosmos/evm/precompiles/slashing" stakingprecompile "github.com/cosmos/evm/precompiles/staking" teeprecompile "github.com/cosmos/evm/precompiles/tee" + "github.com/cosmos/evm/stakingguard" erc20Keeper "github.com/cosmos/evm/x/erc20/keeper" transferkeeper "github.com/cosmos/evm/x/ibc/transfer/keeper" @@ -29,7 +30,9 @@ import ( distributionkeeper "github.com/cosmos/cosmos-sdk/x/distribution/keeper" govkeeper "github.com/cosmos/cosmos-sdk/x/gov/keeper" slashingkeeper "github.com/cosmos/cosmos-sdk/x/slashing/keeper" + slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" ) type StaticPrecompiles map[common.Address]vm.PrecompiledContract @@ -68,9 +71,16 @@ func (s StaticPrecompiles) WithStakingPrecompile( opt(&options) } + var stakingMsgServer stakingtypes.MsgServer = stakingkeeper.NewMsgServerImpl(&stakingKeeper) + if options.StakingGuardAllowlist != nil { + // Guard the EVM path: only the allowlisted vault may delegate, and validator creation + // is blocked. Gov onboarding goes through the module's own router, not this server. + stakingMsgServer = stakingguard.NewStakingMsgServer(stakingMsgServer, options.StakingGuardAllowlist) + } + stakingPrecompile := stakingprecompile.NewPrecompile( stakingKeeper, - stakingkeeper.NewMsgServerImpl(&stakingKeeper), + stakingMsgServer, stakingkeeper.NewQuerier(&stakingKeeper), bankKeeper, options.AddressCodec, @@ -176,9 +186,15 @@ func (s StaticPrecompiles) WithSlashingPrecompile( opt(&options) } + var slashingMsgServer slashingtypes.MsgServer = slashingkeeper.NewMsgServerImpl(slashingKeeper) + if options.StakingGuardAllowlist != nil { + // Guard the EVM path: the slashing precompile's MsgUnjail is blocked on this PoA chain. + slashingMsgServer = stakingguard.NewSlashingMsgServer(slashingMsgServer, options.StakingGuardAllowlist) + } + slashingPrecompile := slashingprecompile.NewPrecompile( slashingKeeper, - slashingkeeper.NewMsgServerImpl(slashingKeeper), + slashingMsgServer, bankKeeper, options.ValidatorAddrCodec, options.ConsensusAddrCodec, diff --git a/stakingguard/ante.go b/stakingguard/ante.go new file mode 100644 index 00000000..5ad5965e --- /dev/null +++ b/stakingguard/ante.go @@ -0,0 +1,52 @@ +package stakingguard + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/authz" +) + +// maxAuthzNestingDepth bounds authz.MsgExec recursion to avoid pathological nesting. +const maxAuthzNestingDepth = 8 + +// Decorator is an ante decorator that enforces the PoA staking policy on the Cosmos tx +// path, recursing into authz.MsgExec so wrapped staking messages cannot bypass the guard. +// It complements the vendored PoA ante (which blocks the delegation family) by also +// blocking MsgCreateValidator / MsgUnjail and unwrapping authz. +type Decorator struct { + allowed StakingPolicyFunc +} + +// NewDecorator returns a staking-guard ante decorator. +func NewDecorator(allowed StakingPolicyFunc) Decorator { + return Decorator{allowed: allowed} +} + +// AnteHandle implements sdk.AnteDecorator. +func (d Decorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) { + for _, msg := range tx.GetMsgs() { + if err := d.checkRecursive(ctx, msg, 0); err != nil { + return ctx, err + } + } + return next(ctx, tx, simulate) +} + +// checkRecursive applies the policy to msg, descending into authz.MsgExec payloads. +func (d Decorator) checkRecursive(ctx sdk.Context, msg sdk.Msg, depth int) error { + if exec, ok := msg.(*authz.MsgExec); ok { + if depth >= maxAuthzNestingDepth { + return ErrDelegationRestricted.Wrap("authz nesting too deep") + } + inner, err := exec.GetMessages() + if err != nil { + return err + } + for _, im := range inner { + if err := d.checkRecursive(ctx, im, depth+1); err != nil { + return err + } + } + return nil + } + return CheckMsg(ctx, msg, d.allowed) +} diff --git a/stakingguard/msgserver.go b/stakingguard/msgserver.go new file mode 100644 index 00000000..f15d235f --- /dev/null +++ b/stakingguard/msgserver.go @@ -0,0 +1,74 @@ +package stakingguard + +import ( + "context" + + sdk "github.com/cosmos/cosmos-sdk/types" + slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" +) + +// NewStakingMsgServer wraps a staking MsgServer so that every delegation-family and +// validator-creation call routed through it (e.g. by the staking precompile) is checked +// against the PoA policy before reaching the underlying server. +func NewStakingMsgServer(inner stakingtypes.MsgServer, allowed StakingPolicyFunc) stakingtypes.MsgServer { + return &stakingMsgServer{MsgServer: inner, allowed: allowed} +} + +type stakingMsgServer struct { + stakingtypes.MsgServer // EditValidator + UpdateParams fall through unguarded + allowed StakingPolicyFunc +} + +func (s *stakingMsgServer) CreateValidator(ctx context.Context, msg *stakingtypes.MsgCreateValidator) (*stakingtypes.MsgCreateValidatorResponse, error) { + if err := CheckMsg(sdk.UnwrapSDKContext(ctx), msg, s.allowed); err != nil { + return nil, err + } + return s.MsgServer.CreateValidator(ctx, msg) +} + +func (s *stakingMsgServer) Delegate(ctx context.Context, msg *stakingtypes.MsgDelegate) (*stakingtypes.MsgDelegateResponse, error) { + if err := CheckMsg(sdk.UnwrapSDKContext(ctx), msg, s.allowed); err != nil { + return nil, err + } + return s.MsgServer.Delegate(ctx, msg) +} + +func (s *stakingMsgServer) Undelegate(ctx context.Context, msg *stakingtypes.MsgUndelegate) (*stakingtypes.MsgUndelegateResponse, error) { + if err := CheckMsg(sdk.UnwrapSDKContext(ctx), msg, s.allowed); err != nil { + return nil, err + } + return s.MsgServer.Undelegate(ctx, msg) +} + +func (s *stakingMsgServer) BeginRedelegate(ctx context.Context, msg *stakingtypes.MsgBeginRedelegate) (*stakingtypes.MsgBeginRedelegateResponse, error) { + if err := CheckMsg(sdk.UnwrapSDKContext(ctx), msg, s.allowed); err != nil { + return nil, err + } + return s.MsgServer.BeginRedelegate(ctx, msg) +} + +func (s *stakingMsgServer) CancelUnbondingDelegation(ctx context.Context, msg *stakingtypes.MsgCancelUnbondingDelegation) (*stakingtypes.MsgCancelUnbondingDelegationResponse, error) { + if err := CheckMsg(sdk.UnwrapSDKContext(ctx), msg, s.allowed); err != nil { + return nil, err + } + return s.MsgServer.CancelUnbondingDelegation(ctx, msg) +} + +// NewSlashingMsgServer wraps a slashing MsgServer so that MsgUnjail routed through it +// (e.g. by the slashing precompile) is rejected under the PoA policy. +func NewSlashingMsgServer(inner slashingtypes.MsgServer, allowed StakingPolicyFunc) slashingtypes.MsgServer { + return &slashingMsgServer{MsgServer: inner, allowed: allowed} +} + +type slashingMsgServer struct { + slashingtypes.MsgServer // UpdateParams falls through unguarded + allowed StakingPolicyFunc +} + +func (s *slashingMsgServer) Unjail(ctx context.Context, msg *slashingtypes.MsgUnjail) (*slashingtypes.MsgUnjailResponse, error) { + if err := CheckMsg(sdk.UnwrapSDKContext(ctx), msg, s.allowed); err != nil { + return nil, err + } + return s.MsgServer.Unjail(ctx, msg) +} diff --git a/stakingguard/policy.go b/stakingguard/policy.go new file mode 100644 index 00000000..4e0db296 --- /dev/null +++ b/stakingguard/policy.go @@ -0,0 +1,115 @@ +// Package stakingguard enforces this chain's Proof-of-Authority (PoA) staking policy on both +// the EVM path (as a staking/slashing MsgServer decorator the precompiles call into) and the +// Cosmos/authz path (as an ante decorator). +// +// The vendored PoA module (xrplevm/node/v10) only blocks the delegation family on the Cosmos +// ante path; it doesn't cover the EVM staking precompile, authz.MsgExec, or validator- +// management messages. This package fills those gaps from local code without touching the +// vendored module. +// +// The policy: +// - MsgCreateValidator is ALWAYS rejected, unconditionally — independent of whether the +// community-pool vault is configured. Creating validators is a pure PoA invariant with no +// legitimate caller on this chain (validators join only via the gov PoA AddValidator flow), +// so it must never depend on operational state that a misconfiguration or migration window +// could clear. +// - MsgUnjail is ALWAYS rejected, unconditionally, for the same reason: the validator +// lifecycle is governance-managed. +// - The delegation family (Delegate, Undelegate, BeginRedelegate, CancelUnbondingDelegation) +// is allowed only when the delegator is in the allowlist, i.e. the community-pool vault. An +// empty allowlist fails closed. This gate is only active once enforcement is on (the vault +// is configured); before that, delegation is unrestricted (upstream behavior), which keeps +// the upstream precompile test suites valid. +// - MsgEditValidator, the staking/slashing UpdateParams, and everything else are allowed: +// they can't change set membership or voting power, and the msg handlers already restrict +// them to the operator or gov. +package stakingguard + +import ( + errorsmod "cosmossdk.io/errors" + + sdk "github.com/cosmos/cosmos-sdk/types" + slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" +) + +// ModuleName is the error codespace for the staking guard. +const ModuleName = "stakingguard" + +var ( + // ErrValidatorCreationDisabled is returned for MsgCreateValidator. + ErrValidatorCreationDisabled = errorsmod.Register(ModuleName, 2, + "creating validators is disabled on this PoA chain; validators are onboarded via governance") + // ErrUnjailDisabled is returned for MsgUnjail. + ErrUnjailDisabled = errorsmod.Register(ModuleName, 3, + "unjailing is disabled on this PoA chain; validator lifecycle is governance-managed") + // ErrDelegationRestricted is returned for delegation-family messages from a + // non-allowlisted delegator. + ErrDelegationRestricted = errorsmod.Register(ModuleName, 4, + "delegation is restricted to the authorized community-pool vault on this PoA chain") +) + +// StakingPolicyFunc reads live state at call time and returns the delegator addresses allowed +// to use the delegation family, plus whether the delegation allowlist is active at all. +// The allowlist turns on once the chain configures the community-pool vault (the EVM scheduler +// target contract); before that (default or test genesis) delegation is unrestricted so the +// upstream staking precompile suites stay valid. The real chain sets the vault at launch. Note +// this flag gates ONLY the delegation family — validator creation and unjailing are blocked +// unconditionally (see CheckMsg). The vendored PoA ante blocks Cosmos-tx delegation on its own +// regardless of this flag. +type StakingPolicyFunc func(ctx sdk.Context) (allowed []sdk.AccAddress, enforced bool) + +// CheckMsg enforces the PoA staking policy on a single message. A nil error means the message +// is permitted. +func CheckMsg(ctx sdk.Context, msg sdk.Msg, policy StakingPolicyFunc) error { + // Validator-set integrity is a pure PoA invariant with no legitimate caller on this chain, + // so these are rejected unconditionally — independent of the policy/enforcement flag. This + // closes the migration-window / misconfiguration hole where an unset vault target would + // otherwise silently reopen validator creation and unjailing. + switch msg.(type) { + case *stakingtypes.MsgCreateValidator: + return ErrValidatorCreationDisabled + case *slashingtypes.MsgUnjail: + return ErrUnjailDisabled + } + + // The delegation family is gated on the allowlist, which is only populated once the + // community-pool vault is configured. Before that, enforcement is inactive and delegation + // is unrestricted (upstream behavior). + var allowed []sdk.AccAddress + enforced := false + if policy != nil { + allowed, enforced = policy(ctx) + } + if !enforced { + return nil + } + switch m := msg.(type) { + case *stakingtypes.MsgDelegate: + return checkDelegator(m.DelegatorAddress, allowed) + case *stakingtypes.MsgUndelegate: + return checkDelegator(m.DelegatorAddress, allowed) + case *stakingtypes.MsgBeginRedelegate: + return checkDelegator(m.DelegatorAddress, allowed) + case *stakingtypes.MsgCancelUnbondingDelegation: + return checkDelegator(m.DelegatorAddress, allowed) + default: + // MsgEditValidator, x/staking & x/slashing MsgUpdateParams, and any non-staking + // message are permitted. + return nil + } +} + +// checkDelegator permits the delegation only if the delegator is in the allowlist. +func checkDelegator(delegator string, allowed []sdk.AccAddress) error { + addr, err := sdk.AccAddressFromBech32(delegator) + if err != nil { + return errorsmod.Wrapf(ErrDelegationRestricted, "invalid delegator address %q", delegator) + } + for _, a := range allowed { + if a.Equals(addr) { + return nil + } + } + return errorsmod.Wrapf(ErrDelegationRestricted, "delegator %s is not authorized to stake", delegator) +} diff --git a/stakingguard/policy_test.go b/stakingguard/policy_test.go new file mode 100644 index 00000000..ee073e95 --- /dev/null +++ b/stakingguard/policy_test.go @@ -0,0 +1,108 @@ +package stakingguard_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/cosmos/evm/stakingguard" + + sdk "github.com/cosmos/cosmos-sdk/types" + slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" +) + +var ( + vaultAddr = sdk.AccAddress([]byte("vault_address_20byte")) + strangerAd = sdk.AccAddress([]byte("stranger_addr_20byte")) + valAddr = sdk.ValAddress([]byte("validator_addr_20byt")).String() +) + +// allowVault enforces the policy, permitting only the vault address. +func allowVault(sdk.Context) ([]sdk.AccAddress, bool) { return []sdk.AccAddress{vaultAddr}, true } + +// allowNone enforces the policy with an empty allowlist (fail-closed for delegation). +func allowNone(sdk.Context) ([]sdk.AccAddress, bool) { return nil, true } + +// notEnforced reports enforcement inactive (guard is a no-op). +func notEnforced(sdk.Context) ([]sdk.AccAddress, bool) { return nil, false } + +func TestCheckMsg_ValidatorManagementBlocked(t *testing.T) { + ctx := sdk.Context{} + + err := stakingguard.CheckMsg(ctx, &stakingtypes.MsgCreateValidator{ValidatorAddress: valAddr}, allowVault) + require.ErrorIs(t, err, stakingguard.ErrValidatorCreationDisabled) + + err = stakingguard.CheckMsg(ctx, &slashingtypes.MsgUnjail{ValidatorAddr: valAddr}, allowVault) + require.ErrorIs(t, err, stakingguard.ErrUnjailDisabled) +} + +func TestCheckMsg_EditValidatorAllowed(t *testing.T) { + // EditValidator cannot change set membership or power; it is permitted. + err := stakingguard.CheckMsg(sdk.Context{}, &stakingtypes.MsgEditValidator{ValidatorAddress: valAddr}, allowNone) + require.NoError(t, err) +} + +func TestCheckMsg_DelegationFamilyAllowlist(t *testing.T) { + ctx := sdk.Context{} + + msgs := []sdk.Msg{ + &stakingtypes.MsgDelegate{DelegatorAddress: vaultAddr.String(), ValidatorAddress: valAddr}, + &stakingtypes.MsgUndelegate{DelegatorAddress: vaultAddr.String(), ValidatorAddress: valAddr}, + &stakingtypes.MsgBeginRedelegate{DelegatorAddress: vaultAddr.String()}, + &stakingtypes.MsgCancelUnbondingDelegation{DelegatorAddress: vaultAddr.String(), ValidatorAddress: valAddr}, + } + for _, m := range msgs { + require.NoError(t, stakingguard.CheckMsg(ctx, m, allowVault), "vault delegation should be allowed: %T", m) + } + + // A non-allowlisted delegator is rejected for the whole delegation family. + for _, m := range msgs { + var stranger sdk.Msg + switch m.(type) { + case *stakingtypes.MsgDelegate: + stranger = &stakingtypes.MsgDelegate{DelegatorAddress: strangerAd.String(), ValidatorAddress: valAddr} + case *stakingtypes.MsgUndelegate: + stranger = &stakingtypes.MsgUndelegate{DelegatorAddress: strangerAd.String(), ValidatorAddress: valAddr} + case *stakingtypes.MsgBeginRedelegate: + stranger = &stakingtypes.MsgBeginRedelegate{DelegatorAddress: strangerAd.String()} + case *stakingtypes.MsgCancelUnbondingDelegation: + stranger = &stakingtypes.MsgCancelUnbondingDelegation{DelegatorAddress: strangerAd.String(), ValidatorAddress: valAddr} + } + require.ErrorIs(t, stakingguard.CheckMsg(ctx, stranger, allowVault), stakingguard.ErrDelegationRestricted) + } +} + +func TestCheckMsg_FailClosedOnEmptyAllowlist(t *testing.T) { + // When enforcement is active but the allowlist is empty, even the vault is rejected + // (fail-closed): delegation is only permitted for an explicitly allowlisted address. + err := stakingguard.CheckMsg(sdk.Context{}, &stakingtypes.MsgDelegate{DelegatorAddress: vaultAddr.String(), ValidatorAddress: valAddr}, allowNone) + require.ErrorIs(t, err, stakingguard.ErrDelegationRestricted) +} + +func TestCheckMsg_NonStakingAllowed(t *testing.T) { + // staking/slashing param updates and unrelated messages are not restricted. + require.NoError(t, stakingguard.CheckMsg(sdk.Context{}, &stakingtypes.MsgUpdateParams{}, allowNone)) + require.NoError(t, stakingguard.CheckMsg(sdk.Context{}, &slashingtypes.MsgUpdateParams{}, allowNone)) +} + +func TestCheckMsg_DelegationInactiveWhenNotEnforced(t *testing.T) { + ctx := sdk.Context{} + // With enforcement inactive (no vault configured), the delegation family is unrestricted + // (upstream/default behavior): even a stranger may delegate. + require.NoError(t, stakingguard.CheckMsg(ctx, &stakingtypes.MsgDelegate{DelegatorAddress: strangerAd.String(), ValidatorAddress: valAddr}, notEnforced)) + + // A nil policy is also treated as inactive for delegation. + require.NoError(t, stakingguard.CheckMsg(ctx, &stakingtypes.MsgDelegate{DelegatorAddress: strangerAd.String(), ValidatorAddress: valAddr}, nil)) +} + +func TestCheckMsg_ValidatorManagementBlockedUnconditionally(t *testing.T) { + ctx := sdk.Context{} + // Validator creation and unjailing are a pure PoA invariant: they are rejected regardless + // of the enforcement flag, including when no vault is configured (notEnforced) or the policy + // is nil. This closes the migration-window / misconfiguration hole. + for _, policy := range []stakingguard.StakingPolicyFunc{allowVault, allowNone, notEnforced, nil} { + require.ErrorIs(t, stakingguard.CheckMsg(ctx, &stakingtypes.MsgCreateValidator{ValidatorAddress: valAddr}, policy), stakingguard.ErrValidatorCreationDisabled) + require.ErrorIs(t, stakingguard.CheckMsg(ctx, &slashingtypes.MsgUnjail{ValidatorAddr: valAddr}, policy), stakingguard.ErrUnjailDisabled) + } +} diff --git a/tests/integration/x/vm/test_flash_loan_exploit.go b/tests/integration/x/vm/test_flash_loan_exploit.go index 98bfdb8c..cd3288fc 100644 --- a/tests/integration/x/vm/test_flash_loan_exploit.go +++ b/tests/integration/x/vm/test_flash_loan_exploit.go @@ -14,7 +14,9 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -// TestFlashLoanExploit runs the two flash loan methods +// TestFlashLoanExploit runs the two flash loan methods. This suite configures no community-pool +// vault, so the PoA staking guard is inactive and the staking precompile behaves as upstream +// (guard enforcement is covered separately by the x/vm StakedBondVault + stakingguard tests). func (s *NestedEVMExtensionCallSuite) TestFlashLoanExploit() { testCases := []struct { method string diff --git a/tests/integration/x/vm/test_scheduler.go b/tests/integration/x/vm/test_scheduler.go index 61237ce4..bad20047 100644 --- a/tests/integration/x/vm/test_scheduler.go +++ b/tests/integration/x/vm/test_scheduler.go @@ -4,8 +4,10 @@ import ( "math/big" "strings" + "github.com/ethereum/go-ethereum/common" + "github.com/cosmos/evm/contracts" - testcontracts "github.com/cosmos/evm/precompiles/testutil/contracts" + testconstants "github.com/cosmos/evm/testutil/constants" testutiltypes "github.com/cosmos/evm/testutil/types" erc20types "github.com/cosmos/evm/x/erc20/types" evmtypes "github.com/cosmos/evm/x/vm/types" @@ -20,7 +22,7 @@ func (s *KeeperTestSuite) TestEVMSchedulerPokesConfiguredVault() { evmtypes.EvmTxArgs{}, testutiltypes.ContractDeploymentData{ Contract: contracts.StakedBondVaultContract, - ConstructorArgs: []interface{}{admin, "Staked Bond", "stBOND"}, + ConstructorArgs: []interface{}{admin, common.HexToAddress(testconstants.WEVMOSContractMainnet), "Staked Bond", "stBOND"}, }, ) s.Require().NoError(err) @@ -31,7 +33,7 @@ func (s *KeeperTestSuite) TestEVMSchedulerPokesConfiguredVault() { Enabled: true, TargetContract: vaultAddr.Hex(), GasCap: 1_000_000, - MaxOps: 7, + MaxOps: evmtypes.SchedulerPokeStepCount, CadenceBlocks: 1, } s.Require().NoError(s.Network.App.GetEVMKeeper().SetParams(s.Network.GetContext(), params)) @@ -53,44 +55,49 @@ func (s *KeeperTestSuite) TestEVMSchedulerPokesConfiguredVault() { s.Require().Equal(0, big.NewInt(1).Cmp(out[0].(*big.Int)), "events: %v", s.Network.GetContext().EventManager().Events()) } +// TestEVMSchedulerRevertDoesNotHaltBlock exercises an actual reverting poke: the vault is +// deployed with its poke paused, so the scheduler's poke(uint256) call reverts. The scheduler +// should catch that revert, emit success=false, and leave the vault's pokeCount untouched, +// without halting the block. func (s *KeeperTestSuite) TestEVMSchedulerRevertDoesNotHaltBlock() { s.SetupTest() - counter, err := testcontracts.LoadCounterContract() - s.Require().NoError(err) - counterAddr, err := s.Factory.DeployContract( - s.Keyring.GetPrivKey(0), - evmtypes.EvmTxArgs{}, - testutiltypes.ContractDeploymentData{Contract: counter}, - ) + vault := s.deployStakedBondVault() + + // Pause the vault's poke so any scheduler poke reverts with "P_PAUSED". + _, err := s.executeVaultTx(s.Keyring.GetPrivKey(0), vault, nil, "setPaused", false, false, true) s.Require().NoError(err) - s.Require().NoError(s.Network.NextBlock()) - params := s.Network.App.GetEVMKeeper().GetParams(s.Network.GetContext()) + ctx := s.Network.GetContext() + params := s.Network.App.GetEVMKeeper().GetParams(ctx) params.Scheduler = evmtypes.EVMSchedulerParams{ Enabled: true, - TargetContract: counterAddr.Hex(), - GasCap: 500_000, - MaxOps: 1, + TargetContract: vault.Hex(), + GasCap: 1_000_000, + MaxOps: evmtypes.SchedulerPokeStepCount, CadenceBlocks: 1, } - s.Require().NoError(s.Network.App.GetEVMKeeper().SetParams(s.Network.GetContext(), params)) - s.Network.App.GetEVMKeeper().RunScheduler(s.Network.GetContext()) + s.Require().NoError(s.Network.App.GetEVMKeeper().SetParams(ctx, params)) - data, err := counter.ABI.Pack("getCounter") - s.Require().NoError(err) - res, err := s.Network.App.GetEVMKeeper().CallEVMWithData( - s.Network.GetContext(), - erc20types.ModuleAddress, - &counterAddr, - data, - false, - nil, - ) - s.Require().NoError(err) - out, err := counter.ABI.Unpack("getCounter", res.Ret) - s.Require().NoError(err) - s.Require().Equal(0, big.NewInt(0).Cmp(out[0].(*big.Int))) + // The poke reverts inside RunScheduler; it should be caught rather than halting the block. + s.Require().NotPanics(func() { + s.Network.App.GetEVMKeeper().RunScheduler(ctx) + }) + + // The scheduler should have recorded a failed poke. + attrs := map[string]string{} + for _, event := range ctx.EventManager().Events() { + if event.Type != "evm_scheduler" { + continue + } + for _, attr := range event.Attributes { + attrs[attr.Key] = attr.Value + } + } + s.Require().Equal("false", attrs["success"], "events: %v", ctx.EventManager().Events()) + + // pokeCount should still be 0, since the reverting poke committed nothing. + s.Require().Zero(s.vaultBig(vault, "pokeCount").Sign()) } func (s *KeeperTestSuite) TestEVMSchedulerCorruptParamsDoNotHaltBlock() { diff --git a/tests/integration/x/vm/test_staked_bond_vault.go b/tests/integration/x/vm/test_staked_bond_vault.go index 3e6f709a..ecc19899 100644 --- a/tests/integration/x/vm/test_staked_bond_vault.go +++ b/tests/integration/x/vm/test_staked_bond_vault.go @@ -7,6 +7,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/cosmos/evm/contracts" + stakingprecompile "github.com/cosmos/evm/precompiles/staking" testconstants "github.com/cosmos/evm/testutil/constants" "github.com/cosmos/evm/testutil/types" erc20types "github.com/cosmos/evm/x/erc20/types" @@ -194,7 +195,217 @@ func (s *KeeperTestSuite) TestStakedBondVaultRejectsUnauthorizedAdminActions() { _, err = s.executeVaultTx(s.Keyring.GetPrivKey(1), vault, nil, "setValidatorPolicy", uint8(1)) s.Require().Error(err) s.Require().NoError(s.Network.NextBlock()) - s.Require().Equal(0, big.NewInt(8).Cmp(s.vaultBig(vault, "targetValidatorCount"))) + // Default targetValidatorCount now covers the full active set (MAX_VALIDATORS=32) so stake + // spreads across all validators; the rejected setValidatorPolicy(1) should not have changed it. + s.Require().Equal(0, big.NewInt(32).Cmp(s.vaultBig(vault, "targetValidatorCount"))) +} + +// TestStakedBondVaultSettleWorksWhilePokePaused checks that pausing the scheduler (pokePaused) +// doesn't strand withdrawals: the permissionless settle() runs settlement on its own. +func (s *KeeperTestSuite) TestStakedBondVaultSettleWorksWhilePokePaused() { + s.SetupTest() + vault := s.deployStakedBondVault() + owner := s.Keyring.GetAddr(0) + depositAmount := big.NewInt(0).Mul(big.NewInt(5), big.NewInt(1e18)) + + depositRes, err := s.executeVaultTx(s.Keyring.GetPrivKey(0), vault, depositAmount, "depositNative", owner) + s.Require().NoError(err) + depositOut, err := contracts.StakedBondVaultContract.ABI.Unpack("depositNative", depositRes.Ret) + s.Require().NoError(err) + shares := depositOut[0].(*big.Int) + + requestRes, err := s.executeVaultTx(s.Keyring.GetPrivKey(0), vault, nil, "requestRedeem", shares, owner, owner) + s.Require().NoError(err) + requestOut, err := contracts.StakedBondVaultContract.ABI.Unpack("requestRedeem", requestRes.Ret) + s.Require().NoError(err) + requestID := requestOut[0].(*big.Int) + + // Pause the scheduler poke. + _, err = s.executeVaultTx(s.Keyring.GetPrivKey(0), vault, nil, "setPaused", false, false, true) + s.Require().NoError(err) + _, err = s.executeVaultTx(s.Keyring.GetPrivKey(0), vault, nil, "poke", big.NewInt(6)) + s.Require().Error(err, "poke must revert while pokePaused") + s.Require().NoError(s.Network.NextBlock()) + + // settle() bypasses pokePaused and makes the batch claimable. + _, err = s.executeVaultTx(s.Keyring.GetPrivKey(0), vault, nil, "settle", big.NewInt(6)) + s.Require().NoError(err) + s.Require().NoError(s.Network.NextBlock()) + + _, err = s.executeVaultTx(s.Keyring.GetPrivKey(0), vault, nil, "claimRedeem", requestID, zeroAddress) + s.Require().NoError(err, "claims must succeed even while the scheduler is paused") + s.Require().Zero(s.vaultBig(vault, "totalSupply").Sign()) +} + +// TestStakedBondVaultPokeStepIsSelfOnly checks that pokeStep is only callable by the contract +// itself, through the internal self-call, and never externally. +func (s *KeeperTestSuite) TestStakedBondVaultPokeStepIsSelfOnly() { + s.SetupTest() + vault := s.deployStakedBondVault() + _, err := s.executeVaultTx(s.Keyring.GetPrivKey(0), vault, nil, "pokeStep", uint8(1)) + s.Require().Error(err, "external pokeStep call must revert (SELF)") +} + +// TestStakedBondVaultPerRecipientPayouts checks that two redeemers in one batch each receive +// assets in proportion to their shares (3:2), not just a correct combined total. +func (s *KeeperTestSuite) TestStakedBondVaultPerRecipientPayouts() { + s.SetupTest() + vault := s.deployStakedBondVault() + ownerA := s.Keyring.GetAddr(0) + ownerB := s.Keyring.GetAddr(1) + amountA := big.NewInt(0).Mul(big.NewInt(3), big.NewInt(1e18)) + amountB := big.NewInt(0).Mul(big.NewInt(2), big.NewInt(1e18)) + + depA, err := s.executeVaultTx(s.Keyring.GetPrivKey(0), vault, amountA, "depositNative", ownerA) + s.Require().NoError(err) + sharesA := s.unpackBig(depA, "depositNative") + depB, err := s.executeVaultTx(s.Keyring.GetPrivKey(1), vault, amountB, "depositNative", ownerB) + s.Require().NoError(err) + sharesB := s.unpackBig(depB, "depositNative") + + reqA, err := s.executeVaultTx(s.Keyring.GetPrivKey(0), vault, nil, "requestRedeem", sharesA, ownerA, ownerA) + s.Require().NoError(err) + idA := s.unpackBig(reqA, "requestRedeem") + reqB, err := s.executeVaultTx(s.Keyring.GetPrivKey(1), vault, nil, "requestRedeem", sharesB, ownerB, ownerB) + s.Require().NoError(err) + idB := s.unpackBig(reqB, "requestRedeem") + + _, err = s.executeVaultTx(s.Keyring.GetPrivKey(0), vault, nil, "settle", big.NewInt(6)) + s.Require().NoError(err) + s.Require().NoError(s.Network.NextBlock()) + + claimA, err := s.executeVaultTx(s.Keyring.GetPrivKey(0), vault, nil, "claimRedeem", idA, zeroAddress) + s.Require().NoError(err) + payoutA := s.unpackBig(claimA, "claimRedeem") + claimB, err := s.executeVaultTx(s.Keyring.GetPrivKey(1), vault, nil, "claimRedeem", idB, zeroAddress) + s.Require().NoError(err) + payoutB := s.unpackBig(claimB, "claimRedeem") + + // Each redeemer gets their own deposit back (liquid batch, 1:1 NAV); the payouts aren't swapped. + s.Require().Equal(0, payoutA.Cmp(amountA), "ownerA payout %s != %s", payoutA, amountA) + s.Require().Equal(0, payoutB.Cmp(amountB), "ownerB payout %s != %s", payoutB, amountB) +} + +func (s *KeeperTestSuite) unpackBig(res *evmtypes.MsgEthereumTxResponse, method string) *big.Int { + out, err := contracts.StakedBondVaultContract.ABI.Unpack(method, res.Ret) + s.Require().NoError(err) + return out[0].(*big.Int) +} + +// TestStakedBondVaultDepositPricedAfterSlash checks that once a slash pushes NAV below +// totalSupply, a fresh deposit is priced at the diverged share price (more shares per token) +// rather than a naive 1:1. +func (s *KeeperTestSuite) TestStakedBondVaultDepositPricedAfterSlash() { + s.SetupTest() + vault := s.deployStakedBondVault() + owner := s.Keyring.GetAddr(0) + depositAmount := big.NewInt(0).Mul(big.NewInt(4), big.NewInt(1e18)) + + _, err := s.executeVaultTx(s.Keyring.GetPrivKey(0), vault, nil, "setValidatorPolicy", uint8(1)) + s.Require().NoError(err) + dep1, err := s.executeVaultTx(s.Keyring.GetPrivKey(0), vault, depositAmount, "depositNative", owner) + s.Require().NoError(err) + shares1 := s.unpackBig(dep1, "depositNative") + s.Require().Equal(0, shares1.Cmp(depositAmount), "first deposit is 1:1") + s.stakeVaultIdle(vault, depositAmount) + + // Slash the sole validator 50%, so NAV halves while supply stays the same. + validator := s.selectedVaultSDKValidator(vault) + consAddr, err := validator.GetConsAddr() + s.Require().NoError(err) + powerReduction := s.Network.App.GetStakingKeeper().PowerReduction(s.Network.GetContext()) + power := validator.GetConsensusPower(powerReduction) + _, err = s.Network.App.GetStakingKeeper().Slash( + s.Network.GetContext(), sdk.ConsAddress(consAddr), + s.Network.GetContext().BlockHeight(), power, sdkmath.LegacyNewDecWithPrec(5, 1)) + s.Require().NoError(err) + + // NAV should have dropped below supply now that the slash is visible, so share price < 1. + navAfter := s.vaultBig(vault, "totalAssets") + s.Require().True(navAfter.Cmp(depositAmount) < 0, "slash should reduce NAV: %s !< %s", navAfter, depositAmount) + + // ERC4626 pricing should reflect the diverged NAV: a fresh deposit is quoted more shares + // than tokens (share price < 1), not a naive 1:1. + quoted := s.vaultBig(vault, "convertToShares", depositAmount) + s.Require().True(quoted.Cmp(depositAmount) > 0, "post-slash deposit must be priced >1:1, got %s shares for %s", quoted, depositAmount) +} + +// TestStakedBondVaultSlashBufferReservesIdle checks that with a non-zero slash buffer the vault +// keeps roughly bufferBps of NAV idle (un-delegated) instead of staking everything. +func (s *KeeperTestSuite) TestStakedBondVaultSlashBufferReservesIdle() { + s.SetupTest() + + vault := s.deployStakedBondVault() + owner := s.Keyring.GetAddr(0) + depositAmount := big.NewInt(0).Mul(big.NewInt(1000), big.NewInt(1e18)) + + // 5% slash-exposure buffer. + _, err := s.executeVaultTx(s.Keyring.GetPrivKey(0), vault, nil, "setSlashBufferBps", big.NewInt(500)) + s.Require().NoError(err) + s.Require().Equal(0, big.NewInt(500).Cmp(s.vaultBig(vault, "slashBufferBps"))) + + _, err = s.executeVaultTx(s.Keyring.GetPrivKey(0), vault, depositAmount, "depositNative", owner) + s.Require().NoError(err) + for i := 0; i < 24; i++ { + _, err = s.executeVaultTx(s.Keyring.GetPrivKey(0), vault, nil, "poke", big.NewInt(6)) + s.Require().NoError(err) + } + + idle := s.vaultBig(vault, "totalIdleLiquid") + delegated := s.vaultBig(vault, "totalDelegated") + nav := new(big.Int).Add(idle, delegated) + expectedBuffer := new(big.Int).Div(new(big.Int).Mul(nav, big.NewInt(500)), big.NewInt(10000)) + + // The buffer is never staked, so idle >= buffer; staking converges close to it. + s.Require().True(idle.Cmp(expectedBuffer) >= 0, "idle %s below buffer %s", idle, expectedBuffer) + overBuffer := new(big.Int).Sub(idle, expectedBuffer) + s.Require().True(overBuffer.Cmp(big.NewInt(1e18)) < 0, "idle %s far above buffer %s", idle, expectedBuffer) + s.Require().True(delegated.Cmp(nav) < 0, "buffer must leave some NAV un-delegated") +} + +// TestStakedBondVaultVoteAbstainAccessControl checks that only the operator may call +// voteAbstain, and that it tolerates ids with no live proposal (each id's failure is swallowed). +func (s *KeeperTestSuite) TestStakedBondVaultVoteAbstainAccessControl() { + s.SetupTest() + vault := s.deployStakedBondVault() + + // Non-operator is rejected. + _, err := s.executeVaultTx(s.Keyring.GetPrivKey(1), vault, nil, "voteAbstain", []uint64{1}) + s.Require().Error(err) + + // The operator call succeeds even when the proposal id isn't in a voting period; each id's + // failure is swallowed so a stale id can't fail the whole call. + _, err = s.executeVaultTx(s.Keyring.GetPrivKey(0), vault, nil, "voteAbstain", []uint64{1}) + s.Require().NoError(err) +} + +// TestStakingGuardBlocksNonVaultDelegationViaPrecompile checks the PoA staking guard on the +// EVM path: a non-allowlisted EOA can't delegate through the staking precompile, so the +// validator set can't be captured from the EVM. +func (s *KeeperTestSuite) TestStakingGuardBlocksNonVaultDelegationViaPrecompile() { + s.SetupTest() + + // Deploy the vault so the scheduler target (the guard's allowlist) is populated. + s.deployStakedBondVault() + + vals := s.Network.GetValidators() + s.Require().NotEmpty(vals) + valoper := vals[0].OperatorAddress + + // A non-vault EOA (keyring[1]) attempts to delegate via the staking precompile. + stranger := s.Keyring.GetAddr(1) + stakingAddr := common.HexToAddress(evmtypes.StakingPrecompileAddress) + _, err := s.Factory.ExecuteContractCall( + s.Keyring.GetPrivKey(1), + evmtypes.EvmTxArgs{To: &stakingAddr, GasLimit: 2_000_000}, + types.CallArgs{ + ContractABI: stakingprecompile.ABI, + MethodName: "delegate", + Args: []interface{}{stranger, valoper, big.NewInt(1e18)}, + }, + ) + s.Require().Error(err, "non-vault delegation via the staking precompile must be rejected by the PoA guard") + s.Require().NoError(s.Network.NextBlock()) } func (s *KeeperTestSuite) TestStakedBondVaultSetPausedTogglesExactFlags() { @@ -416,7 +627,10 @@ func (s *KeeperTestSuite) TestStakedBondVaultStakeIdleAcrossAutomaticallySelecte delegatedAcrossSelected.Add(delegatedAcrossSelected, out[2].(*big.Int)) } s.requireVaultDelegatedClose(vault, depositAmount) - s.Require().Equal(0, depositAmount.Cmp(delegatedAcrossSelected)) + // Equal-spread across the 3 selected validators covers ~the full deposit; a small + // rounding dust (< MIN_OPERATION_ASSETS) may remain idle under the per-validator cap. + dust := new(big.Int).Sub(depositAmount, delegatedAcrossSelected) + s.Require().True(dust.Sign() >= 0 && dust.Cmp(big.NewInt(1e15)) < 0, "unexpected spread shortfall: %s", dust) } func (s *KeeperTestSuite) TestStakedBondVaultCanUpdateValidatorPolicyWhileDelegated() { @@ -731,6 +945,54 @@ func (s *KeeperTestSuite) TestStakedBondVaultSlashedStakeCanRedeemRemainingAsset s.Require().Zero(s.vaultBig(vault, "totalWithdrawalUnbonding").Sign()) } +// TestStakedBondVaultFullySlashedBatchStillClaimable checks that a withdrawal batch whose +// unbonding proceeds are entirely slashed away is still marked claimable, settled at its +// liquid portion, instead of being stranded. +func (s *KeeperTestSuite) TestStakedBondVaultFullySlashedBatchStillClaimable() { + s.SetupTest() + + vault := s.deployStakedBondVault() + owner := s.Keyring.GetAddr(0) + depositAmount := big.NewInt(0).Mul(big.NewInt(3), big.NewInt(1e18)) + + _, err := s.executeVaultTx(s.Keyring.GetPrivKey(0), vault, nil, "setValidatorPolicy", uint8(1)) + s.Require().NoError(err) + _, err = s.executeVaultTx(s.Keyring.GetPrivKey(0), vault, depositAmount, "depositNative", owner) + s.Require().NoError(err) + s.stakeVaultIdle(vault, depositAmount) + + // Request full redemption and undelegate it. + requestRes, err := s.executeVaultTx(s.Keyring.GetPrivKey(0), vault, nil, "requestRedeem", depositAmount, owner, owner) + s.Require().NoError(err) + requestOut, err := contracts.StakedBondVaultContract.ABI.Unpack("requestRedeem", requestRes.Ret) + s.Require().NoError(err) + requestID := requestOut[0].(*big.Int) + validator := s.selectedVaultSDKValidator(vault) + _, err = s.executeVaultTx(s.Keyring.GetPrivKey(0), vault, nil, "poke", big.NewInt(6)) + s.Require().NoError(err) + + // Slash the validator 100% while the batch is unbonding, so proceeds go to zero. + consAddr, err := validator.GetConsAddr() + s.Require().NoError(err) + powerReduction := s.Network.App.GetStakingKeeper().PowerReduction(s.Network.GetContext()) + power := validator.GetConsensusPower(powerReduction) + _, err = s.Network.App.GetStakingKeeper().Slash( + s.Network.GetContext(), + sdk.ConsAddress(consAddr), + s.Network.GetContext().BlockHeight(), + power, + sdkmath.LegacyOneDec(), + ) + s.Require().NoError(err) + + // Settle and claim: the batch should be claimable, not stranded, despite the total loss. + s.waitAndSettleVaultBatch(vault, 1) + _, err = s.executeVaultTx(s.Keyring.GetPrivKey(0), vault, nil, "claimRedeem", requestID, zeroAddress) + s.Require().NoError(err, "fully-slashed batch must still be claimable, not permanently stranded") + s.Require().Zero(s.vaultBig(vault, "totalSupply").Sign()) + s.Require().Zero(s.vaultBig(vault, "totalWithdrawalUnbonding").Sign()) +} + func (s *KeeperTestSuite) TestStakedBondVaultSlashSyncUpdatesCachedDelegations() { s.SetupTest() @@ -827,11 +1089,27 @@ func (s *KeeperTestSuite) deployStakedBondVault() common.Address { s.Keyring.GetPrivKey(0), evmtypes.EvmTxArgs{GasLimit: 8_000_000}, types.ContractDeploymentData{ - Contract: contracts.StakedBondVaultContract, - ConstructorArgs: []interface{}{admin, "Staked Bond", "stBOND"}, + Contract: contracts.StakedBondVaultContract, + // The test genesis registers the native ERC20 at WEVMOSContractMainnet (0xD494...), + // so the vault's asset points there; production and local deploys use 0xEeee...EEeE. + ConstructorArgs: []interface{}{admin, common.HexToAddress(testconstants.WEVMOSContractMainnet), "Staked Bond", "stBOND"}, }, ) s.Require().NoError(err) + + // Authorize the deployed vault as the sole staking-guard delegator by pointing the EVM + // scheduler's target contract at it, mirroring the production deploy step. The deploy tx + // above ran in the live finalize-block state without committing, so write the param into + // that same state (via a context over it) and let the NextBlock below commit the deploy and + // the param together. Otherwise the guard fails closed and rejects the vault's own + // delegations during later poke txs. + baseApp := s.Network.App.GetBaseApp() + liveCtx := baseApp.NewContextLegacy(false, s.Network.GetContext().BlockHeader()) + evmKeeper := s.Network.App.GetEVMKeeper() + params := evmKeeper.GetParams(liveCtx) + params.Scheduler.TargetContract = vault.Hex() + s.Require().NoError(evmKeeper.SetParams(liveCtx, params)) + s.Require().NoError(s.Network.NextBlock()) return vault } diff --git a/x/svip/genesis.go b/x/svip/genesis.go index cc7346c9..e23f0510 100644 --- a/x/svip/genesis.go +++ b/x/svip/genesis.go @@ -6,6 +6,7 @@ import ( "github.com/cosmos/evm/x/svip/types" errorsmod "cosmossdk.io/errors" + sdkmath "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -33,6 +34,20 @@ func InitGenesis(ctx sdk.Context, k keeper.Keeper, data types.GenesisState) []ab if data.PoolBalanceAtActivation.IsPositive() { k.SetPoolBalanceAtActivation(ctx, data.PoolBalanceAtActivation) } + + // Rebuild the decay-curve state, which lives in the KV store rather than the genesis + // proto. A fresh chain is not activated, so there is nothing to rebuild. On export/import + // of a running chain, d comes back exactly from the half-life, and S re-anchors to the + // live pool balance (bank inits before svip). That balance already reflects how far the + // curve has decayed, so mid-decay the re-anchor only drifts by truncation dust. + if data.Activated && data.Params.HalfLifeSeconds > 0 { + d, err := keeper.ComputeDecayFactor(data.Params.HalfLifeSeconds) + if err != nil { + panic(errorsmod.Wrap(err, "could not compute svip decay factor at genesis")) + } + k.SetDecayFactor(ctx, d) + k.SetScheduledRemaining(ctx, sdkmath.LegacyNewDecFromInt(k.GetLivePoolBalance(ctx))) + } return []abci.ValidatorUpdate{} } diff --git a/x/svip/keeper/abci.go b/x/svip/keeper/abci.go index 742bd064..ea70b1c9 100644 --- a/x/svip/keeper/abci.go +++ b/x/svip/keeper/abci.go @@ -1,69 +1,82 @@ package keeper import ( + "time" + "github.com/cosmos/evm/x/svip/types" + sdkmath "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" ) -// BeginBlock distributes decayed rewards to FeeCollector. +// BeginBlock distributes decayed rewards to the fee collector. +// +// The math is all integer/fixed-point so every node computes the same result. Expected +// failures (corrupt decay state, a failed transfer) are logged and skipped for the block +// instead of returned, since a non-nil error from BeginBlock halts the chain. func (k Keeper) BeginBlock(ctx sdk.Context) error { - // 1. Guard: skip if not active + // Skip if not active. if !k.GetActivated(ctx) || k.GetPaused(ctx) { return nil } - params := k.GetParams(ctx) - - // 2. Time context + // Whole seconds elapsed since the last processed block. now := ctx.BlockTime() - activationTime := k.GetActivationTime(ctx) lastBlockTime := k.GetLastBlockTime(ctx) - - totalPausedSec := float64(k.GetTotalPausedSeconds(ctx)) - totalElapsed := now.Sub(activationTime).Seconds() - totalPausedSec - blockDelta := now.Sub(lastBlockTime).Seconds() - - if totalElapsed <= 0 || blockDelta <= 0 { + blockDelta := now.Unix() - lastBlockTime.Unix() + if blockDelta <= 0 { + // Sub-second or non-advancing block: keep the accrual, leave the anchor alone. return nil } - // 3. Calculate reward (exponential decay, hardcoded) - poolAtActivation := k.GetPoolBalanceAtActivation(ctx) - reward := CalculateBlockReward(params.HalfLifeSeconds, poolAtActivation, totalElapsed, blockDelta) - - if reward.IsZero() || reward.IsNegative() { - k.SetLastBlockTime(ctx, now) + // Load the decay state. A read error means corrupt state, so skip rather than halt. + scheduledRemaining, err := k.GetScheduledRemaining(ctx) + if err != nil { + k.Logger(ctx).Error("svip: failed to read scheduled remaining; skipping emission", "error", err) + return nil + } + decayFactor, err := k.GetDecayFactor(ctx) + if err != nil { + k.Logger(ctx).Error("svip: failed to read decay factor; skipping emission", "error", err) return nil } - // 4. Cap at actual pool balance + reward, newS := CalculateBlockReward(scheduledRemaining, decayFactor, blockDelta) + + // Never distribute more than the pool actually holds. poolBalance := k.getPoolBalance(ctx) if poolBalance.LT(reward) { reward = poolBalance } - if reward.IsZero() { - k.SetLastBlockTime(ctx, now) + + if !reward.IsPositive() { + // Nothing to pay this block, but still advance the curve and the anchor. + k.SetScheduledRemaining(ctx, k.clampToPool(ctx, newS)) + k.advanceAnchor(ctx, lastBlockTime, blockDelta) return nil } - // 5. Transfer: svip → fee_collector + // Send the emission to the fee collector, where x/distribution splits it across + // validators and delegators by consensus power in the same block. That is deliberate: + // this inflation is a staking reward and follows the same fee_collector path the mint + // module uses. Reordering the BeginBlocker wouldn't change it (the fee_collector balance + // carries across blocks), and paying a different recipient would mean changing this target. denom := k.getDenom(ctx) coins := sdk.NewCoins(sdk.NewCoin(denom, reward)) - err := k.bk.SendCoinsFromModuleToModule( - ctx, - types.ModuleName, - authtypes.FeeCollectorName, - coins, - ) - if err != nil { - return err + if err := k.bk.SendCoinsFromModuleToModule( + ctx, types.ModuleName, authtypes.FeeCollectorName, coins, + ); err != nil { + // Skip without advancing the curve or anchor, so the reward is deferred rather than + // burned and the next block retries it. + k.Logger(ctx).Error("svip: reward transfer failed; skipping emission", "error", err) + return nil } - // 6. Bookkeeping + events + // Only persist the decayed curve after the send succeeds, then bookkeep. + k.SetScheduledRemaining(ctx, k.clampToPool(ctx, newS)) k.AddTotalDistributed(ctx, reward) - k.SetLastBlockTime(ctx, now) + k.advanceAnchor(ctx, lastBlockTime, blockDelta) ctx.EventManager().EmitEvent(sdk.NewEvent( "svip_reward", @@ -73,3 +86,20 @@ func (k Keeper) BeginBlock(ctx sdk.Context) error { return nil } + +// clampToPool holds the scheduled remaining at or below the live pool balance, keeping +// the S <= pool invariant after capping and truncation dust. +func (k Keeper) clampToPool(ctx sdk.Context, s sdkmath.LegacyDec) sdkmath.LegacyDec { + poolDec := sdkmath.LegacyNewDecFromInt(k.getPoolBalance(ctx)) + if s.GT(poolDec) { + return poolDec + } + return s +} + +// advanceAnchor moves LastBlockTime forward by exactly the whole seconds consumed rather +// than to now, so any sub-second remainder carries into the next block and fast blocks +// don't lose accrual. +func (k Keeper) advanceAnchor(ctx sdk.Context, lastBlockTime time.Time, blockDelta int64) { + k.SetLastBlockTime(ctx, lastBlockTime.Add(time.Duration(blockDelta)*time.Second)) +} diff --git a/x/svip/keeper/keeper.go b/x/svip/keeper/keeper.go index be652cbf..ff718e3a 100644 --- a/x/svip/keeper/keeper.go +++ b/x/svip/keeper/keeper.go @@ -1,6 +1,7 @@ package keeper import ( + "fmt" "time" "github.com/cosmos/evm/x/svip/types" @@ -209,12 +210,84 @@ func (k Keeper) SetPaused(ctx sdk.Context, paused bool) { store.Delete(types.PausedKey) } +// getDec reads a LegacyDec from the store at key. An absent value returns zero; a value that +// won't unmarshal returns the error for the caller to handle. The block path reads this every +// block and skips emission on error rather than halting the chain. +func (k Keeper) getDec(ctx sdk.Context, key []byte) (sdkmath.LegacyDec, error) { + bz := ctx.KVStore(k.storeKey).Get(key) + if bz == nil { + return sdkmath.LegacyZeroDec(), nil + } + var d sdkmath.LegacyDec + if err := d.Unmarshal(bz); err != nil { + return sdkmath.LegacyZeroDec(), err + } + return d, nil +} + +// setDec stores a LegacyDec at key. +func (k Keeper) setDec(ctx sdk.Context, key []byte, d sdkmath.LegacyDec) { + bz, err := d.Marshal() + if err != nil { + panic(err) + } + ctx.KVStore(k.storeKey).Set(key, bz) +} + +// GetScheduledRemaining returns the scheduled remaining pool S on the decay curve. +func (k Keeper) GetScheduledRemaining(ctx sdk.Context) (sdkmath.LegacyDec, error) { + return k.getDec(ctx, types.ScheduledRemainingKey) +} + +// SetScheduledRemaining stores the scheduled remaining pool S. +func (k Keeper) SetScheduledRemaining(ctx sdk.Context, s sdkmath.LegacyDec) { + k.setDec(ctx, types.ScheduledRemainingKey, s) +} + +// GetDecayFactor returns the per-second decay factor d. +func (k Keeper) GetDecayFactor(ctx sdk.Context) (sdkmath.LegacyDec, error) { + return k.getDec(ctx, types.DecayFactorKey) +} + +// SetDecayFactor stores the per-second decay factor d. +func (k Keeper) SetDecayFactor(ctx sdk.Context, d sdkmath.LegacyDec) { + k.setDec(ctx, types.DecayFactorKey, d) +} + +// ComputeDecayFactor returns the per-second decay factor d = 0.5^(1/halfLife), the +// halfLife-th root of 0.5. It uses LegacyDec.ApproxRoot (big.Int arithmetic) so the result +// is the same on every architecture. Call it only outside the block path, at activation or +// on a param change, since ApproxRoot is comparatively expensive. +func ComputeDecayFactor(halfLifeSeconds int64) (sdkmath.LegacyDec, error) { + if halfLifeSeconds <= 0 { + return sdkmath.LegacyZeroDec(), fmt.Errorf("half_life_seconds must be positive: %d", halfLifeSeconds) + } + return sdkmath.LegacyMustNewDecFromStr("0.5").ApproxRoot(uint64(halfLifeSeconds)) +} + +// initDecayCurve seeds the decay-curve state at (re)activation: the decay factor d and the +// scheduled remaining pool S, which starts at the full pool balance. +func (k Keeper) initDecayCurve(ctx sdk.Context, halfLifeSeconds int64, pool sdkmath.Int) error { + d, err := ComputeDecayFactor(halfLifeSeconds) + if err != nil { + return err + } + k.SetDecayFactor(ctx, d) + k.SetScheduledRemaining(ctx, sdkmath.LegacyNewDecFromInt(pool)) + return nil +} + // getPoolBalance reads the live pool balance from x/bank (not from custom state). func (k Keeper) getPoolBalance(ctx sdk.Context) sdkmath.Int { moduleAddr := k.ak.GetModuleAddress(types.ModuleName) return k.bk.GetBalance(ctx, moduleAddr, k.getDenom(ctx)).Amount } +// GetLivePoolBalance exposes the live x/bank pool balance for genesis re-anchoring. +func (k Keeper) GetLivePoolBalance(ctx sdk.Context) sdkmath.Int { + return k.getPoolBalance(ctx) +} + // getDenom returns the native denom at runtime via the EVM module's global config. func (k Keeper) getDenom(_ sdk.Context) string { return evmtypes.GetEVMCoinDenom() diff --git a/x/svip/keeper/keeper_test.go b/x/svip/keeper/keeper_test.go index add652d2..23c1561b 100644 --- a/x/svip/keeper/keeper_test.go +++ b/x/svip/keeper/keeper_test.go @@ -199,12 +199,18 @@ func TestBeginBlock_Distributes(t *testing.T) { td.keeper.SetLastBlockTime(td.ctx, activationTime.Add(95*time.Second)) td.keeper.SetPoolBalanceAtActivation(td.ctx, poolBalance) - // Set block time to 100s after activation + // Seed the decay curve: S starts at the pool balance, d from the half-life. + decayFactor, err := keeper.ComputeDecayFactor(halfLife) + require.NoError(t, err) + td.keeper.SetDecayFactor(td.ctx, decayFactor) + td.keeper.SetScheduledRemaining(td.ctx, sdkmath.LegacyNewDecFromInt(poolBalance)) + + // Set block time to 100s after activation (5s block delta since last block). blockTime := activationTime.Add(100 * time.Second) ctx := td.ctx.WithBlockTime(blockTime) - // Calculate expected reward - reward := keeper.CalculateBlockReward(halfLife, poolBalance, 100, 5) + // Calculate expected reward for the 5-second delta. + reward, _ := keeper.CalculateBlockReward(sdkmath.LegacyNewDecFromInt(poolBalance), decayFactor, 5) require.True(t, reward.IsPositive()) moduleAddr := authtypes.NewModuleAddress(types.ModuleName) @@ -223,11 +229,12 @@ func TestBeginBlock_Distributes(t *testing.T) { // Mock: GetBalance for post-distribution event (pool_remaining) td.bk.On("GetBalance", ctx, moduleAddr, denom).Return(sdk.NewCoin(denom, poolBalance.Sub(reward))) - err := td.keeper.BeginBlock(ctx) + err = td.keeper.BeginBlock(ctx) require.NoError(t, err) // Verify bookkeeping require.Equal(t, reward, td.keeper.GetTotalDistributed(ctx)) + // Anchor advances by exactly the whole-second delta (95s + 5s = 100s = blockTime). require.Equal(t, blockTime, td.keeper.GetLastBlockTime(ctx).UTC()) td.bk.AssertCalled(t, "SendCoinsFromModuleToModule", @@ -253,6 +260,13 @@ func TestBeginBlock_CapsAtPoolBalance(t *testing.T) { td.keeper.SetLastBlockTime(td.ctx, activationTime.Add(95*time.Second)) td.keeper.SetPoolBalanceAtActivation(td.ctx, poolAtActivation) + // Initialize the decay curve so a (large) reward is computed, then capped at the + // tiny live balance. + decayFactor, err := keeper.ComputeDecayFactor(halfLife) + require.NoError(t, err) + td.keeper.SetDecayFactor(td.ctx, decayFactor) + td.keeper.SetScheduledRemaining(td.ctx, sdkmath.LegacyNewDecFromInt(poolAtActivation)) + blockTime := activationTime.Add(100 * time.Second) ctx := td.ctx.WithBlockTime(blockTime) @@ -268,7 +282,7 @@ func TestBeginBlock_CapsAtPoolBalance(t *testing.T) { sdk.NewCoins(sdk.NewCoin(denom, tinyBalance)), ).Return(nil) - err := td.keeper.BeginBlock(ctx) + err = td.keeper.BeginBlock(ctx) require.NoError(t, err) require.Equal(t, tinyBalance, td.keeper.GetTotalDistributed(ctx)) diff --git a/x/svip/keeper/msg_server.go b/x/svip/keeper/msg_server.go index bca76569..315b78d0 100644 --- a/x/svip/keeper/msg_server.go +++ b/x/svip/keeper/msg_server.go @@ -41,21 +41,33 @@ func (s msgServer) UpdateParams(goCtx context.Context, msg *types.MsgUpdateParam } if s.GetActivated(ctx) && current.HalfLifeSeconds > 0 && msg.Params.HalfLifeSeconds > 0 { - // Reject if half_life changes by more than 50% - oldHL := float64(current.HalfLifeSeconds) - newHL := float64(msg.Params.HalfLifeSeconds) - ratio := newHL / oldHL - if ratio < 0.5 || ratio > 1.5 { + // Reject a half-life change of more than 50% in either direction. The division form + // avoids the int64 overflow the cross-multiplied 2*newHL vs 3*oldHL form can hit. + oldHL := current.HalfLifeSeconds + newHL := msg.Params.HalfLifeSeconds + if newHL < oldHL/2 || newHL > oldHL+oldHL/2 { return nil, types.ErrHalfLifeChange } } - if msg.Params.HalfLifeSeconds > 0 && msg.Params.HalfLifeSeconds < 31536000 { - return nil, errorsmod.Wrap(govtypes.ErrInvalidProposalMsg, "half_life_seconds must be >= 1 year (31536000s)") + if msg.Params.HalfLifeSeconds > 0 && + (msg.Params.HalfLifeSeconds < types.MinHalfLifeSeconds || msg.Params.HalfLifeSeconds > types.MaxHalfLifeSeconds) { + return nil, errorsmod.Wrapf(govtypes.ErrInvalidProposalMsg, + "half_life_seconds must be within [%d, %d]", types.MinHalfLifeSeconds, types.MaxHalfLifeSeconds) } if err := s.SetParams(ctx, msg.Params); err != nil { return nil, err } + + // If activated, recompute the decay factor for the new half-life. The scheduled + // remaining S stays as it is, so the curve continues from its current level at the new rate. + if s.GetActivated(ctx) && msg.Params.HalfLifeSeconds > 0 { + d, err := ComputeDecayFactor(msg.Params.HalfLifeSeconds) + if err != nil { + return nil, errorsmod.Wrap(govtypes.ErrInvalidProposalMsg, err.Error()) + } + s.SetDecayFactor(ctx, d) + } return &types.MsgUpdateParamsResponse{}, nil } @@ -82,7 +94,12 @@ func (s msgServer) Activate(goCtx context.Context, msg *types.MsgActivate) (*typ } s.SetPoolBalanceAtActivation(ctx, poolBalance) - // Clean any stale pause state before activating (defense-in-depth). + // Seed the decay curve: S starts at the pool balance, d from the half-life. + if err := s.initDecayCurve(ctx, params.HalfLifeSeconds, poolBalance); err != nil { + return nil, errorsmod.Wrap(govtypes.ErrInvalidProposalMsg, err.Error()) + } + + // Clear any stale pause state before activating. s.SetPaused(ctx, false) s.SetTotalPausedSeconds(ctx, 0) @@ -111,7 +128,7 @@ func (s msgServer) Reactivate(goCtx context.Context, msg *types.MsgReactivate) ( return nil, types.ErrNotYetActivated } - // Defense-in-depth: ensure half_life is valid before restarting the curve. + // Make sure half_life is valid before restarting the curve. params := s.GetParams(ctx) if params.HalfLifeSeconds <= 0 { return nil, errorsmod.Wrap(govtypes.ErrInvalidProposalMsg, "half_life_seconds must be set before reactivation") @@ -126,6 +143,11 @@ func (s msgServer) Reactivate(goCtx context.Context, msg *types.MsgReactivate) ( s.SetActivationTime(ctx, ctx.BlockTime()) s.SetLastBlockTime(ctx, ctx.BlockTime()) + // Restart the decay curve from scratch: S back to the pool balance, d from the half-life. + if err := s.initDecayCurve(ctx, params.HalfLifeSeconds, poolBalance); err != nil { + return nil, errorsmod.Wrap(govtypes.ErrInvalidProposalMsg, err.Error()) + } + // Reset cumulative counters for the new curve s.SetTotalDistributed(ctx, sdkmath.ZeroInt()) s.SetTotalPausedSeconds(ctx, 0) @@ -156,10 +178,12 @@ func (s msgServer) Pause(goCtx context.Context, msg *types.MsgPause) (*types.Msg wasPaused := s.GetPaused(ctx) s.SetPaused(ctx, msg.Paused) - // On unpause: accumulate paused duration + reset LastBlockTime + // On unpause, move the block anchor to now so the paused gap is left out of the next + // block's decay; the curve S is unchanged across a pause. The paused-seconds counter is + // tracked for observability only. if wasPaused && !msg.Paused { lastBlock := s.GetLastBlockTime(ctx) - pausedGap := int64(ctx.BlockTime().Sub(lastBlock).Seconds()) + pausedGap := ctx.BlockTime().Unix() - lastBlock.Unix() if pausedGap > 0 { s.SetTotalPausedSeconds(ctx, s.GetTotalPausedSeconds(ctx)+pausedGap) } diff --git a/x/svip/keeper/msg_server_test.go b/x/svip/keeper/msg_server_test.go index af1b242c..7218a9fa 100644 --- a/x/svip/keeper/msg_server_test.go +++ b/x/svip/keeper/msg_server_test.go @@ -92,7 +92,18 @@ func TestUpdateParams_HalfLifeMinimum(t *testing.T) { Authority: govAuthority(), Params: types.Params{HalfLifeSeconds: 1000}, // < 1 year }) - require.ErrorContains(t, err, "half_life_seconds must be >= 1 year") + require.ErrorContains(t, err, "half_life_seconds must be within") +} + +func TestUpdateParams_HalfLifeMaximum(t *testing.T) { + td := newMockedTestData(t) + srv := keeper.NewMsgServerImpl(td.keeper) + + _, err := srv.UpdateParams(td.ctx, &types.MsgUpdateParams{ + Authority: govAuthority(), + Params: types.Params{HalfLifeSeconds: types.MaxHalfLifeSeconds + 1}, + }) + require.ErrorContains(t, err, "half_life_seconds must be within") } func TestUpdateParams_HappyPath(t *testing.T) { @@ -400,15 +411,25 @@ func TestBeginBlock_AfterPauseUnpause(t *testing.T) { err = td.keeper.BeginBlock(ctxPost) require.NoError(t, err) - // 6. Verify the exact reward distributed matches elapsed=105s (not halfLife+105s) - correctReward := keeper.CalculateBlockReward(halfLife, poolBalance, 105, 5) - buggyReward := keeper.CalculateBlockReward(halfLife, poolBalance, float64(halfLife)+105, 5) + // 6. Check the pause did not decay the curve. The post-unpause 5s block should use the + // scheduled remaining from the pre-pause block (s1, the curve after the first 100s block), + // not one that decayed through the full half-life of the pause. + d, err := keeper.ComputeDecayFactor(halfLife) + require.NoError(t, err) + s0 := sdkmath.LegacyNewDecFromInt(poolBalance) + _, s1 := keeper.CalculateBlockReward(s0, d, 100) // curve after the first 100s block + correctReward, _ := keeper.CalculateBlockReward(s1, d, 5) + + // Counterfactual: if the paused gap had decayed the curve by a half-life, S would be about + // half of s1 and the same 5s block would pay roughly half as much. + _, sBuggy := keeper.CalculateBlockReward(s1, d, halfLife) + buggyReward, _ := keeper.CalculateBlockReward(sBuggy, d, 5) expectedTotal := step2Reward.Add(correctReward) require.Equal(t, expectedTotal, td.keeper.GetTotalDistributed(ctxPost), - "total distributed should match correct elapsed, not buggy elapsed") + "total distributed should reflect the undecayed curve across the pause") - // Sanity: correct reward must be ~2x buggy reward after 1 half-life pause + // Sanity: correct reward must be ~2x the buggy (decayed-through-pause) reward. require.True(t, correctReward.GT(buggyReward), "correct reward (%s) should be ~2x buggy reward (%s)", correctReward, buggyReward) } diff --git a/x/svip/keeper/query_server.go b/x/svip/keeper/query_server.go index d8017896..0cc1d3d4 100644 --- a/x/svip/keeper/query_server.go +++ b/x/svip/keeper/query_server.go @@ -36,17 +36,15 @@ func (s queryServer) PoolState(goCtx context.Context, _ *types.QueryPoolStateReq moduleAddr := s.ak.GetModuleAddress(types.ModuleName) balance := s.bk.GetBalance(ctx, moduleAddr, denom) - var currentRate math.LegacyDec + currentRate := math.LegacyZeroDec() if activated && !paused { - params := s.GetParams(ctx) - totalPausedSec := float64(s.GetTotalPausedSeconds(ctx)) - elapsed := ctx.BlockTime().Sub(s.GetActivationTime(ctx)).Seconds() - totalPausedSec - poolAtAct := s.GetPoolBalanceAtActivation(ctx) - // Calculate tokens per second at current time - reward := CalculateBlockReward(params.HalfLifeSeconds, poolAtAct, elapsed, 1.0) - currentRate = math.LegacyNewDecFromInt(reward) - } else { - currentRate = math.LegacyZeroDec() + // Per-second emission on the current curve: S * (1 - d). + scheduledRemaining, err := s.GetScheduledRemaining(ctx) + decayFactor, err2 := s.GetDecayFactor(ctx) + if err == nil && err2 == nil && + scheduledRemaining.IsPositive() && decayFactor.IsPositive() && decayFactor.LT(math.LegacyOneDec()) { + currentRate = scheduledRemaining.Sub(scheduledRemaining.Mul(decayFactor)) + } } return &types.QueryPoolStateResponse{ diff --git a/x/svip/keeper/reward.go b/x/svip/keeper/reward.go index 6e7cff93..d7b57f08 100644 --- a/x/svip/keeper/reward.go +++ b/x/svip/keeper/reward.go @@ -1,54 +1,40 @@ package keeper import ( - "fmt" - "math" - sdkmath "cosmossdk.io/math" ) -// CalculateBlockReward returns how many tokens (in base denom units) to -// distribute for this block using exponential decay. +// CalculateBlockReward computes this block's reward with discrete geometric decay in +// fixed-point arithmetic, so the result is identical on every node. // -// poolAtActivation — token balance when SVIP was activated (base denom) -// totalElapsedSec — seconds since SVIP activation -// blockDeltaSec — seconds since the previous block +// The curve is tracked as a scheduled remaining pool S. Given the per-second decay factor +// d (precomputed as 0.5^(1/halfLife)) and a delta of dt seconds: // -// Exponential decay formula: +// S_new = S * d^dt +// reward = S - S_new = S * (1 - d^dt) // -// R₀ = (ln2 / half_life) × poolAtActivation -// rate(t) = R₀ × e^(-λ × t) -// blockReward = rate(t) × blockDelta +// Because the steps multiply, N blocks decay S by d^(sum of deltas) exactly the same as one +// block of the summed delta would, so there is no dependence on absolute time and no +// per-block requantization. The reward truncates to integer base units so we never +// over-distribute. Returns the reward and the new scheduled remaining. func CalculateBlockReward( - halfLifeSeconds int64, - poolAtActivation sdkmath.Int, - totalElapsedSec float64, - blockDeltaSec float64, -) sdkmath.Int { - if poolAtActivation.IsZero() || blockDeltaSec <= 0 || totalElapsedSec < 0 { - return sdkmath.ZeroInt() - } - - halfLifeSec := float64(halfLifeSeconds) - if halfLifeSec <= 0 { - return sdkmath.ZeroInt() + scheduledRemaining sdkmath.LegacyDec, + decayFactor sdkmath.LegacyDec, + blockDeltaSec int64, +) (reward sdkmath.Int, newScheduledRemaining sdkmath.LegacyDec) { + // No decay for a non-positive delta, missing/degenerate state, or a factor >= 1. + if blockDeltaSec <= 0 || + scheduledRemaining.IsNil() || !scheduledRemaining.IsPositive() || + decayFactor.IsNil() || !decayFactor.IsPositive() || decayFactor.GTE(sdkmath.LegacyOneDec()) { + if scheduledRemaining.IsNil() { + scheduledRemaining = sdkmath.LegacyZeroDec() + } + return sdkmath.ZeroInt(), scheduledRemaining } - lambda := math.Log(2) / halfLifeSec - poolFloat := poolAtActivation.ToLegacyDec().MustFloat64() - initialRate := lambda * poolFloat // tokens per second at t=0 - currentRate := initialRate * math.Exp(-lambda*totalElapsedSec) // tokens/sec now - blockReward := currentRate * blockDeltaSec - - if blockReward <= 0 { - return sdkmath.ZeroInt() - } - - // Truncate to integer — we never over-distribute. - rewardStr := fmt.Sprintf("%.0f", math.Floor(blockReward)) - reward, ok := sdkmath.NewIntFromString(rewardStr) - if !ok { - return sdkmath.ZeroInt() - } - return reward + // d^dt via LegacyDec.Power (exponentiation by squaring on big.Int), identical on every node. + factor := decayFactor.Power(uint64(blockDeltaSec)) + newScheduledRemaining = scheduledRemaining.Mul(factor) + reward = scheduledRemaining.Sub(newScheduledRemaining).TruncateInt() + return reward, newScheduledRemaining } diff --git a/x/svip/keeper/reward_test.go b/x/svip/keeper/reward_test.go index 1589b12e..25100723 100644 --- a/x/svip/keeper/reward_test.go +++ b/x/svip/keeper/reward_test.go @@ -1,7 +1,6 @@ package keeper_test import ( - "math" "testing" "github.com/cosmos/evm/x/svip/keeper" @@ -10,144 +9,144 @@ import ( sdkmath "cosmossdk.io/math" ) -func TestCalculateBlockReward(t *testing.T) { - halfLife := int64(31536000) // 1 year in seconds - pool := sdkmath.NewInt(1_000_000_000_000) - - testCases := []struct { - name string - halfLifeSeconds int64 - poolAtActivation sdkmath.Int - totalElapsedSec float64 - blockDeltaSec float64 - expectZero bool +const oneYear = int64(31536000) + +// mustDecayFactor is a test helper for the per-second decay factor. +func mustDecayFactor(t *testing.T, halfLife int64) sdkmath.LegacyDec { + t.Helper() + d, err := keeper.ComputeDecayFactor(halfLife) + require.NoError(t, err) + return d +} + +func TestComputeDecayFactor(t *testing.T) { + // Non-positive half-life is an error, not a panic. + _, err := keeper.ComputeDecayFactor(0) + require.Error(t, err) + _, err = keeper.ComputeDecayFactor(-1) + require.Error(t, err) + + // d must be in (0,1) and very close to 1 for a 1-year half-life. + d := mustDecayFactor(t, oneYear) + require.True(t, d.IsPositive()) + require.True(t, d.LT(sdkmath.LegacyOneDec())) + require.True(t, d.GT(sdkmath.LegacyMustNewDecFromStr("0.9999"))) + + // d^halfLife should equal ~0.5 (the defining property). + half := d.Power(uint64(oneYear)) + require.InDelta(t, 0.5, half.MustFloat64(), 1e-6) +} + +func TestCalculateBlockReward_Guards(t *testing.T) { + d := mustDecayFactor(t, oneYear) + pool := sdkmath.LegacyNewDec(1_000_000_000_000) + + cases := []struct { + name string + s sdkmath.LegacyDec + d sdkmath.LegacyDec + delta int64 }{ - { - name: "zero pool balance", - halfLifeSeconds: halfLife, - poolAtActivation: sdkmath.ZeroInt(), - totalElapsedSec: 100, - blockDeltaSec: 5, - expectZero: true, - }, - { - name: "zero block delta", - halfLifeSeconds: halfLife, - poolAtActivation: pool, - totalElapsedSec: 100, - blockDeltaSec: 0, - expectZero: true, - }, - { - name: "negative block delta", - halfLifeSeconds: halfLife, - poolAtActivation: pool, - totalElapsedSec: 100, - blockDeltaSec: -5, - expectZero: true, - }, - { - name: "negative elapsed", - halfLifeSeconds: halfLife, - poolAtActivation: pool, - totalElapsedSec: -100, - blockDeltaSec: 5, - expectZero: true, - }, - { - name: "zero half_life", - halfLifeSeconds: 0, - poolAtActivation: pool, - totalElapsedSec: 100, - blockDeltaSec: 5, - expectZero: true, - }, - { - name: "negative half_life", - halfLifeSeconds: -1, - poolAtActivation: pool, - totalElapsedSec: 100, - blockDeltaSec: 5, - expectZero: true, - }, - { - name: "normal case - first block", - halfLifeSeconds: halfLife, - poolAtActivation: pool, - totalElapsedSec: 5, - blockDeltaSec: 5, - expectZero: false, - }, - { - name: "normal case - mid life", - halfLifeSeconds: halfLife, - poolAtActivation: pool, - totalElapsedSec: 1000, - blockDeltaSec: 5, - expectZero: false, - }, + {"zero scheduled remaining", sdkmath.LegacyZeroDec(), d, 5}, + {"zero delta", pool, d, 0}, + {"negative delta", pool, d, -5}, + {"decay factor >= 1", pool, sdkmath.LegacyOneDec(), 5}, + {"zero decay factor", pool, sdkmath.LegacyZeroDec(), 5}, } - - for _, tc := range testCases { + for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { - reward := keeper.CalculateBlockReward( - tc.halfLifeSeconds, - tc.poolAtActivation, - tc.totalElapsedSec, - tc.blockDeltaSec, - ) - if tc.expectZero { - require.True(t, reward.IsZero(), "expected zero reward, got %s", reward) - } else { - require.True(t, reward.IsPositive(), "expected positive reward, got %s", reward) - } + reward, newS := keeper.CalculateBlockReward(tc.s, tc.d, tc.delta) + require.True(t, reward.IsZero(), "expected zero reward, got %s", reward) + require.True(t, newS.Equal(tc.s), "scheduled remaining must be unchanged on a guarded call") }) } } +func TestCalculateBlockReward_Normal(t *testing.T) { + d := mustDecayFactor(t, oneYear) + s := sdkmath.LegacyNewDec(1_000_000_000_000_000) + + reward, newS := keeper.CalculateBlockReward(s, d, 5) + require.True(t, reward.IsPositive(), "expected positive reward") + require.True(t, newS.LT(s), "scheduled remaining must decrease") + // Reward equals the exact curve decrease (truncated). + require.Equal(t, s.Sub(newS).TruncateInt().String(), reward.String()) +} + +// TestCalculateBlockReward_HalfLifeDecay: after one half-life the per-block rate is ~50%. func TestCalculateBlockReward_HalfLifeDecay(t *testing.T) { - halfLife := int64(31536000) - pool := sdkmath.NewInt(1_000_000_000_000_000) // large pool for precision - blockDelta := float64(5) - - // Reward at t=0 - r0 := keeper.CalculateBlockReward(halfLife, pool, blockDelta, blockDelta) - require.True(t, r0.IsPositive()) - - // Reward at t=halfLife - rHalf := keeper.CalculateBlockReward(halfLife, pool, float64(halfLife), blockDelta) - require.True(t, rHalf.IsPositive()) - - // After one half-life, the rate should be ~half of the initial rate. - // Allow 5% tolerance for truncation. - r0Float := r0.ToLegacyDec().MustFloat64() - rHalfFloat := rHalf.ToLegacyDec().MustFloat64() - ratio := rHalfFloat / r0Float - require.InDelta(t, 0.5, ratio, 0.05, "reward after one half-life should be ~50%% of initial, got ratio %f", ratio) + d := mustDecayFactor(t, oneYear) + s0 := sdkmath.LegacyNewDec(1_000_000_000_000_000) + + r0, _ := keeper.CalculateBlockReward(s0, d, 5) + + // Advance the curve by exactly one half-life, which halves S: S = S * d^halfLife. + sHalf := s0.Mul(d.Power(uint64(oneYear))) + rHalf, _ := keeper.CalculateBlockReward(sHalf, d, 5) + + require.True(t, r0.IsPositive() && rHalf.IsPositive()) + ratio := rHalf.ToLegacyDec().Quo(r0.ToLegacyDec()).MustFloat64() + require.InDelta(t, 0.5, ratio, 0.01, "per-block rate after one half-life should be ~50%%, got %f", ratio) } +// TestCalculateBlockReward_Telescoping: N one-second steps leave S about where a single +// N-second step would. +func TestCalculateBlockReward_Telescoping(t *testing.T) { + d := mustDecayFactor(t, oneYear) + start := sdkmath.LegacyNewDec(1_000_000_000_000_000) + + const n = 600 + // Incremental: n blocks of 1 second. + sInc := start + for range n { + _, sInc = keeper.CalculateBlockReward(sInc, d, 1) + } + // Single step of n seconds. + _, sOne := keeper.CalculateBlockReward(start, d, n) + + rel := sInc.Sub(sOne).Abs().Quo(sOne).MustFloat64() + require.Less(t, rel, 1e-9, "telescoping drift too large: %g", rel) +} + +// TestCalculateBlockReward_VeryLargeElapsed: after 100 half-lives, reward truncates to zero. func TestCalculateBlockReward_VeryLargeElapsed(t *testing.T) { - halfLife := int64(31536000) - pool := sdkmath.NewInt(1_000_000_000_000) + d := mustDecayFactor(t, oneYear) + pool := sdkmath.LegacyNewDec(1_000_000_000_000) - // After 100 half-lives, reward should be effectively zero - elapsed := float64(halfLife) * 100 - reward := keeper.CalculateBlockReward(halfLife, pool, elapsed, 5) + // Decay 100 half-lives, then take a block. + s := pool.Mul(d.Power(uint64(oneYear * 100))) + reward, _ := keeper.CalculateBlockReward(s, d, 5) require.True(t, reward.IsZero(), "reward after 100 half-lives should be zero, got %s", reward) } +// TestCalculateBlockReward_Monotonic: rewards decrease as the curve advances. func TestCalculateBlockReward_Monotonic(t *testing.T) { - halfLife := int64(31536000) - pool := sdkmath.NewInt(1_000_000_000_000_000) - blockDelta := float64(5) + d := mustDecayFactor(t, oneYear) + s := sdkmath.LegacyNewDec(1_000_000_000_000_000) - // Rewards should decrease monotonically over time var prev sdkmath.Int - for elapsed := float64(0); elapsed < float64(halfLife)*3; elapsed += float64(halfLife) / 10 { - r := keeper.CalculateBlockReward(halfLife, pool, math.Max(elapsed, blockDelta), blockDelta) + for range 30 { + var r sdkmath.Int + r, s = keeper.CalculateBlockReward(s, d, oneYear/10) if !prev.IsNil() && prev.IsPositive() && r.IsPositive() { - require.True(t, r.LTE(prev), "reward should decrease: at elapsed=%f got %s > prev %s", elapsed, r, prev) + require.True(t, r.LTE(prev), "reward should decrease monotonically: got %s > prev %s", r, prev) } prev = r } } + +// TestCalculateBlockReward_HugePool: no panic / overflow for an enormous pool. +func TestCalculateBlockReward_HugePool(t *testing.T) { + d := mustDecayFactor(t, oneYear) + // 1e9 tokens * 1e18 = 1e27 base units. + huge, ok := sdkmath.NewIntFromString("1000000000000000000000000000") + require.True(t, ok) + s := sdkmath.LegacyNewDecFromInt(huge) + + require.NotPanics(t, func() { + reward, newS := keeper.CalculateBlockReward(s, d, 6) + require.True(t, reward.IsPositive()) + require.True(t, reward.LT(huge)) + require.True(t, newS.LT(s)) + }) +} diff --git a/x/svip/types/keys.go b/x/svip/types/keys.go index 6fab1871..db383b5c 100644 --- a/x/svip/types/keys.go +++ b/x/svip/types/keys.go @@ -16,6 +16,8 @@ const ( prefixTotalPausedSeconds prefixActivated prefixPaused + prefixScheduledRemaining + prefixDecayFactor ) var ( @@ -27,4 +29,10 @@ var ( TotalPausedSecondsKey = []byte{prefixTotalPausedSeconds} ActivatedKey = []byte{prefixActivated} PausedKey = []byte{prefixPaused} + // ScheduledRemainingKey stores the remaining pool on the decay curve (LegacyDec), + // stepped forward each block as S = S * d^dt. + ScheduledRemainingKey = []byte{prefixScheduledRemaining} + // DecayFactorKey stores the per-second decay factor d (LegacyDec), recomputed only at + // (re)activation and when the half-life changes. + DecayFactorKey = []byte{prefixDecayFactor} ) diff --git a/x/svip/types/params.go b/x/svip/types/params.go index ebe9ac25..ab88d155 100644 --- a/x/svip/types/params.go +++ b/x/svip/types/params.go @@ -2,6 +2,15 @@ package types import "fmt" +const ( + // MinHalfLifeSeconds is the shortest non-zero half-life (1 year). Anything shorter would + // front-load emissions too aggressively. + MinHalfLifeSeconds = 31536000 + // MaxHalfLifeSeconds caps the half-life at 100 years, past which the curve barely decays + // and the fixed-point factor loses precision. + MaxHalfLifeSeconds = 100 * MinHalfLifeSeconds +) + // DefaultParams returns the default SVIP module parameters. func DefaultParams() Params { return Params{ @@ -9,10 +18,14 @@ func DefaultParams() Params { } } -// Validate checks that the SVIP parameters are valid. +// Validate checks that the SVIP parameters are valid. A zero half-life is permitted (the +// pre-activation / genesis default); any non-zero value must lie within [1 year, 100 years]. func (p Params) Validate() error { if p.HalfLifeSeconds < 0 { return fmt.Errorf("half_life_seconds cannot be negative: %d", p.HalfLifeSeconds) } + if p.HalfLifeSeconds != 0 && (p.HalfLifeSeconds < MinHalfLifeSeconds || p.HalfLifeSeconds > MaxHalfLifeSeconds) { + return fmt.Errorf("half_life_seconds must be 0 or within [%d, %d]: %d", MinHalfLifeSeconds, MaxHalfLifeSeconds, p.HalfLifeSeconds) + } return nil } diff --git a/x/vm/keeper/call_evm.go b/x/vm/keeper/call_evm.go index ed0fd515..d14d1279 100644 --- a/x/vm/keeper/call_evm.go +++ b/x/vm/keeper/call_evm.go @@ -130,9 +130,12 @@ func (k Keeper) CallEVMSystemWithData( } cachedCtx, commit := ctx.CacheContext() fromAcc := sdk.AccAddress(from.Bytes()) - if !k.accountKeeper.HasAccount(cachedCtx, fromAcc) { - k.accountKeeper.SetAccount(cachedCtx, k.accountKeeper.NewAccountWithAddress(cachedCtx, fromAcc)) - } + // The caller is responsible for ensuring the `from` account already exists; the scheduler + // creates the EVM module account as a real ModuleAccount on the live ctx. We don't + // auto-create it here on purpose: putting a plain BaseAccount at a module address would + // break the module-account invariants precisebank relies on for mint/burn. A missing + // account just falls through to the error below, which the scheduler handles without + // halting. nonce, err := k.accountKeeper.GetSequence(cachedCtx, fromAcc) if err != nil { return nil, err diff --git a/x/vm/keeper/scheduler.go b/x/vm/keeper/scheduler.go index 9958dd7b..5779bd55 100644 --- a/x/vm/keeper/scheduler.go +++ b/x/vm/keeper/scheduler.go @@ -37,6 +37,12 @@ func shouldRunScheduler(ctx sdk.Context, params types.EVMSchedulerParams) bool { // returns an error because scheduler failures must not halt consensus. func (k Keeper) RunScheduler(ctx sdk.Context) { targetContract := "" + // Recovering here stays deterministic: the EVM system call runs in a CacheContext that + // commits only on success (see CallEVMSystemWithData), so a caught panic drops the same + // uncommitted state on every node. The only thing touched on the live ctx before the call + // is the idempotent module-account creation below, which is also deterministic. Note that + // recover() does not make a nondeterministic computation safe: the reward math it drives + // still has to be integer/fixed-point (see x/svip). defer func() { if r := recover(); r != nil { errText := fmt.Sprintf("panic: %v", r) @@ -51,11 +57,17 @@ func (k Keeper) RunScheduler(ctx sdk.Context) { return } - moduleAddr := k.accountKeeper.GetModuleAddress(types.ModuleName) - if moduleAddr == nil { + // Create the EVM module account as a real ModuleAccount on the live ctx (not the call's + // cache context) so it survives even if the poke panics later. The system call below moves + // native value through precisebank mint/burn, which needs a genuine ModuleAccount at this + // address; a plain BaseAccount (what CallEVMSystemWithData used to auto-create) would break + // those invariants. + moduleAcc := k.accountKeeper.GetModuleAccount(ctx, types.ModuleName) + if moduleAcc == nil { k.emitSchedulerEvent(ctx, params.TargetContract, false, 0, "", "evm module account not found") return } + moduleAddr := moduleAcc.GetAddress() target := common.HexToAddress(params.TargetContract) res, err := k.CallEVMSystemWithData( diff --git a/x/vm/types/interfaces.go b/x/vm/types/interfaces.go index b4129feb..8f78d7cb 100644 --- a/x/vm/types/interfaces.go +++ b/x/vm/types/interfaces.go @@ -26,6 +26,7 @@ import ( type AccountKeeper interface { NewAccountWithAddress(ctx context.Context, addr sdk.AccAddress) sdk.AccountI GetModuleAddress(moduleName string) sdk.AccAddress + GetModuleAccount(ctx context.Context, moduleName string) sdk.ModuleAccountI HasAccount(ctx context.Context, addr sdk.AccAddress) bool GetAccount(ctx context.Context, addr sdk.AccAddress) sdk.AccountI SetAccount(ctx context.Context, account sdk.AccountI) diff --git a/x/vm/types/mocks/AccountKeeper.go b/x/vm/types/mocks/AccountKeeper.go index d4965cff..ec7d9b08 100644 --- a/x/vm/types/mocks/AccountKeeper.go +++ b/x/vm/types/mocks/AccountKeeper.go @@ -80,6 +80,26 @@ func (_m *AccountKeeper) GetModuleAddress(moduleName string) cosmos_sdktypes.Acc return r0 } +// GetModuleAccount provides a mock function with given fields: ctx, moduleName +func (_m *AccountKeeper) GetModuleAccount(ctx context.Context, moduleName string) cosmos_sdktypes.ModuleAccountI { + ret := _m.Called(ctx, moduleName) + + if len(ret) == 0 { + panic("no return value specified for GetModuleAccount") + } + + var r0 cosmos_sdktypes.ModuleAccountI + if rf, ok := ret.Get(0).(func(context.Context, string) cosmos_sdktypes.ModuleAccountI); ok { + r0 = rf(ctx, moduleName) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(cosmos_sdktypes.ModuleAccountI) + } + } + + return r0 +} + // GetParams provides a mock function with given fields: ctx func (_m *AccountKeeper) GetParams(ctx context.Context) authtypes.Params { ret := _m.Called(ctx) diff --git a/x/vm/types/params.go b/x/vm/types/params.go index b0155722..b36344a5 100644 --- a/x/vm/types/params.go +++ b/x/vm/types/params.go @@ -125,6 +125,19 @@ func (p Params) Validate() error { return validateChannels(p.EVMChannels) } +const ( + // SchedulerPokeStepCount mirrors StakedBondVault.POKE_STEP_COUNT: the number of steps in a + // single poke. MaxOps has to be at least this, or the final steps (undelegation resume, + // staking) get starved. + SchedulerPokeStepCount = 7 + // MaxSchedulerGasCap caps the per-block scheduler compute. The poke runs on its own meter + // but still spends wall-clock EndBlock time, so a mis-set gas cap shouldn't be able to hurt + // block liveness. + MaxSchedulerGasCap = 100_000_000 + // MaxSchedulerOps is an upper bound on the poke op count. + MaxSchedulerOps = 256 +) + func (p EVMSchedulerParams) Validate() error { if p.TargetContract != "" && !common.IsHexAddress(p.TargetContract) { return fmt.Errorf("invalid scheduler target contract: %s", p.TargetContract) @@ -138,8 +151,14 @@ func (p EVMSchedulerParams) Validate() error { if p.GasCap == 0 { return fmt.Errorf("scheduler gas cap must be greater than zero") } - if p.MaxOps == 0 { - return fmt.Errorf("scheduler max ops must be greater than zero") + if p.GasCap > MaxSchedulerGasCap { + return fmt.Errorf("scheduler gas cap %d exceeds maximum %d", p.GasCap, MaxSchedulerGasCap) + } + if p.MaxOps < SchedulerPokeStepCount { + return fmt.Errorf("scheduler max ops must be >= %d (poke step count)", SchedulerPokeStepCount) + } + if p.MaxOps > MaxSchedulerOps { + return fmt.Errorf("scheduler max ops %d exceeds maximum %d", p.MaxOps, MaxSchedulerOps) } if p.CadenceBlocks == 0 { return fmt.Errorf("scheduler cadence blocks must be greater than zero")