diff --git a/rust/contracts/artifacts/HandleEscrow.sol/HandleEscrow.json b/rust/contracts/artifacts/HandleEscrow.sol/HandleEscrow.json new file mode 100644 index 0000000..1cba11e --- /dev/null +++ b/rust/contracts/artifacts/HandleEscrow.sol/HandleEscrow.json @@ -0,0 +1,25 @@ +{ + "bytecode": { + "linkReferences": {}, + "object": "0x60a080604052346100c257306080525f516020611d325f395f51905f525460ff8160401c166100b3576002600160401b03196001600160401b03821601610060575b604051611c6b90816100c7823960805181818161038601526104290152f35b6001600160401b0319166001600160401b039081175f516020611d325f395f51905f525581527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d290602090a15f80610041565b63f92ee8a960e01b5f5260045ffd5b5f80fdfe6080806040526004361015610012575f80fd5b5f3560e01c908163056da04814610b285750806314ac004614610ae0578063192d06e31461085e5780632b229bb9146108205780632b87fea01461079b578063485cc955146105b95780634f1ef286146103da57806352d1902d14610374578063715018a61461032b5780637967c6f0146102cd57806379ba50971461027f5780638da5cb5b1461024b578063a0cf0aea14610231578063ad3cb1cc146101ee578063c1473d3f14610193578063e30c39781461015f5763f2fde38b146100d7575f80fd5b3461015b57602036600319011261015b576100f0610c6b565b6100f8611605565b5f516020611c4b5f395f51905f5280546001600160a01b0319166001600160a01b039283169081179091555f516020611b8b5f395f51905f52549091167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e227005f80a3005b5f80fd5b3461015b575f36600319011261015b575f516020611c4b5f395f51905f52546040516001600160a01b039091168152602090f35b3461015b57604036600319011261015b576024356001600160a01b038116810361015b576004355f525f516020611bcb5f395f51905f5260205260405f209060018060a01b03165f52602052602060405f2054604051908152f35b3461015b575f36600319011261015b5761022d60405161020f604082610b57565b60058152640352e302e360dc1b602082015260405191829182610bfd565b0390f35b3461015b575f36600319011261015b5760206040515f8152f35b3461015b575f36600319011261015b575f516020611b8b5f395f51905f52546040516001600160a01b039091168152602090f35b3461015b575f36600319011261015b575f516020611c4b5f395f51905f5254336001600160a01b03909116036102ba576102b83361159a565b005b63118cdaa760e01b5f523360045260245ffd5b608036600319011261015b5760243567ffffffffffffffff811161015b576102fc610319913690600401610c27565b610304610c55565b9061030d6114a7565b60643592600435610d79565b5f5f516020611c0b5f395f51905f525d005b3461015b575f36600319011261015b5760405162461bcd60e51b81526020600482015260116024820152701c995b9bdd5b98d948191a5cd8589b1959607a1b6044820152606490fd5b3461015b575f36600319011261015b577f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031630036103cb5760206040515f516020611bab5f395f51905f528152f35b63703e46dd60e11b5f5260045ffd5b604036600319011261015b576103ee610c6b565b60243567ffffffffffffffff811161015b573660238201121561015b5761041f903690602481600401359101610ba9565b6001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016308114908115610597575b506103cb57610461611605565b6040516352d1902d60e01b81526001600160a01b0383169290602081600481875afa5f9181610563575b506104a35783634c9c8ce360e01b5f5260045260245ffd5b805f516020611bab5f395f51905f528592036105515750813b1561053f575f516020611bab5f395f51905f5280546001600160a01b031916821790557fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b5f80a2815115610527575f808360206102b895519101845af46105216114dc565b91611b2c565b50503461053057005b63b398979f60e01b5f5260045ffd5b634c9c8ce360e01b5f5260045260245ffd5b632a87526960e21b5f5260045260245ffd5b9091506020813d60201161058f575b8161057f60209383610b57565b8101031261015b5751908561048b565b3d9150610572565b5f516020611bab5f395f51905f52546001600160a01b03161415905083610454565b3461015b57604036600319011261015b576105d2610c6b565b6024356001600160a01b0381169081900361015b575f516020611c2b5f395f51905f52549160ff8360401c16159267ffffffffffffffff811680159081610793575b6001149081610789575b159081610780575b506107715767ffffffffffffffff1981166001175f516020611c2b5f395f51905f525583610745575b5081156107365761065e61167d565b61066661167d565b6001600160a01b038116156107235761067e9061159a565b61068661167d565b61068e61167d565b61069661167d565b6bffffffffffffffffffffffff60a01b5f516020611beb5f395f51905f525416175f516020611beb5f395f51905f52556106cc57005b68ff0000000000000000195f516020611c2b5f395f51905f5254165f516020611c2b5f395f51905f52557fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2602060405160018152a1005b631e4fbdf760e01b5f525f60045260245ffd5b6325c514cb60e11b5f5260045ffd5b68ffffffffffffffffff191668010000000000000001175f516020611c2b5f395f51905f52558361064f565b63f92ee8a960e01b5f5260045ffd5b90501585610626565b303b15915061061e565b859150610614565b3461015b57606036600319011261015b5760243567ffffffffffffffff811161015b576107ec6107d26107e4923690600401610c27565b92906107dc610c55565b933691610ba9565b600435610cd7565b5f525f516020611bcb5f395f51905f5260205260405f209060018060a01b03165f52602052602060405f2054604051908152f35b3461015b57604036600319011261015b5760243567ffffffffffffffff811161015b576108566107e46020923690600401610bdf565b604051908152f35b3461015b57608036600319011261015b5760043560243567ffffffffffffffff811161015b57610892903690600401610c27565b9061089b610c55565b606435939092906001600160a01b03851680860361015b576108bb6114a7565b80158015610ad7575b610ac5575061091a916020916108e46108de368488610ba9565b82610cd7565b9460018060a01b035f516020611beb5f395f51905f52541691604051958694859384936391ef5b5160e01b855260048501610cc0565b03915afa908115610aba575f91610a8b575b506001600160a01b0316338103610a755750805f525f516020611bcb5f395f51905f5260205260405f2060018060a01b0383165f5260205260405f2054918215610a52575f8281525f516020611bcb5f395f51905f52602090815260408083206001600160a01b0390941680845293909152812055917f8c18dc0a2c06d397feb6dd44b409d75c443e6cfbe8cd39e19619d7809ff9b874906109fa9084610a0f576109d7818761150b565b604080516001600160a01b03909716875260208701919091523395918291820190565b0390a45f5f516020611c0b5f395f51905f525d005b60405163a9059cbb60e01b60208201526001600160a01b038716602482015260448082018390528152610a4d90610a47606482610b57565b86611625565b6109d7565b630c946d8360e01b5f9081526004929092526001600160a01b0316602452604490fd5b63b6bd8e8360e01b5f526004523360245260445ffd5b610aad915060203d602011610ab3575b610aa58183610b57565b810190610c81565b8461092c565b503d610a9b565b6040513d5f823e3d90fd5b631ecdd20960e31b5f5260045260245ffd5b503081146108c4565b3461015b57602036600319011261015b5760043567ffffffffffffffff811161015b57610b1c610b1761022d923690600401610bdf565b61131b565b60405191829182610bfd565b3461015b575f36600319011261015b575f516020611beb5f395f51905f52546001600160a01b03168152602090f35b90601f8019910116810190811067ffffffffffffffff821117610b7957604052565b634e487b7160e01b5f52604160045260245ffd5b67ffffffffffffffff8111610b7957601f01601f191660200190565b929192610bb582610b8d565b91610bc36040519384610b57565b82948184528183011161015b578281602093845f960137010152565b9080601f8301121561015b57816020610bfa93359101610ba9565b90565b602060409281835280519182918282860152018484015e5f828201840152601f01601f1916010190565b9181601f8401121561015b5782359167ffffffffffffffff831161015b576020838186019501011161015b57565b604435906001600160a01b038216820361015b57565b600435906001600160a01b038216820361015b57565b9081602091031261015b57516001600160a01b038116810361015b5790565b908060209392818452848401375f828201840152601f01601f1916010190565b604090610bfa949281528160208201520191610ca0565b610d1a601b6020604051610cec604082610b57565b828152017f6c696269642e657363726f772e68616e646c652d736c6f742e763100000000008152209261131b565b6020815191012060405191602083019384526040830152606082015260608152610d45608082610b57565b51902090565b91908203918211610d5857565b634e487b7160e01b5f52601160045260245ffd5b91908201809211610d5857565b939291938315610f85575f516020611beb5f395f51905f5254604051630f62983f60e41b8152600481018390526001600160a01b03909116949060a081602481895afa8015610aba575f906111f4575b610dde9150610dd9368987610ba9565b6116a8565b5060058110156111e057806111ce5750610e02610dfc368886610ba9565b83610cd7565b6001600160a01b039094169485159081156111b15782340361119a575b602060405180926391ef5b5160e01b82528180610e418d8b8b60048501610cc0565b03915afa908115610aba575f9161117b575b506001600160a01b0381169081610ffe57505015610ee7577fee495663dac7b04af757ba8ee849872169d884c3d001f57fa2352898ce71f7aa92610ed991965b855f525f516020611bcb5f395f51905f5260205260405f20875f5260205260405f20610ec0898254610d6c565b9055604051938452606060208501526060840191610ca0565b9460408201528033950390a4565b6040516370a0823160e01b815230600482015290602082602481895afa918215610aba575f92610fc8575b50610f1f9030338861154f565b6040516370a0823160e01b815230600482015290602082602481895afa8015610aba575f90610f94575b610f539250610d4b565b948515610f85577fee495663dac7b04af757ba8ee849872169d884c3d001f57fa2352898ce71f7aa92610ed991610e93565b631f2a200560e01b5f5260045ffd5b506020823d602011610fc0575b81610fae60209383610b57565b8101031261015b57610f539151610f49565b3d9150610fa1565b9091506020813d602011610ff6575b81610fe460209383610b57565b8101031261015b575190610f1f610f12565b3d9150610fd7565b83919594929893985f14611068579161103e61105a94927f276c5feea4a110ef0c00982b96bce8903321dfc84e414bdf4e898fad7ecfb5cc97969461150b565b6040519485526020850152608060408501526080840191610ca0565b9460608201528033950390a4565b949392919750604051946370a0823160e01b86528460048701526020866024818b5afa958615610aba575f96611143575b50906110a691338961154f565b6040516370a0823160e01b815260048101849052936020856024818a5afa948515610aba575f95611109575b509061110361105a93927f276c5feea4a110ef0c00982b96bce8903321dfc84e414bdf4e898fad7ecfb5cc96610d4b565b9761103e565b945091906020853d60201161113b575b8161112660209383610b57565b8101031261015b5793519390916111036110d2565b3d9150611119565b919095506020823d602011611173575b8161116060209383610b57565b8101031261015b579051946110a6611099565b3d9150611153565b611194915060203d602011610ab357610aa58183610b57565b5f610e53565b82630626ade360e41b5f526004523460245260445ffd5b3415610e1f57630626ade360e41b5f525f6004523460245260445ffd5b63eb1fffd960e01b5f5260045260245ffd5b634e487b7160e01b5f52602160045260245ffd5b5060a0813d60a011611290575b8161120e60a09383610b57565b8101031261015b5760405160a0810181811067ffffffffffffffff821117610b795760405281519161ffff8316830361015b57608061128691610dde94845261125960208201611542565b602085015261126a60408201611542565b604085015261127b60608201611542565b606085015201611542565b6080820152610dc9565b3d9150611201565b9081518110156112a9570160200190565b634e487b7160e01b5f52603260045260245ffd5b5f198114610d585760010190565b8015610d58575f190190565b906112e182610b8d565b6112ee6040519182610b57565b82815280926112ff601f1991610b8d565b0190602036910137565b60ff60209116019060ff8211610d5857565b80515f5b81811080611486575b1561133b57611336906112bd565b61131f565b905b81811180611458575b1561135957611354906112cb565b61133d565b91909180831080611437575b611422575b8261137491610d4b565b90811561140e57611384826112d7565b925f5b838110611395575050505090565b806113ab6113a560019385610d6c565b85611298565b516001600160f81b0319811690604160f81b82101580611400575b6113df575b505f1a6113d88288611298565b5301611387565b6001600160f81b031991506113f69060f81c611309565b60f81b165f6113cb565b50602d60f91b8211156113c6565b63eb1fffd960e01b5f52600160045260245ffd5b9161142f611374916112bd565b92905061136a565b50600160fe1b6001600160f81b03196114508585611298565b511614611365565b505f198101818111610d5857600160fd1b906001600160f81b03199061147e9086611298565b511614611346565b50600160fd1b6001600160f81b031961149f8386611298565b511614611328565b5f516020611c0b5f395f51905f525c6114cd5760015f516020611c0b5f395f51905f525d565b633ee5aeb560e01b5f5260045ffd5b3d15611506573d906114ed82610b8d565b916114fb6040519384610b57565b82523d5f602084013e565b606090565b5f80808085855af161151b6114dc565b5015611525575050565b63296c17bb60e21b5f5260018060a01b031660045260245260445ffd5b5190811515820361015b57565b6040516323b872dd60e01b60208201526001600160a01b03928316602482015292909116604483015260648083019390935291815261159891611593608483610b57565b611625565b565b5f516020611c4b5f395f51905f5280546001600160a01b03199081169091555f516020611b8b5f395f51905f5280549182166001600160a01b0393841690811790915591167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e05f80a3565b5f516020611b8b5f395f51905f52546001600160a01b031633036102ba57565b905f602091828151910182855af115610aba575f513d61167457506001600160a01b0381163b155b6116545750565b635274afe760e01b5f9081526001600160a01b0391909116600452602490fd5b6001141561164d565b60ff5f516020611c2b5f395f51905f525460401c161561169957565b631afcd79f60e31b5f5260045ffd5b915f918351925b838110806118d9575b156116cb576116c6906112bd565b6116af565b93925b848111806118ab575b156116ea576116e5906112cb565b6116ce565b9290919260208301511515806118a2575b80611881575b61186c575b8461171091610d4b565b9081156118565761ffff83511682116118405761172c826112d7565b945f5b83811061179d5750505050604081015115611771575061174e82611ac2565b15611759575b5f9190565b6040516004925061176b602082610b57565b5f815290565b6080015161177e575f9190565b611787826119f9565b611754576040516004925061176b602082610b57565b6117b06117aa8284610d6c565b84611298565b516001600160f81b0319811690604160f81b82101580611832575b611811575b506117db86826118fa565b156117f757906001915f1a6117f0828a611298565b530161172f565b5050505050509050600390602061176b6040519182610b57565b6001600160f81b031991506118289060f81c611309565b60f81b165f6117d0565b50602d60f91b8211156117cb565b505050905060029060405161176b602082610b57565b505050905060019060405161176b602082610b57565b93611879611710916112bd565b949050611706565b50600160fe1b6001600160f81b031961189a8785611298565b511614611701565b508481116116fb565b505f198101818111610d5857600160fd1b906001600160f81b0319906118d19087611298565b5116146116d7565b50600160fd1b6001600160f81b03196118f28388611298565b5116146116b8565b6001600160f81b03191690606160f81b821015806119eb575b61196c57600360fc1b821015806119dd575b61196c576040810151611980576060810151151580611973575b61196c57608001511515908161195e575b50611959575f90565b600190565b602d60f81b1490505f611950565b5050600190565b50605f60f81b821461193f565b50601760f91b81149081156119cf575b81156119c1575b81156119b3575b81156119a8575090565b600160fe1b14919050565b605f60f81b8114915061199e565b602d60f81b81149150611997565b602b60f81b81149150611990565b50603960f81b821115611925565b50603d60f91b821115611913565b8051156112a95760208101516001600160f81b031916602d60f81b148015611a92575b611a8d5760015b815181101561196c57602d60f81b6001600160f81b0319611a448385611298565b51161480611a5f575b611a5957600101611a23565b50505f90565b505f198101818111610d5857602d60f81b906001600160f81b031990611a859085611298565b511614611a4d565b505f90565b5080515f198101908111610d5857602d60f81b906001600160f81b031990611aba9084611298565b511614611a1c565b5f195f5b8251811015611b0357600160fe1b6001600160f81b0319611ae78386611298565b511614611af7575b600101611ac6565b9019611a595780611aef565b505f198114611a59578015159182611b1a57505090565b515f19810192508211610d5857141590565b90611b505750805115611b4157805190602001fd5b63d6bda27560e01b5f5260045ffd5b81511580611b81575b611b61575090565b639996b31560e01b5f9081526001600160a01b0391909116600452602490fd5b50803b15611b5956fe9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbcfcca8d7d2c66f78c2760f3fcd99e0bf938b0aeb0d0b471f481dd50b8aff6b400fcca8d7d2c66f78c2760f3fcd99e0bf938b0aeb0d0b471f481dd50b8aff6b4019b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00f0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00237e158222e3e6968b72b9db0d8043aacf074ad9f650f0d1606b4d82ee432c00f0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00" + }, + "methodIdentifiers": { + "NATIVE()": "a0cf0aea", + "UPGRADE_INTERFACE_VERSION()": "ad3cb1cc", + "acceptOwnership()": "79ba5097", + "canonicalHandle(string)": "14ac0046", + "claim(bytes32,string,address,address)": "192d06e3", + "deposit(bytes32,string,address,uint256)": "7967c6f0", + "escrowed(bytes32,string,address)": "2b87fea0", + "escrowedAt(bytes32,address)": "c1473d3f", + "initialize(address,address)": "485cc955", + "names()": "056da048", + "owner()": "8da5cb5b", + "pendingOwner()": "e30c3978", + "proxiableUUID()": "52d1902d", + "renounceOwnership()": "715018a6", + "slotOf(bytes32,string)": "2b229bb9", + "transferOwnership(address)": "f2fde38b", + "upgradeToAndCall(address,bytes)": "4f1ef286" + } +} diff --git a/rust/contracts/artifacts/IdentityNames.sol/IdentityNames.json b/rust/contracts/artifacts/IdentityNames.sol/IdentityNames.json index c92982c..da9ee97 100644 --- a/rust/contracts/artifacts/IdentityNames.sol/IdentityNames.json +++ b/rust/contracts/artifacts/IdentityNames.sol/IdentityNames.json @@ -1,7 +1,7 @@ { "bytecode": { "linkReferences": {}, - "object": "0x60a080604052346100c257306080525f516020612c4a5f395f51905f525460ff8160401c166100b3576002600160401b03196001600160401b03821601610060575b604051612b8390816100c7823960805181818161168e01526118120152f35b6001600160401b0319166001600160401b039081175f516020612c4a5f395f51905f525581527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d290602090a15f80610041565b63f92ee8a960e01b5f5260045ffd5b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c80631340305a14611da8578063284e6abf14611ca45780632de00c0114611aec57806338e09e7814611ad15780633b31fcd114611a2d5780634a9252cf146119c95780634f1ef286146117c4578063502927d8146116e257806352d1902d1461167c57806358b9b1e71461113e578063606bf407146110e757806361d5a73114610954578063621fa08914610915578063715018a6146108cc57806379ba50971461087e578063839692bc146108275780638da5cb5b146107f357806391ef5b5114610781578063a43f59661461067b578063a69460311461056c578063ad3cb1cc14610525578063b1b805b514610403578063bf89efd21461039a578063c4d66de81461021b578063e30c3978146101e7578063ec8efd21146101ca5763f2fde38b14610142575f80fd5b346101c65760203660031901126101c65761015b611dd0565b6101636122c9565b5f516020612b635f395f51905f5280546001600160a01b0319166001600160a01b039283169081179091555f516020612ac35f395f51905f52549091167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e227005f80a3005b5f80fd5b346101c6575f3660031901126101c6576020604051620151808152f35b346101c6575f3660031901126101c6575f516020612b635f395f51905f52546040516001600160a01b039091168152602090f35b346101c65760203660031901126101c657610234611dd0565b5f516020612b435f395f51905f52549060ff8260401c1615916001600160401b03811680159081610392575b6001149081610388575b15908161037f575b506103705767ffffffffffffffff1981166001175f516020612b435f395f51905f525582610344575b506102a46127a7565b6102ac6127a7565b6001600160a01b03811615610331576102c49061232f565b6102cc6127a7565b6102d46127a7565b6102da57005b68ff0000000000000000195f516020612b435f395f51905f5254165f516020612b435f395f51905f52557fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2602060405160018152a1005b631e4fbdf760e01b5f525f60045260245ffd5b68ffffffffffffffffff191668010000000000000001175f516020612b435f395f51905f52558261029b565b63f92ee8a960e01b5f5260045ffd5b90501584610272565b303b15915061026a565b849150610260565b346101c65760203660031901126101c6576004355f9081525f516020612a835f395f51905f5260209081526040918290205482516001600160a01b038216815260a082901c6001600160401b03169281019290925260e01c9181019190915280606081015b0390f35b346101c65760c03660031901126101c65760043560a03660231901126101c65761042b6122c9565b805f525f516020612a635f395f51905f5260205260405f2060243561ffff81168091036101c65781549060443590811515918281036101c6575060643590811515918281036101c65762ff00009263ff000000915060181b169363ffffffff1916179160101b1617178155608435801515908181036101c6575064ff0000000082549160201b169064ff00000000191617815560a435908115158083036101c657815465ff0000000000191660289190911b65ff000000000016178155600101805464ff000000001916640100000000179055507fb01c0a30cea93f12bdaa4228f3ced17148be2cc93045c1848d99d588c0141c8f5f80a2005b346101c6575f3660031901126101c6576103ff604051610546604082611eb6565b60058152640352e302e360dc1b6020820152604051918291602083526020830190611de6565b346101c65760403660031901126101c65760043563ffffffff61058d611e37565b6105956122c9565b1690811561066c57805f525f516020612a635f395f51905f5260205260ff600160405f20015460201c161561065a575f8181525f516020612ae35f395f51905f52602090815260408083208584529091529020546001600160a01b03161561064557805f525f516020612a635f395f51905f52602052600160405f20018263ffffffff198254161790557f39de5aa871268e261cc41a46876cebce3b7b112a7b158b360f850538860a9bab5f80a3005b6312eaaba960e21b5f5260045260245260445ffd5b6313f6864d60e01b5f5260045260245ffd5b635498399960e11b5f5260045ffd5b346101c65760403660031901126101c657600435610697611e37565b906106a06122c9565b5f8181525f516020612ae35f395f51905f526020908152604080832063ffffffff861684529091529020546001600160a01b03161561076457805f525f516020612a635f395f51905f5260205263ffffffff80600160405f20015416921680921461074f57805f525f516020612ae35f395f51905f5260205260405f20825f526020525f60408120557f2af3e10bdbb7f8ceef8368a212ed59736df2eb37120a7ef435c48d980c04480a5f80a3005b637d788d8f60e11b5f5260045260245260445ffd5b9063ffffffff916312eaaba960e21b5f526004521660245260445ffd5b346101c6576107af6107a961079536611f28565b92906107a08361219f565b51933691611ef2565b90612171565b90156107ea575f525f516020612b235f395f51905f52602052602060018060a01b0360405f2054165b6040516001600160a01b039091168152f35b5060205f6107d8565b346101c6575f3660031901126101c6575f516020612ac35f395f51905f52546040516001600160a01b039091168152602090f35b346101c65760403660031901126101c657610840611e37565b6004355f525f516020612ae35f395f51905f5260205263ffffffff60405f2091165f52602052602060405f2060018060a01b03905416604051908152f35b346101c6575f3660031901126101c6575f516020612b635f395f51905f5254336001600160a01b03909116036108b9576108b73361232f565b005b63118cdaa760e01b5f523360045260245ffd5b346101c6575f3660031901126101c65760405162461bcd60e51b81526020600482015260116024820152701c995b9bdd5b98d948191a5cd8589b1959607a1b6044820152606490fd5b346101c65760203660031901126101c6576004355f525f516020612a635f395f51905f52602052602063ffffffff600160405f20015416604051908152f35b346101c65760603660031901126101c6576004356024356001600160401b0381116101c657610987903690600401611e0a565b9060443592831591821585036101c6576109a08261219f565b9263ffffffff60208501511691835f525f516020612ae35f395f51905f5260205260405f2063ffffffff84165f5260205260405f2090604051916109e383611e4a565b549160018060a01b0383168082526001600160401b03602083019460a01c168452156110d0575160405163473b057f60e11b81526020600482015260248101899052975f9289926001600160a01b031691839160449183918190838501378181018301879052601f01601f191681010301915afa9586156110c5575f96610fff575b506040860180516001600160a01b031615610ff057516001600160a01b0316338103610fda5750606086016001600160401b0381511615610fcb5786515115610fbc576001600160401b038151166001600160401b038351166001600160401b034216016001600160401b038111610f87576001600160401b031690818111610fa75750505190516001600160401b03908116911681811115610f9b57036001600160401b038111610f8757610b2490945b60208701519051906123cd565b906005811015610f735760018114610f645760028114610f555760038114610f4657600414610f3757610b5886518561224a565b95610b63828661263a565b97875f525f516020612a835f395f51905f52602052610b926001600160401b0360405f205460a01c16886126a5565b885f525f516020612b235f395f51905f52602052610bc06001600160401b0360405f205460a01c16886126a5565b610c64604051610bcf81611e80565b3381526001600160401b03989098166020808a018281526040808c018a81525f8e81525f516020612a835f395f51905f5290945292209a518b5491516001600160e01b03199092166001600160a01b03919091161760a09190911b67ffffffffffffffff60a01b16178a5590989063ffffffff905b5182546001600160e01b0316911660e01b6001600160e01b031916179055565b610cdd604051610c7381611e80565b33815260208082018a815260408084018a81525f8f81525f516020612b235f395f51905f5290945292209251835491516001600160e01b03199092166001600160a01b03919091161760a09190911b67ffffffffffffffff60a01b1617825563ffffffff90610c44565b610ce88989886126d2565b875f527f064503501234cc9c6e116cf4a84c07475158dabb6a3dcee437a89227e23bf2046020528860405f2055885f527f064503501234cc9c6e116cf4a84c07475158dabb6a3dcee437a89227e23bf2056020528760405f205592610f11575b82610da7575b5f516020612aa35f395f51905f5294939291610d7f610d8d925160405196875260c0602088015260c0870190611de6565b908582036040870152611de6565b9460608401521515608083015260a08201528033930390a4005b610db033611f5b565b855f5260205260405f2082516001600160401b038111610efd57610dd48254611f93565b601f8111610eba575b506020601f8211600114610e4057925f516020612aa35f395f51905f529796959492610e2583610d7f94610d8d975f91610e35575b508160011b915f199060031b1c19161790565b90555b9250509192939450610d4e565b90508601518f610e12565b601f19821690835f52805f20915f5b818110610ea2575083610d8d96935f516020612aa35f395f51905f529b9a99989693610d7f9660019410610e8a575b5050811b019055610e28565b8701515f1960f88460031b161c191690558e80610e7e565b9192602060018192868b015181550194019201610e4f565b81811115610ddd57610eef90835f5260205f2090601f840160051c9060208510610ef5575b601f82910160051c039101612156565b8a610ddd565b5f9150610edf565b634e487b7160e01b5f52604160045260245ffd5b9150610f1c33611f5b565b845f52602052610f2f60405f2054611f93565b151591610d48565b63a9b0b66560e01b5f5260045ffd5b634bd5d0ad60e01b5f5260045ffd5b630183219160e51b5f5260045ffd5b6375ebfc4960e01b5f5260045ffd5b634e487b7160e01b5f52602160045260245ffd5b634e487b7160e01b5f52601160045260245ffd5b5050610b245f94610b17565b63182f1ac760e21b5f5260045260245260445ffd5b63eae8cfd360e01b5f5260045ffd5b634f1a06e760e11b5f5260045ffd5b634dab16af60e11b5f526004523360245260445ffd5b633c88bf6360e01b5f5260045ffd5b9095503d805f833e6110118183611eb6565b8101906020818303126101c6578051906001600160401b0382116101c657016080818303126101c6576040519161104783611e9b565b81516001600160401b0381116101c657816110639184016122e9565b83526020820151906001600160401b0382116101c6576110849183016122e9565b60208301526040810151906001600160a01b03821682036101c657606091604084015201516001600160401b03811681036101c65760608201529487610a65565b6040513d5f823e3d90fd5b84866312eaaba960e21b5f5260045260245260445ffd5b346101c65761111461110e6110fb36611f28565b61110683949361219f565b503691611ef2565b9061224a565b5f525f516020612a835f395f51905f52602052602060018060a01b0360405f205416604051908152f35b346101c65760803660031901126101c65760043561115a611e37565b906044356001600160401b0381116101c65761117a903690600401611e0a565b9160643593841590811586036101c65763ffffffff1690811561066c576111a08361219f565b93835f525f516020612ae35f395f51905f5260205260405f20835f5260205260405f2090604051916111d183611e4a565b549160018060a01b0383168082526001600160401b03602083019460a01c168452156110d0575160405163473b057f60e11b81526020600482015260248101899052975f9289926001600160a01b031691839160449183918190838501378181018301879052601f01601f191681010301915afa9586156110c5575f966115b6575b506040860180516001600160a01b031615610ff057516001600160a01b0316338103610fda5750606086016001600160401b0381511615610fcb5786515115610fbc576001600160401b038151166001600160401b038351166001600160401b034216016001600160401b038111610f87576001600160401b031690818111610fa75750505190516001600160401b039081169116818111156115aa57036001600160401b038111610f8757611311909460208701519051906123cd565b906005811015610f735760018114610f645760028114610f555760038114610f4657600414610f375761134586518561224a565b95611350828661263a565b97875f525f516020612a835f395f51905f5260205261137f6001600160401b0360405f205460a01c16886126a5565b885f525f516020612b235f395f51905f526020526113ad6001600160401b0360405f205460a01c16886126a5565b6113bc604051610bcf81611e80565b6113cb604051610c7381611e80565b6113d68989886126d2565b875f527f064503501234cc9c6e116cf4a84c07475158dabb6a3dcee437a89227e23bf2046020528860405f2055885f527f064503501234cc9c6e116cf4a84c07475158dabb6a3dcee437a89227e23bf2056020528760405f205592611584575b8261146c575f516020612aa35f395f51905f5294939291610d7f610d8d925160405196875260c0602088015260c0870190611de6565b61147533611f5b565b855f5260205260405f2082516001600160401b038111610efd576114998254611f93565b601f811161154a575b506020601f82116001146114e957925f516020612aa35f395f51905f529796959492610e2583610d7f94610d8d975f91610e3557508160011b915f199060031b1c19161790565b601f19821690835f52805f20915f5b818110611532575083610d8d96935f516020612aa35f395f51905f529b9a99989693610d7f9660019410610e8a575050811b019055610e28565b9192602060018192868b0151815501940192016114f8565b818111156114a25761157e90835f5260205f2090601f840160051c9060208510610ef557601f82910160051c039101612156565b8a6114a2565b915061158f33611f5b565b845f526020526115a260405f2054611f93565b151591611436565b50506113115f94610b17565b9095503d805f833e6115c88183611eb6565b8101906020818303126101c6578051906001600160401b0382116101c657016080818303126101c657604051916115fe83611e9b565b81516001600160401b0381116101c6578161161a9184016122e9565b83526020820151906001600160401b0382116101c65761163b9183016122e9565b60208301526040810151906001600160a01b03821682036101c657606091604084015201516001600160401b03811681036101c65760608201529487611253565b346101c6575f3660031901126101c6577f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031630036116d35760206040515f516020612b035f395f51905f528152f35b63703e46dd60e11b5f5260045ffd5b346101c65760403660031901126101c6576117036116fe611dd0565b611f5b565b6024355f5260205260405f20604051905f9080549061172182611f93565b808552916001811690811561179d575060011461175d575b6103ff8461174981860382611eb6565b604051918291602083526020830190611de6565b5f90815260208120939250905b8082106117835750909150810160200161174982611739565b91926001816020925483858801015201910190929161176a565b60ff191660208087019190915292151560051b850190920192506117499150839050611739565b60403660031901126101c6576117d8611dd0565b6024356001600160401b0381116101c657366023820112156101c657611808903690602481600401359101611ef2565b6001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000163081149081156119a7575b506116d35761184a6122c9565b6040516352d1902d60e01b81526001600160a01b0383169290602081600481875afa5f9181611973575b5061188c5783634c9c8ce360e01b5f5260045260245ffd5b805f516020612b035f395f51905f528592036119615750813b1561194f575f516020612b035f395f51905f5280546001600160a01b031916821790557fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b5f80a2815115611937575f808360206108b795519101845af43d1561192f573d9161191383611ed7565b926119216040519485611eb6565b83523d5f602085013e612a04565b606091612a04565b50503461194057005b63b398979f60e01b5f5260045ffd5b634c9c8ce360e01b5f5260045260245ffd5b632a87526960e21b5f5260045260245ffd5b9091506020813d60201161199f575b8161198f60209383611eb6565b810103126101c657519085611874565b3d9150611982565b5f516020612b035f395f51905f52546001600160a01b0316141590508361183d565b346101c65760203660031901126101c6576004355f9081525f516020612b235f395f51905f5260209081526040918290205482516001600160a01b03821681526001600160401b0360a083901c169281019290925260e01c91810191909152606090f35b346101c65760203660031901126101c657600435611a4a33611f5b565b815f5260205260405f20611a5e8154611f93565b9081611a8d575b82337f8e43ba98be6948052c3368d53fef5505611df0ae5994d278a646eb9ad3479edd5f80a3005b81601f5f9311600114611aa45750555b8180611a65565b81835260208320611ac191601f0160051c84190190600101612156565b8082528160208120915555611a9d565b346101c6575f3660031901126101c657602060405160018152f35b346101c65760803660031901126101c657600435611b08611e37565b6044356001600160a01b0381169291908390036101c657606435906001600160401b0382168092036101c65763ffffffff90611b426122c9565b1692831561066c578015611c9557620151808211611c7b57825f525f516020612a635f395f51905f52602052600160405f20019160ff835460201c1615611c68577f13b9f8bb5ee05a42b59cc1d4197d78c5ca07a7236ce1bd191161faca7e5bb398604086938693611c1b8351611bb881611e4a565b83815260208082018481525f8981525f516020612ae35f395f51905f5283528781208b8252909252908690209151825491516001600160e01b03199092166001600160a01b03919091161760a09190911b67ffffffffffffffff60a01b16179055565b82519182526020820152a363ffffffff81541615611c3557005b805463ffffffff1916831790557f39de5aa871268e261cc41a46876cebce3b7b112a7b158b360f850538860a9bab5f80a3005b836313f6864d60e01b5f5260045260245ffd5b5063911d6f1360e01b5f526004526201518060245260445ffd5b6379e2e00360e01b5f5260045ffd5b346101c65760603660031901126101c6576004356024356001600160401b0381116101c657611cd7903690600401611e0a565b91604435906001600160401b0382116101c65761110e611d1d93611d23604096611d08611d54963690600401611e0a565b979093611d148761219f565b51923691611ef2565b85612171565b9015611da0575f9081525f516020612b235f395f51905f52602052869020546001600160a01b0316945b3691611ef2565b5f9081525f516020612a835f395f51905f52602052829020546001600160a01b03918216918215159183911682611d96575b5050825191825215156020820152f35b1490508184611d86565b505f94611d4d565b346101c65760403660031901126101c6576103ff611749611dc7611dd0565b60243590612018565b600435906001600160a01b03821682036101c657565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b9181601f840112156101c6578235916001600160401b0383116101c657602083818601950101116101c657565b6024359063ffffffff821682036101c657565b604081019081106001600160401b03821117610efd57604052565b60a081019081106001600160401b03821117610efd57604052565b606081019081106001600160401b03821117610efd57604052565b608081019081106001600160401b03821117610efd57604052565b90601f801991011681019081106001600160401b03821117610efd57604052565b6001600160401b038111610efd57601f01601f191660200190565b929192611efe82611ed7565b91611f0c6040519384611eb6565b8294818452818301116101c6578281602093845f960137010152565b9060406003198301126101c65760043591602435906001600160401b0382116101c657611f5791600401611e0a565b9091565b6001600160a01b03165f9081527f064503501234cc9c6e116cf4a84c07475158dabb6a3dcee437a89227e23bf2026020526040902090565b90600182811c92168015611fc1575b6020831014611fad57565b634e487b7160e01b5f52602260045260245ffd5b91607f1691611fa2565b90604051611fd881611e65565b608060ff82945461ffff81168452818160101c1615156020850152818160181c1615156040850152818160201c161515606085015260281c161515910152565b61202181611f5b565b825f5260205260405f209160405192835f82549261203e84611f93565b808452936001811690811561213457506001146120f0575b5061206392500384611eb6565b8251156120df5780612095915f525f516020612a635f395f51905f526020528361208f60405f20611fcb565b91612171565b90156120df575f9081525f516020612b235f395f51905f5260205260409020546001600160a01b039081169116036120ca5790565b506040516120d9602082611eb6565b5f815290565b5050506040516120d9602082611eb6565b90505f9291925260205f20905f915b818310612118575050906020612063928201015f612056565b6020919350806001915483858a010152019101909185926120ff565b90506020925061206394915060ff191682840152151560051b8201015f612056565b5f5b82811061216457505050565b5f82820155600101612158565b9161217b916123cd565b906005811015610f7357612197576121929161263a565b600191565b50505f905f90565b905f604080516121ae81611e80565b81516121b981611e65565b838152836020820152838382015283606082015283608082015281528260208201520152815f525f516020612a635f395f51905f5260205260405f209160016040519361220585611e80565b61220e81611fcb565b855201549260ff63ffffffff85169485602084015260201c1615938415918260408201529491612241575b5061065a5750565b9050155f612239565b6122c36019602060405161225f604082611eb6565b828152017f6479616b612e6964656e746974792e69642d6e6f64652e7631000000000000008152209260208151910120916122b56040519384926020840196876040919493926060820195825260208201520152565b03601f198101835282611eb6565b51902090565b5f516020612ac35f395f51905f52546001600160a01b031633036108b957565b81601f820112156101c65780519061230082611ed7565b9261230e6040519485611eb6565b828452602083830101116101c657815f9260208093018386015e8301015290565b5f516020612b635f395f51905f5280546001600160a01b03199081169091555f516020612ac35f395f51905f5280549182166001600160a01b0393841690811790915591167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e05f80a3565b9081518110156123ab570160200190565b634e487b7160e01b5f52603260045260245ffd5b5f198114610f875760010190565b80519290915f5b84811080612619575b156123f0576123eb906123bf565b6123d4565b92935b838111806125eb575b1561240f578015610f87575f19016123f3565b9091929360208301511515806125e2575b806125c1575b6125b1575b84820391808311610f8757851461259b5761ffff83511682116125855761245182611ed7565b9461245f6040519687611eb6565b828652601f1961246e84611ed7565b013660208801375f5b8381106124df57505050506040810151156124b357506124968261299a565b156124a1575b5f9190565b604051600492506120d9602082611eb6565b608001516124c0575f9190565b6124c9826128d1565b61249c57604051600492506120d9602082611eb6565b808201808311610f87576124f3908461239a565b516001600160f81b0319811690604160f81b82101580612577575b612554575b5061251e86826127d2565b1561253a57906001915f1a612533828a61239a565b5301612477565b505050505050905060039060206120d96040519182611eb6565b6020915060f81c0160ff8111610f875760f81b6001600160f81b0319165f612513565b50602d60f91b82111561250e565b50505090506002906040516120d9602082611eb6565b50505090506001906040516120d9602082611eb6565b936125bb906123bf565b9361242b565b50600160fe1b6001600160f81b03196125da878461239a565b511614612426565b50848211612420565b505f198101818111610f8757600160fd1b906001600160f81b031990612611908861239a565b5116146123fc565b50600160fd1b6001600160f81b0319612632838761239a565b5116146123dd565b6122c3601d602060405161264f604082611eb6565b828152017f6479616b612e6964656e746974792e68616e646c652d6e6f64652e76310000008152209260208151910120916122b56040519384926020840196876040919493926060820195825260208201520152565b6001600160401b039182169116818111156126be575050565b6277f1a160e81b5f5260045260245260445ffd5b9190805f527f064503501234cc9c6e116cf4a84c07475158dabb6a3dcee437a89227e23bf20460205260405f205491821590811561279d575b5061279857815f527f064503501234cc9c6e116cf4a84c07475158dabb6a3dcee437a89227e23bf20560205260405f205403612794575f8181525f516020612b235f395f51905f5260205260408120805467ffffffffffffffff60a01b1690553392907f278e2122d24782b703f05416ac53a9750d67bcf6fa1603b94ec95660821a89149080a4565b5050565b505050565b905082145f61270b565b60ff5f516020612b435f395f51905f525460401c16156127c357565b631afcd79f60e31b5f5260045ffd5b6001600160f81b03191690606160f81b821015806128c3575b61284457600360fc1b821015806128b5575b61284457604081015161285857606081015115158061284b575b612844576080015115159081612836575b50612831575f90565b600190565b602d60f81b1490505f612828565b5050600190565b50605f60f81b8214612817565b50601760f91b81149081156128a7575b8115612899575b811561288b575b8115612880575090565b600160fe1b14919050565b605f60f81b81149150612876565b602d60f81b8114915061286f565b602b60f81b81149150612868565b50603960f81b8211156127fd565b50603d60f91b8211156127eb565b8051156123ab5760208101516001600160f81b031916602d60f81b14801561296a575b6129655760015b815181101561284457602d60f81b6001600160f81b031961291c838561239a565b51161480612937575b612931576001016128fb565b50505f90565b505f198101818111610f8757602d60f81b906001600160f81b03199061295d908561239a565b511614612925565b505f90565b5080515f198101908111610f8757602d60f81b906001600160f81b031990612992908461239a565b5116146128f4565b5f195f5b82518110156129db57600160fe1b6001600160f81b03196129bf838661239a565b5116146129cf575b60010161299e565b901961293157806129c7565b505f1981146129315780151591826129f257505090565b515f19810192508211610f8757141590565b90612a285750805115612a1957805190602001fd5b63d6bda27560e01b5f5260045ffd5b81511580612a59575b612a39575090565b639996b31560e01b5f9081526001600160a01b0391909116600452602490fd5b50803b15612a3156fe064503501234cc9c6e116cf4a84c07475158dabb6a3dcee437a89227e23bf203064503501234cc9c6e116cf4a84c07475158dabb6a3dcee437a89227e23bf2008acbd4495dbb2ff215c4799cd4551da5688633536a4c5381ff06eaca80119a2e9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300064503501234cc9c6e116cf4a84c07475158dabb6a3dcee437a89227e23bf206360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc064503501234cc9c6e116cf4a84c07475158dabb6a3dcee437a89227e23bf201f0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00237e158222e3e6968b72b9db0d8043aacf074ad9f650f0d1606b4d82ee432c00f0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00" + "object": "0x60a080604052346100c257306080525f516020612cc75f395f51905f525460ff8160401c166100b3576002600160401b03196001600160401b03821601610060575b604051612c0090816100c782396080518181816116fe01526118820152f35b6001600160401b0319166001600160401b039081175f516020612cc75f395f51905f525581527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d290602090a15f80610041565b63f92ee8a960e01b5f5260045ffd5b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c80631340305a14611e18578063284e6abf14611d145780632de00c0114611b5c57806338e09e7814611b415780633b31fcd114611a9d5780634a9252cf14611a395780634f1ef28614611834578063502927d81461175257806352d1902d146116ec57806358b9b1e7146111ae578063606bf4071461115757806361d5a731146109c4578063621fa08914610985578063715018a61461093c57806379ba5097146108ee578063839692bc146108975780638da5cb5b1461086357806391ef5b51146107f1578063a43f5966146106eb578063a6946031146105dc578063ad3cb1cc14610595578063b1b805b514610473578063bf89efd21461040a578063c4d66de81461028b578063e30c397814610257578063ec8efd211461023a578063f2fde38b146101b65763f62983f01461014d575f80fd5b346101b25760203660031901126101b2576101666121e1565b5060a0610174600435612239565b5160806040519161ffff8151168352602081015115156020840152604081015115156040840152606081015115156060840152015115156080820152f35b5f80fd5b346101b25760203660031901126101b2576101cf611e40565b6101d7612346565b5f516020612be05f395f51905f5280546001600160a01b0319166001600160a01b039283169081179091555f516020612b405f395f51905f52549091167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e227005f80a3005b346101b2575f3660031901126101b2576020604051620151808152f35b346101b2575f3660031901126101b2575f516020612be05f395f51905f52546040516001600160a01b039091168152602090f35b346101b25760203660031901126101b2576102a4611e40565b5f516020612bc05f395f51905f52549060ff8260401c1615916001600160401b03811680159081610402575b60011490816103f8575b1590816103ef575b506103e05767ffffffffffffffff1981166001175f516020612bc05f395f51905f5255826103b4575b50610314612824565b61031c612824565b6001600160a01b038116156103a157610334906123ac565b61033c612824565b610344612824565b61034a57005b68ff0000000000000000195f516020612bc05f395f51905f5254165f516020612bc05f395f51905f52557fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2602060405160018152a1005b631e4fbdf760e01b5f525f60045260245ffd5b68ffffffffffffffffff191668010000000000000001175f516020612bc05f395f51905f52558261030b565b63f92ee8a960e01b5f5260045ffd5b905015846102e2565b303b1591506102da565b8491506102d0565b346101b25760203660031901126101b2576004355f9081525f516020612b005f395f51905f5260209081526040918290205482516001600160a01b038216815260a082901c6001600160401b03169281019290925260e01c9181019190915280606081015b0390f35b346101b25760c03660031901126101b25760043560a03660231901126101b25761049b612346565b805f525f516020612ae05f395f51905f5260205260405f2060243561ffff81168091036101b25781549060443590811515918281036101b2575060643590811515918281036101b25762ff00009263ff000000915060181b169363ffffffff1916179160101b1617178155608435801515908181036101b2575064ff0000000082549160201b169064ff00000000191617815560a435908115158083036101b257815465ff0000000000191660289190911b65ff000000000016178155600101805464ff000000001916640100000000179055507fb01c0a30cea93f12bdaa4228f3ced17148be2cc93045c1848d99d588c0141c8f5f80a2005b346101b2575f3660031901126101b25761046f6040516105b6604082611f26565b60058152640352e302e360dc1b6020820152604051918291602083526020830190611e56565b346101b25760403660031901126101b25760043563ffffffff6105fd611ea7565b610605612346565b169081156106dc57805f525f516020612ae05f395f51905f5260205260ff600160405f20015460201c16156106ca575f8181525f516020612b605f395f51905f52602090815260408083208584529091529020546001600160a01b0316156106b557805f525f516020612ae05f395f51905f52602052600160405f20018263ffffffff198254161790557f39de5aa871268e261cc41a46876cebce3b7b112a7b158b360f850538860a9bab5f80a3005b6312eaaba960e21b5f5260045260245260445ffd5b6313f6864d60e01b5f5260045260245ffd5b635498399960e11b5f5260045ffd5b346101b25760403660031901126101b257600435610707611ea7565b90610710612346565b5f8181525f516020612b605f395f51905f526020908152604080832063ffffffff861684529091529020546001600160a01b0316156107d457805f525f516020612ae05f395f51905f5260205263ffffffff80600160405f2001541692168092146107bf57805f525f516020612b605f395f51905f5260205260405f20825f526020525f60408120557f2af3e10bdbb7f8ceef8368a212ed59736df2eb37120a7ef435c48d980c04480a5f80a3005b637d788d8f60e11b5f5260045260245260445ffd5b9063ffffffff916312eaaba960e21b5f526004521660245260445ffd5b346101b25761081f61081961080536611f98565b929061081083612239565b51933691611f62565b9061220b565b901561085a575f525f516020612ba05f395f51905f52602052602060018060a01b0360405f2054165b6040516001600160a01b039091168152f35b5060205f610848565b346101b2575f3660031901126101b2575f516020612b405f395f51905f52546040516001600160a01b039091168152602090f35b346101b25760403660031901126101b2576108b0611ea7565b6004355f525f516020612b605f395f51905f5260205263ffffffff60405f2091165f52602052602060405f2060018060a01b03905416604051908152f35b346101b2575f3660031901126101b2575f516020612be05f395f51905f5254336001600160a01b039091160361092957610927336123ac565b005b63118cdaa760e01b5f523360045260245ffd5b346101b2575f3660031901126101b25760405162461bcd60e51b81526020600482015260116024820152701c995b9bdd5b98d948191a5cd8589b1959607a1b6044820152606490fd5b346101b25760203660031901126101b2576004355f525f516020612ae05f395f51905f52602052602063ffffffff600160405f20015416604051908152f35b346101b25760603660031901126101b2576004356024356001600160401b0381116101b2576109f7903690600401611e7a565b9060443592831591821585036101b257610a1082612239565b9263ffffffff60208501511691835f525f516020612b605f395f51905f5260205260405f2063ffffffff84165f5260205260405f209060405191610a5383611eba565b549160018060a01b0383168082526001600160401b03602083019460a01c16845215611140575160405163473b057f60e11b81526020600482015260248101899052975f9289926001600160a01b031691839160449183918190838501378181018301879052601f01601f191681010301915afa958615611135575f9661106f575b506040860180516001600160a01b03161561106057516001600160a01b031633810361104a5750606086016001600160401b038151161561103b578651511561102c576001600160401b038151166001600160401b038351166001600160401b034216016001600160401b038111610ff7576001600160401b0316908181116110175750505190516001600160401b0390811691168181111561100b57036001600160401b038111610ff757610b9490945b602087015190519061244a565b906005811015610fe35760018114610fd45760028114610fc55760038114610fb657600414610fa757610bc88651856122c7565b95610bd382866126b7565b97875f525f516020612b005f395f51905f52602052610c026001600160401b0360405f205460a01c1688612722565b885f525f516020612ba05f395f51905f52602052610c306001600160401b0360405f205460a01c1688612722565b610cd4604051610c3f81611ef0565b3381526001600160401b03989098166020808a018281526040808c018a81525f8e81525f516020612b005f395f51905f5290945292209a518b5491516001600160e01b03199092166001600160a01b03919091161760a09190911b67ffffffffffffffff60a01b16178a5590989063ffffffff905b5182546001600160e01b0316911660e01b6001600160e01b031916179055565b610d4d604051610ce381611ef0565b33815260208082018a815260408084018a81525f8f81525f516020612ba05f395f51905f5290945292209251835491516001600160e01b03199092166001600160a01b03919091161760a09190911b67ffffffffffffffff60a01b1617825563ffffffff90610cb4565b610d5889898861274f565b875f527f064503501234cc9c6e116cf4a84c07475158dabb6a3dcee437a89227e23bf2046020528860405f2055885f527f064503501234cc9c6e116cf4a84c07475158dabb6a3dcee437a89227e23bf2056020528760405f205592610f81575b82610e17575b5f516020612b205f395f51905f5294939291610def610dfd925160405196875260c0602088015260c0870190611e56565b908582036040870152611e56565b9460608401521515608083015260a08201528033930390a4005b610e2033611fcb565b855f5260205260405f2082516001600160401b038111610f6d57610e448254612003565b601f8111610f2a575b506020601f8211600114610eb057925f516020612b205f395f51905f529796959492610e9583610def94610dfd975f91610ea5575b508160011b915f199060031b1c19161790565b90555b9250509192939450610dbe565b90508601518f610e82565b601f19821690835f52805f20915f5b818110610f12575083610dfd96935f516020612b205f395f51905f529b9a99989693610def9660019410610efa575b5050811b019055610e98565b8701515f1960f88460031b161c191690558e80610eee565b9192602060018192868b015181550194019201610ebf565b81811115610e4d57610f5f90835f5260205f2090601f840160051c9060208510610f65575b601f82910160051c0391016121c6565b8a610e4d565b5f9150610f4f565b634e487b7160e01b5f52604160045260245ffd5b9150610f8c33611fcb565b845f52602052610f9f60405f2054612003565b151591610db8565b63a9b0b66560e01b5f5260045ffd5b634bd5d0ad60e01b5f5260045ffd5b630183219160e51b5f5260045ffd5b6375ebfc4960e01b5f5260045ffd5b634e487b7160e01b5f52602160045260245ffd5b634e487b7160e01b5f52601160045260245ffd5b5050610b945f94610b87565b63182f1ac760e21b5f5260045260245260445ffd5b63eae8cfd360e01b5f5260045ffd5b634f1a06e760e11b5f5260045ffd5b634dab16af60e11b5f526004523360245260445ffd5b633c88bf6360e01b5f5260045ffd5b9095503d805f833e6110818183611f26565b8101906020818303126101b2578051906001600160401b0382116101b257016080818303126101b257604051916110b783611f0b565b81516001600160401b0381116101b257816110d3918401612366565b83526020820151906001600160401b0382116101b2576110f4918301612366565b60208301526040810151906001600160a01b03821682036101b257606091604084015201516001600160401b03811681036101b25760608201529487610ad5565b6040513d5f823e3d90fd5b84866312eaaba960e21b5f5260045260245260445ffd5b346101b25761118461117e61116b36611f98565b611176839493612239565b503691611f62565b906122c7565b5f525f516020612b005f395f51905f52602052602060018060a01b0360405f205416604051908152f35b346101b25760803660031901126101b2576004356111ca611ea7565b906044356001600160401b0381116101b2576111ea903690600401611e7a565b9160643593841590811586036101b25763ffffffff169081156106dc5761121083612239565b93835f525f516020612b605f395f51905f5260205260405f20835f5260205260405f20906040519161124183611eba565b549160018060a01b0383168082526001600160401b03602083019460a01c16845215611140575160405163473b057f60e11b81526020600482015260248101899052975f9289926001600160a01b031691839160449183918190838501378181018301879052601f01601f191681010301915afa958615611135575f96611626575b506040860180516001600160a01b03161561106057516001600160a01b031633810361104a5750606086016001600160401b038151161561103b578651511561102c576001600160401b038151166001600160401b038351166001600160401b034216016001600160401b038111610ff7576001600160401b0316908181116110175750505190516001600160401b0390811691168181111561161a57036001600160401b038111610ff7576113819094602087015190519061244a565b906005811015610fe35760018114610fd45760028114610fc55760038114610fb657600414610fa7576113b58651856122c7565b956113c082866126b7565b97875f525f516020612b005f395f51905f526020526113ef6001600160401b0360405f205460a01c1688612722565b885f525f516020612ba05f395f51905f5260205261141d6001600160401b0360405f205460a01c1688612722565b61142c604051610c3f81611ef0565b61143b604051610ce381611ef0565b61144689898861274f565b875f527f064503501234cc9c6e116cf4a84c07475158dabb6a3dcee437a89227e23bf2046020528860405f2055885f527f064503501234cc9c6e116cf4a84c07475158dabb6a3dcee437a89227e23bf2056020528760405f2055926115f4575b826114dc575f516020612b205f395f51905f5294939291610def610dfd925160405196875260c0602088015260c0870190611e56565b6114e533611fcb565b855f5260205260405f2082516001600160401b038111610f6d576115098254612003565b601f81116115ba575b506020601f821160011461155957925f516020612b205f395f51905f529796959492610e9583610def94610dfd975f91610ea557508160011b915f199060031b1c19161790565b601f19821690835f52805f20915f5b8181106115a2575083610dfd96935f516020612b205f395f51905f529b9a99989693610def9660019410610efa575050811b019055610e98565b9192602060018192868b015181550194019201611568565b81811115611512576115ee90835f5260205f2090601f840160051c9060208510610f6557601f82910160051c0391016121c6565b8a611512565b91506115ff33611fcb565b845f5260205261161260405f2054612003565b1515916114a6565b50506113815f94610b87565b9095503d805f833e6116388183611f26565b8101906020818303126101b2578051906001600160401b0382116101b257016080818303126101b2576040519161166e83611f0b565b81516001600160401b0381116101b2578161168a918401612366565b83526020820151906001600160401b0382116101b2576116ab918301612366565b60208301526040810151906001600160a01b03821682036101b257606091604084015201516001600160401b03811681036101b257606082015294876112c3565b346101b2575f3660031901126101b2577f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031630036117435760206040515f516020612b805f395f51905f528152f35b63703e46dd60e11b5f5260045ffd5b346101b25760403660031901126101b25761177361176e611e40565b611fcb565b6024355f5260205260405f20604051905f9080549061179182612003565b808552916001811690811561180d57506001146117cd575b61046f846117b981860382611f26565b604051918291602083526020830190611e56565b5f90815260208120939250905b8082106117f3575090915081016020016117b9826117a9565b9192600181602092548385880101520191019092916117da565b60ff191660208087019190915292151560051b850190920192506117b991508390506117a9565b60403660031901126101b257611848611e40565b6024356001600160401b0381116101b257366023820112156101b257611878903690602481600401359101611f62565b6001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016308114908115611a17575b50611743576118ba612346565b6040516352d1902d60e01b81526001600160a01b0383169290602081600481875afa5f91816119e3575b506118fc5783634c9c8ce360e01b5f5260045260245ffd5b805f516020612b805f395f51905f528592036119d15750813b156119bf575f516020612b805f395f51905f5280546001600160a01b031916821790557fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b5f80a28151156119a7575f8083602061092795519101845af43d1561199f573d9161198383611f47565b926119916040519485611f26565b83523d5f602085013e612a81565b606091612a81565b5050346119b057005b63b398979f60e01b5f5260045ffd5b634c9c8ce360e01b5f5260045260245ffd5b632a87526960e21b5f5260045260245ffd5b9091506020813d602011611a0f575b816119ff60209383611f26565b810103126101b2575190856118e4565b3d91506119f2565b5f516020612b805f395f51905f52546001600160a01b031614159050836118ad565b346101b25760203660031901126101b2576004355f9081525f516020612ba05f395f51905f5260209081526040918290205482516001600160a01b03821681526001600160401b0360a083901c169281019290925260e01c91810191909152606090f35b346101b25760203660031901126101b257600435611aba33611fcb565b815f5260205260405f20611ace8154612003565b9081611afd575b82337f8e43ba98be6948052c3368d53fef5505611df0ae5994d278a646eb9ad3479edd5f80a3005b81601f5f9311600114611b145750555b8180611ad5565b81835260208320611b3191601f0160051c841901906001016121c6565b8082528160208120915555611b0d565b346101b2575f3660031901126101b257602060405160018152f35b346101b25760803660031901126101b257600435611b78611ea7565b6044356001600160a01b0381169291908390036101b257606435906001600160401b0382168092036101b25763ffffffff90611bb2612346565b169283156106dc578015611d0557620151808211611ceb57825f525f516020612ae05f395f51905f52602052600160405f20019160ff835460201c1615611cd8577f13b9f8bb5ee05a42b59cc1d4197d78c5ca07a7236ce1bd191161faca7e5bb398604086938693611c8b8351611c2881611eba565b83815260208082018481525f8981525f516020612b605f395f51905f5283528781208b8252909252908690209151825491516001600160e01b03199092166001600160a01b03919091161760a09190911b67ffffffffffffffff60a01b16179055565b82519182526020820152a363ffffffff81541615611ca557005b805463ffffffff1916831790557f39de5aa871268e261cc41a46876cebce3b7b112a7b158b360f850538860a9bab5f80a3005b836313f6864d60e01b5f5260045260245ffd5b5063911d6f1360e01b5f526004526201518060245260445ffd5b6379e2e00360e01b5f5260045ffd5b346101b25760603660031901126101b2576004356024356001600160401b0381116101b257611d47903690600401611e7a565b91604435906001600160401b0382116101b25761117e611d8d93611d93604096611d78611dc4963690600401611e7a565b979093611d8487612239565b51923691611f62565b8561220b565b9015611e10575f9081525f516020612ba05f395f51905f52602052869020546001600160a01b0316945b3691611f62565b5f9081525f516020612b005f395f51905f52602052829020546001600160a01b03918216918215159183911682611e06575b5050825191825215156020820152f35b1490508184611df6565b505f94611dbd565b346101b25760403660031901126101b25761046f6117b9611e37611e40565b60243590612088565b600435906001600160a01b03821682036101b257565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b9181601f840112156101b2578235916001600160401b0383116101b257602083818601950101116101b257565b6024359063ffffffff821682036101b257565b604081019081106001600160401b03821117610f6d57604052565b60a081019081106001600160401b03821117610f6d57604052565b606081019081106001600160401b03821117610f6d57604052565b608081019081106001600160401b03821117610f6d57604052565b90601f801991011681019081106001600160401b03821117610f6d57604052565b6001600160401b038111610f6d57601f01601f191660200190565b929192611f6e82611f47565b91611f7c6040519384611f26565b8294818452818301116101b2578281602093845f960137010152565b9060406003198301126101b25760043591602435906001600160401b0382116101b257611fc791600401611e7a565b9091565b6001600160a01b03165f9081527f064503501234cc9c6e116cf4a84c07475158dabb6a3dcee437a89227e23bf2026020526040902090565b90600182811c92168015612031575b602083101461201d57565b634e487b7160e01b5f52602260045260245ffd5b91607f1691612012565b9060405161204881611ed5565b608060ff82945461ffff81168452818160101c1615156020850152818160181c1615156040850152818160201c161515606085015260281c161515910152565b61209181611fcb565b825f5260205260405f209160405192835f8254926120ae84612003565b80845293600181169081156121a45750600114612160575b506120d392500384611f26565b82511561214f5780612105915f525f516020612ae05f395f51905f52602052836120ff60405f2061203b565b9161220b565b901561214f575f9081525f516020612ba05f395f51905f5260205260409020546001600160a01b0390811691160361213a5790565b50604051612149602082611f26565b5f815290565b505050604051612149602082611f26565b90505f9291925260205f20905f915b8183106121885750509060206120d3928201015f6120c6565b6020919350806001915483858a0101520191019091859261216f565b9050602092506120d394915060ff191682840152151560051b8201015f6120c6565b5f5b8281106121d457505050565b5f828201556001016121c8565b604051906121ee82611ed5565b5f6080838281528260208201528260408201528260608201520152565b916122159161244a565b906005811015610fe3576122315761222c916126b7565b600191565b50505f905f90565b905f6040805161224881611ef0565b6122506121e1565b81528260208201520152815f525f516020612ae05f395f51905f5260205260405f209160016040519361228285611ef0565b61228b8161203b565b855201549260ff63ffffffff85169485602084015260201c16159384159182604082015294916122be575b506106ca5750565b9050155f6122b6565b612340601960206040516122dc604082611f26565b828152017f6479616b612e6964656e746974792e69642d6e6f64652e7631000000000000008152209260208151910120916123326040519384926020840196876040919493926060820195825260208201520152565b03601f198101835282611f26565b51902090565b5f516020612b405f395f51905f52546001600160a01b0316330361092957565b81601f820112156101b25780519061237d82611f47565b9261238b6040519485611f26565b828452602083830101116101b257815f9260208093018386015e8301015290565b5f516020612be05f395f51905f5280546001600160a01b03199081169091555f516020612b405f395f51905f5280549182166001600160a01b0393841690811790915591167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e05f80a3565b908151811015612428570160200190565b634e487b7160e01b5f52603260045260245ffd5b5f198114610ff75760010190565b80519290915f5b84811080612696575b1561246d576124689061243c565b612451565b92935b83811180612668575b1561248c578015610ff7575f1901612470565b90919293602083015115158061265f575b8061263e575b61262e575b84820391808311610ff75785146126185761ffff8351168211612602576124ce82611f47565b946124dc6040519687611f26565b828652601f196124eb84611f47565b013660208801375f5b83811061255c5750505050604081015115612530575061251382612a17565b1561251e575b5f9190565b60405160049250612149602082611f26565b6080015161253d575f9190565b6125468261294e565b6125195760405160049250612149602082611f26565b808201808311610ff7576125709084612417565b516001600160f81b0319811690604160f81b821015806125f4575b6125d1575b5061259b868261284f565b156125b757906001915f1a6125b0828a612417565b53016124f4565b505050505050905060039060206121496040519182611f26565b6020915060f81c0160ff8111610ff75760f81b6001600160f81b0319165f612590565b50602d60f91b82111561258b565b5050509050600290604051612149602082611f26565b5050509050600190604051612149602082611f26565b936126389061243c565b936124a8565b50600160fe1b6001600160f81b03196126578784612417565b5116146124a3565b5084821161249d565b505f198101818111610ff757600160fd1b906001600160f81b03199061268e9088612417565b511614612479565b50600160fd1b6001600160f81b03196126af8387612417565b51161461245a565b612340601d60206040516126cc604082611f26565b828152017f6479616b612e6964656e746974792e68616e646c652d6e6f64652e76310000008152209260208151910120916123326040519384926020840196876040919493926060820195825260208201520152565b6001600160401b0391821691168181111561273b575050565b6277f1a160e81b5f5260045260245260445ffd5b9190805f527f064503501234cc9c6e116cf4a84c07475158dabb6a3dcee437a89227e23bf20460205260405f205491821590811561281a575b5061281557815f527f064503501234cc9c6e116cf4a84c07475158dabb6a3dcee437a89227e23bf20560205260405f205403612811575f8181525f516020612ba05f395f51905f5260205260408120805467ffffffffffffffff60a01b1690553392907f278e2122d24782b703f05416ac53a9750d67bcf6fa1603b94ec95660821a89149080a4565b5050565b505050565b905082145f612788565b60ff5f516020612bc05f395f51905f525460401c161561284057565b631afcd79f60e31b5f5260045ffd5b6001600160f81b03191690606160f81b82101580612940575b6128c157600360fc1b82101580612932575b6128c15760408101516128d55760608101511515806128c8575b6128c15760800151151590816128b3575b506128ae575f90565b600190565b602d60f81b1490505f6128a5565b5050600190565b50605f60f81b8214612894565b50601760f91b8114908115612924575b8115612916575b8115612908575b81156128fd575090565b600160fe1b14919050565b605f60f81b811491506128f3565b602d60f81b811491506128ec565b602b60f81b811491506128e5565b50603960f81b82111561287a565b50603d60f91b821115612868565b8051156124285760208101516001600160f81b031916602d60f81b1480156129e7575b6129e25760015b81518110156128c157602d60f81b6001600160f81b03196129998385612417565b511614806129b4575b6129ae57600101612978565b50505f90565b505f198101818111610ff757602d60f81b906001600160f81b0319906129da9085612417565b5116146129a2565b505f90565b5080515f198101908111610ff757602d60f81b906001600160f81b031990612a0f9084612417565b511614612971565b5f195f5b8251811015612a5857600160fe1b6001600160f81b0319612a3c8386612417565b511614612a4c575b600101612a1b565b90196129ae5780612a44565b505f1981146129ae578015159182612a6f57505090565b515f19810192508211610ff757141590565b90612aa55750805115612a9657805190602001fd5b63d6bda27560e01b5f5260045ffd5b81511580612ad6575b612ab6575090565b639996b31560e01b5f9081526001600160a01b0391909116600452602490fd5b50803b15612aae56fe064503501234cc9c6e116cf4a84c07475158dabb6a3dcee437a89227e23bf203064503501234cc9c6e116cf4a84c07475158dabb6a3dcee437a89227e23bf2008acbd4495dbb2ff215c4799cd4551da5688633536a4c5381ff06eaca80119a2e9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300064503501234cc9c6e116cf4a84c07475158dabb6a3dcee437a89227e23bf206360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc064503501234cc9c6e116cf4a84c07475158dabb6a3dcee437a89227e23bf201f0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00237e158222e3e6968b72b9db0d8043aacf074ad9f650f0d1606b4d82ee432c00f0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00" }, "methodIdentifiers": { "INITIAL_VERSION()": "38e09e78", @@ -24,6 +24,7 @@ "resolvePair(bytes32,string,string)": "284e6abf", "retireVerifier(bytes32,uint32)": "a43f5966", "reverseOf(address,bytes32)": "502927d8", + "rulesOf(bytes32)": "f62983f0", "setLatestVersion(bytes32,uint32)": "a6946031", "setPlatform(bytes32,(uint16,bool,bool,bool,bool))": "b1b805b5", "setVerifier(bytes32,uint32,address,uint64)": "2de00c01", diff --git a/rust/contracts/src/artifacts.rs b/rust/contracts/src/artifacts.rs index 8a2e052..6e818da 100644 --- a/rust/contracts/src/artifacts.rs +++ b/rust/contracts/src/artifacts.rs @@ -67,6 +67,8 @@ pub const COVERED: &[(&str, &str)] = &[ ("GoogleIdentityVerifier", "GoogleIdentityVerifier"), ("XIdentityVerifier", "XIdentityVerifier"), ("IdentityJwksRoots", "IdentityJwksRoots"), + // escrow — value held against a handle nobody has claimed yet + ("HandleEscrow", "HandleEscrow"), // factory ("LibidFactory", "LibidFactory"), ]; diff --git a/rust/contracts/src/bindings/escrow.rs b/rust/contracts/src/bindings/escrow.rs new file mode 100644 index 0000000..6098db6 --- /dev/null +++ b/rust/contracts/src/bindings/escrow.rs @@ -0,0 +1,90 @@ +//! Bindings for the handle escrow (`solidity/contracts/escrow/`). +//! +//! Value held against a platform handle rather than against an account id, so +//! a sender who knows only a name can pay it before anybody has claimed it. +//! Whoever proves that handle to the naming system takes what is held. +//! +//! The escrow keys on a rules-independent form of the text, so the slot never +//! moves when a platform's normalization is reconfigured, and validates the +//! text against the platform's current rules on the way in, so nothing funds a +//! slot no proof could claim. `slotOf` is `pure` and public for exactly that +//! reason: a client computes the same key off chain. + +/// Bindings for `escrow/HandleEscrow.sol`. +#[allow(clippy::too_many_arguments, unused_attributes)] +mod escrow_inner { + use alloy::sol; + + sol! { + #[sol(rpc)] + interface HandleEscrow { + function initialize(address owner_, address names_) external; + + /// Put `amount` of `token` against a handle. `address(0)` is the + /// chain's own token, and then `amount` must equal the value sent. + /// + /// A handle that already resolves is paid STRAIGHT THROUGH to its + /// holder — the escrow is for the window before a handle is + /// claimed. Only an unclaimed handle escrows, and then there is no + /// way to take it back. Watch `Deposited` against `Forwarded` to + /// tell the two apart. + function deposit( + bytes32 platformId, + string calldata handle, + address token, + uint256 amount + ) external payable; + + /// Take everything held for a handle in one token. The caller has + /// to be the wallet that handle currently resolves to. + function claim( + bytes32 platformId, + string calldata handle, + address token, + address recipient + ) external; + + function escrowed(bytes32 platformId, string calldata handle, address token) + external + view + returns (uint256); + function escrowedAt(bytes32 slot, address token) external view returns (uint256); + function slotOf(bytes32 platformId, string memory handle) external pure returns (bytes32); + function canonicalHandle(string memory handle) external pure returns (string memory); + function names() external view returns (address); + function NATIVE() external view returns (address); + + /// `handle` rides along as text: a slot cannot be turned back into + /// a string, so an indexer reads the name from here. + event Deposited( + bytes32 indexed slot, + address indexed token, + address indexed depositor, + bytes32 platformId, + string handle, + uint256 amount + ); + /// A deposit for a handle somebody already held, paid to that holder + /// and never booked. Nothing is claimable afterwards, which is what + /// separates it from `Deposited`. + event Forwarded( + bytes32 indexed slot, + address indexed token, + address indexed depositor, + address holder, + bytes32 platformId, + string handle, + uint256 amount + ); + event Claimed( + bytes32 indexed slot, + address indexed token, + address indexed claimer, + address recipient, + uint256 amount + ); + } + } +} + +pub use escrow_inner::HandleEscrow; diff --git a/rust/contracts/src/bindings/mod.rs b/rust/contracts/src/bindings/mod.rs index f7a82ba..dd79f0e 100644 --- a/rust/contracts/src/bindings/mod.rs +++ b/rust/contracts/src/bindings/mod.rs @@ -1,6 +1,7 @@ //! Hand-written `alloy::sol!` bindings, kept in lockstep with the Solidity //! sources in `solidity/contracts`. Grouped by product area. +pub mod escrow; pub mod factory; pub mod identity; pub mod login; diff --git a/rust/contracts/tests/anvil.rs b/rust/contracts/tests/anvil.rs index 90f6972..0cd75fe 100644 --- a/rust/contracts/tests/anvil.rs +++ b/rust/contracts/tests/anvil.rs @@ -11,9 +11,11 @@ use alloy::{ Provider, ProviderBuilder, }, + sol_types::SolValue, }; use libid_contracts::{ bindings::{ + escrow::HandleEscrow, identity::{ GitHubIdentityVerifier, IdentityNames, @@ -298,6 +300,167 @@ async fn deploys_the_identity_stack() { ); } +/// (c2) The handle escrow against a real chain: deploy it, pay a handle nobody +/// has claimed, and watch two spellings land in one slot. +/// +/// The payout path is covered by the Solidity suite, which stages proofs +/// through a mock verifier. That mock deliberately does NOT ship in this +/// crate's artifacts: it reports whatever a caller stages, so a copy reachable +/// from a deploy tool is a way to mint any identity on a live chain. What is +/// left for Rust is what Rust owns — the artifact deploys, the binding shapes, +/// and the slot derivation agreeing with the contract. +#[tokio::test] +async fn escrows_value_against_an_unclaimed_handle() { + let provider = test_provider(); + let artifacts = Artifacts::embedded(); + let deployer = default_signer(&provider).await; + let stranger = provider.get_accounts().await.unwrap()[1]; + + let names_proxy = deploy_behind_proxy( + &provider, + &artifacts, + "IdentityNames", + &IdentityNames::initializeCall { owner_: deployer }, + None, + ) + .await + .unwrap(); + + let notary_proxy = deploy_behind_proxy( + &provider, + &artifacts, + "Notary", + &Notary::initializeCall { + owner_: deployer, + notary_: Address::repeat_byte(0x11), + }, + None, + ) + .await + .unwrap(); + + // A real verifier, so the platform is wired the way a deployment wires it. + // Nothing here calls it: the escrow only reads `rulesOf`, which needs the + // platform to have one. + let github_proxy = deploy_behind_proxy( + &provider, + &artifacts, + "GitHubIdentityVerifier", + &GitHubIdentityVerifier::initializeCall { + owner_: deployer, + notaryContract_: notary_proxy, + shape_: GitHubIdentityVerifier::ResponseShape { + endpoint: "/user".into(), + handlePrefix: "\"login\":\"".into(), + idPrefix: "\"id\":".into(), + idSuffix: ",".into(), + }, + }, + None, + ) + .await + .unwrap(); + + let platform_id = keccak256(b"dyaka.identity.platform.github"); + let names = IdentityNames::new(names_proxy, &provider); + names + .setPlatform( + platform_id, + IdentityNames::Rules { + maxLength: 39, + stripLeadingAt: true, + isEmail: false, + allowUnderscore: false, + allowHyphen: true, + }, + ) + .send() + .await + .unwrap() + .get_receipt() + .await + .unwrap(); + names + .setVerifier(platform_id, 1, github_proxy, 300) + .send() + .await + .unwrap() + .get_receipt() + .await + .unwrap(); + + let escrow_proxy = deploy_behind_proxy( + &provider, + &artifacts, + "HandleEscrow", + &HandleEscrow::initializeCall { + owner_: deployer, + names_: names_proxy, + }, + None, + ) + .await + .unwrap(); + let escrow = HandleEscrow::new(escrow_proxy, &provider); + assert_eq!(escrow.names().call().await.unwrap(), names_proxy); + + // The slot a client computes off chain has to be the slot the contract + // keys on, or an indexer watches the wrong one. + let slot_v1 = keccak256(b"libid.escrow.handle-slot.v1"); + let computed = + keccak256((slot_v1, platform_id, keccak256(b"alice-1")).abi_encode_params()); + assert_eq!( + escrow + .slotOf(platform_id, " Alice-1 ".into()) + .call() + .await + .unwrap(), + computed, + "Rust and the contract derive different slots" + ); + + // Two spellings of one handle, paid before anybody holds it. + let amount = U256::from(1_000_000_000_000_000_000u64); + for spelling in [" Alice-1 ", "alice-1"] { + escrow + .deposit(platform_id, spelling.into(), Address::ZERO, amount) + .value(amount) + .send() + .await + .unwrap() + .get_receipt() + .await + .unwrap(); + } + + assert_eq!( + escrow + .escrowed(platform_id, "ALICE-1".into(), Address::ZERO) + .call() + .await + .unwrap(), + amount * U256::from(2), + "the two spellings did not accumulate in one slot" + ); + + // Nobody holds the handle, so nobody can take it — including the depositor. + let claim = escrow + .claim(platform_id, "alice-1".into(), Address::ZERO, stranger) + .from(stranger) + .send() + .await; + // The AUTHORIZATION refusal specifically. A bare `is_err` would also pass + // on a mistyped platform, an unwired one or an RPC hiccup, so it would + // stay green with the holder check removed entirely. + let err = claim + .expect_err("an unheld handle was claimable") + .to_string(); + assert!( + err.contains("NotTheHolder") || err.contains("0xb6bd8e83"), + "refused for the wrong reason: {err}" + ); +} + /// (d) XHonkVerifier via linked bytecode — exercises the recursive library /// deploy + link-reference substitution (ZKTranscriptLib). #[tokio::test] diff --git a/scripts/vendor-artifacts.sh b/scripts/vendor-artifacts.sh index ec8d921..467665c 100755 --- a/scripts/vendor-artifacts.sh +++ b/scripts/vendor-artifacts.sh @@ -55,6 +55,8 @@ ARTIFACTS=( "GoogleIdentityVerifier:GoogleIdentityVerifier" "XIdentityVerifier:XIdentityVerifier" "IdentityJwksRoots:IdentityJwksRoots" + # escrow — value held against a handle nobody has claimed yet + "HandleEscrow:HandleEscrow" # factory "LibidFactory:LibidFactory" ) diff --git a/solidity/contracts/escrow/HandleEscrow.sol b/solidity/contracts/escrow/HandleEscrow.sol new file mode 100644 index 0000000..dd827d8 --- /dev/null +++ b/solidity/contracts/escrow/HandleEscrow.sol @@ -0,0 +1,457 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; +import { + ReentrancyGuardTransientUpgradeable +} from "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardTransientUpgradeable.sol"; + +import {HandleNormalizer} from "../identity/HandleNormalizer.sol"; +import {IIdentityNames} from "./IIdentityNames.sol"; + +/// @title HandleEscrow - send to a platform handle before anybody claims it. +/// +/// @notice Holds tokens against a platform handle. Whoever proves that handle +/// to the naming system takes what is held for it. +/// +/// @dev The point is a sender who knows `@alice` and nothing else. The Bank's +/// escrow is keyed by an account's immutable id, which such a sender does +/// not have, so this keys by the handle itself. +/// +/// **The handle is the whole key. There is no deadline and no refund.** +/// That was decided deliberately, and the consequence is stated here +/// rather than left to be discovered: A PLATFORM THAT RECYCLES A HANDLE +/// HANDS THE NEW HOLDER WHATEVER ACCUMULATED FOR THE PREVIOUS ONE. So does +/// a rename — the account that renames away stops being able to claim, and +/// whoever proves the freed handle next receives it. A depositor cannot +/// take a deposit back, and neither can the owner. Send to a handle the +/// way you send to an address: because you mean that name to have it. +/// +/// **The escrow is for the window before a handle is claimed, and only +/// that.** A deposit for a handle that already resolves is paid straight to +/// its holder; once an account has claimed its identity, holding the value +/// would add a claim transaction and reach the same wallet. So an escrow +/// slot exists only while nobody holds the handle, and stops being written +/// the moment somebody does. +/// +/// **What the escrow keys on is NOT what it validates against.** The slot +/// comes from a rules-independent form of the text — see `_canonical` — so +/// the owner reconfiguring a platform's normalization cannot move money +/// that is already held. The text is separately checked against the +/// platform's CURRENT rules on the way in, so nobody funds a slot that +/// could never be claimed. A later rules change therefore decides what is +/// accepted next, and never where what is already here belongs. +/// +/// What it can still do is STRAND. A claim is authorized by +/// `resolveHandle`, which normalizes under the rules of the moment, so an +/// owner who narrows a platform — dropping the underscore, say — makes +/// handles carrying one resolve to nobody, and the value keyed to them +/// waits, at the same key, until compatible rules return. Immunity from +/// re-keying is not immunity from that. +/// +/// **The transform is pinned here, the rules are read live.** This +/// contract carries its own compiled copy of `HandleNormalizer` and reads +/// only `Rules` from the naming system. That is what makes the key +/// independent of configuration — but it also means the naming system +/// cannot change the TRANSFORM alone: it is separately upgradeable, and a +/// normalizer that folded differently there would break the agreement +/// silently, in the direction of one identity holding two slots. Such an +/// upgrade has to carry an upgrade here. The same goes for a `Rules` +/// field appended on that side: this contract's decoder would drop it. +/// +/// **The naming contract is set once and never moved.** Repointing it +/// would redirect every entitlement held, so changing it is an upgrade, +/// which leaves a record. +/// +/// **There is no pause.** A pause on `claim` freezes other people's money +/// behind an owner key, and the emergency lever is the upgrade, which is +/// already visible. +/// +/// **The naming system's owner is part of this contract's trust base, and +/// that is not a pause substitute — it is larger.** Authorization here is +/// `resolveHandle` and nothing else, so whoever can decide what that +/// answers can take what is held. The naming owner can: `setVerifier` +/// installs a verifier it controls, a `bind` through it makes it the +/// holder of any handle, and `claim` then pays it. Two ordinary +/// transactions, no upgrade and no proxy event. Read every guarantee here +/// as "under an honest naming owner"; it is the same key that already +/// decides which proofs mint names at all, so this adds no new party — +/// only a new thing that key can reach. +contract HandleEscrow is Initializable, UUPSUpgradeable, Ownable2StepUpgradeable, ReentrancyGuardTransientUpgradeable { + using SafeERC20 for IERC20; + + /// @notice The native token of the chain, as a token address. + address public constant NATIVE = address(0); + + /// @custom:storage-location erc7201:libid.storage.HandleEscrow + struct HandleEscrowStorage { + /// slot -> token -> amount held. + mapping(bytes32 => mapping(address => uint256)) held; + /// The naming system this escrow resolves through. Set in `initialize`. + IIdentityNames names; + } + + // keccak256(abi.encode(uint256(keccak256("libid.storage.HandleEscrow")) - 1)) & ~bytes32(uint256(0xff)) + bytes32 private constant HANDLE_ESCROW_STORAGE = 0xfcca8d7d2c66f78c2760f3fcd99e0bf938b0aeb0d0b471f481dd50b8aff6b400; + + /// @dev One namespaced root, as `IdentityNames` and `Registry` have. Fields + /// may be APPENDED on upgrade; reordering or removing one would make + /// every stored balance read out of the wrong bytes, and a balance read + /// from the wrong bytes does not revert — it answers. + function _s() private pure returns (HandleEscrowStorage storage $) { + assembly { + $.slot := HANDLE_ESCROW_STORAGE + } + } + + /// @dev The domain separator of the slot key. Version it rather than + /// changing the derivation in place: a new scheme must land on new + /// slots, not silently re-point the funded ones. + bytes32 private constant HANDLE_SLOT_V1 = keccak256(bytes("libid.escrow.handle-slot.v1")); + + // ─── Events ───────────────────────────────────────────────────── + + /// @notice Value was placed against a handle. + /// @dev `handle` rides along as text so an indexer can show who it is for + /// without turning a slot back into a string, which cannot be done. + event Deposited( + bytes32 indexed slot, + address indexed token, + address indexed depositor, + bytes32 platformId, + string handle, + uint256 amount + ); + + /// @notice A deposit for a handle somebody already held was paid straight to + /// that holder, and never entered the books. + /// @dev Distinct from `Deposited` on purpose: an indexer must be able to tell + /// "this is waiting" from "this was delivered", and no balance changed + /// here for it to read. + event Forwarded( + bytes32 indexed slot, + address indexed token, + address indexed depositor, + address holder, + bytes32 platformId, + string handle, + uint256 amount + ); + + /// @notice The holder of a handle took what was held for it. + event Claimed( + bytes32 indexed slot, address indexed token, address indexed claimer, address recipient, uint256 amount + ); + + // ─── Errors ───────────────────────────────────────────────────── + + /// A deposit of nothing writes nothing. + error ZeroAmount(); + /// Native value must equal the amount, and a token deposit carries none. + error ValueMismatch(uint256 expected, uint256 provided); + /// Nothing is held for this handle in this token. + error NothingHeld(bytes32 slot, address token); + /// The caller does not hold this handle. + error NotTheHolder(address holder, address caller); + /// A payout to the zero address burns it; one to this contract strands it. + error BadRecipient(address recipient); + /// Text this platform could never accept as a handle. + error UnusableHandle(HandleNormalizer.Problem problem); + /// The recipient refused the transfer. + error NativeTransferFailed(address recipient, uint256 amount); + /// The escrow needs a naming system to resolve through. + error NoNames(); + + // ─── Setup ────────────────────────────────────────────────────── + + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() { + _disableInitializers(); + } + + function initialize(address owner_, IIdentityNames names_) external initializer { + if (address(names_) == address(0)) revert NoNames(); + __Ownable_init(owner_); + __Ownable2Step_init(); + __UUPSUpgradeable_init(); + __ReentrancyGuardTransient_init(); + _s().names = names_; + } + + /// @notice The naming system this escrow resolves through. + function names() external view returns (IIdentityNames) { + return _s().names; + } + + // ─── Depositing ───────────────────────────────────────────────── + + /// @notice Put `amount` of `token` against a handle. + /// + /// @dev Escrows whether or not somebody holds the handle today. The + /// depositor gives up the value: there is no way to take it back. + /// + /// The text is validated against the platform's current rules, so a + /// typo — a space, a character the platform forbids, a handle past its + /// length — is refused here rather than accepted into a slot no proof + /// could ever claim. + /// + /// **A handle somebody already holds is paid straight through.** The + /// escrow exists for the window before a handle is claimed. Once it is + /// claimed the escrow has no purpose: holding the value would only add + /// a claim transaction to reach the same wallet. So a deposit for a + /// resolving handle is a payment, and only an unclaimed handle escrows. + /// + /// This is what makes a deposit depend on the recipient: a holder that + /// cannot receive value fails the whole call. That is the honest + /// outcome — the sender learns instead of the value waiting in a slot + /// only that same wallet could ever claim. + /// + /// **The race is accepted.** One calldata has two outcomes depending on + /// whether it lands before or after a `bind` in the same block: it pays + /// through, or it escrows and waits for a claim. Both deliver to the + /// holder of the handle, so the difference is one transaction, not one + /// of destination. + /// + /// When it does escrow, what is credited is the balance the contract + /// actually gained, not the amount asked for, so a token that takes a + /// fee on transfer cannot make the books promise more than the contract + /// holds. A payment through is not measured, because nothing is booked: + /// such a token simply delivers less, exactly as it would on a direct + /// transfer. + /// + /// @param platformId Which platform the handle belongs to. + /// @param handle The handle, as written. Case and surrounding spaces do + /// not matter; a leading at-sign is refused, because it + /// is the same handle spelled another way. + /// @param token The ERC-20, or `NATIVE` for the chain's own token. + /// @param amount How much. For `NATIVE` it must equal `msg.value`. + function deposit(bytes32 platformId, string calldata handle, address token, uint256 amount) + external + payable + nonReentrant + { + if (amount == 0) revert ZeroAmount(); + bytes32 slot = _requireUsableSlot(platformId, handle); + + if (token == NATIVE) { + if (msg.value != amount) revert ValueMismatch(amount, msg.value); + } else if (msg.value != 0) { + // Ether riding on a token deposit has nowhere to land. + revert ValueMismatch(0, msg.value); + } + + address holder = _s().names.resolveHandle(platformId, handle); + if (holder != address(0)) { + uint256 delivered = amount; + if (token == NATIVE) { + _sendNative(holder, amount); + } else { + // What the holder GAINED, not what was asked for. A token that + // takes a fee on transfer delivers less, and an event carrying + // the requested figure would be the only record of a payment + // that never happened at that size — the escrowed path at + // least has a stored balance to correct against. + uint256 before = IERC20(token).balanceOf(holder); + IERC20(token).safeTransferFrom(msg.sender, holder, amount); + delivered = IERC20(token).balanceOf(holder) - before; + } + emit Forwarded(slot, token, msg.sender, holder, platformId, handle, delivered); + return; + } + + uint256 credited; + if (token == NATIVE) { + credited = amount; + } else { + uint256 before = IERC20(token).balanceOf(address(this)); + IERC20(token).safeTransferFrom(msg.sender, address(this), amount); + credited = IERC20(token).balanceOf(address(this)) - before; + if (credited == 0) revert ZeroAmount(); + } + + _s().held[slot][token] += credited; + emit Deposited(slot, token, msg.sender, platformId, handle, credited); + } + + // ─── Claiming ─────────────────────────────────────────────────── + + /// @notice Take everything held for a handle in one token. + /// + /// @dev Authorized by the naming system and nothing else: the caller has to + /// be the wallet that handle currently resolves to. Copying this + /// calldata out of the mempool gains nothing, because the check is + /// against the caller. + /// + /// A retired handle — one whose account renamed away — resolves to + /// nobody, so its slot waits until somebody proves that handle again. + /// That may be a different account, and then the balance is theirs. + /// See the contract comment. + /// + /// **The destination is checked, not only the caller.** The zero + /// address accepts a native transfer without reverting, so an unset + /// recipient would burn the slot and log a success; this contract's + /// own address would empty the books while the value stayed put as + /// surplus nothing points at. Neither is recoverable — there is no + /// refund and no owner lever — so both are refused here. + /// + /// The platform's rules are NOT re-checked on the way out. Text that + /// no longer normalizes resolves to nobody, so the holder check + /// already refuses it, and re-checking would only replace an honest + /// `NotTheHolder` with a misleading `UnusableHandle`. + /// + /// @param recipient Where the value goes. The claimer's choice, so a wallet + /// that holds the name can pay out somewhere else. + function claim(bytes32 platformId, string calldata handle, address token, address recipient) external nonReentrant { + if (recipient == address(0) || recipient == address(this)) revert BadRecipient(recipient); + + bytes32 slot = slotOf(platformId, handle); + + address holder = _s().names.resolveHandle(platformId, handle); + if (holder != msg.sender) revert NotTheHolder(holder, msg.sender); + + uint256 amount = _s().held[slot][token]; + if (amount == 0) revert NothingHeld(slot, token); + _s().held[slot][token] = 0; + + if (token == NATIVE) { + _sendNative(recipient, amount); + } else { + IERC20(token).safeTransfer(recipient, amount); + } + emit Claimed(slot, token, msg.sender, recipient, amount); + } + + // ─── Reading ──────────────────────────────────────────────────── + + /// @notice How much is held for a handle in one token. + function escrowed(bytes32 platformId, string calldata handle, address token) external view returns (uint256) { + return _s().held[slotOf(platformId, handle)][token]; + } + + /// @notice The same, for a slot already computed. + /// + /// @dev Takes the slot as given and checks nothing about it, which is what + /// an indexer reading raw slots off the logs needs. A slot that was + /// never funded and a slot that could never exist both answer zero, + /// and this cannot tell them apart. Where the handle is known, use + /// `escrowed` — but note it applies only the refusals that KEYING + /// needs, not the platform's rules; text a deposit would refuse can + /// still be read for. + function escrowedAt(bytes32 slot, address token) external view returns (uint256) { + return _s().held[slot][token]; + } + + /// @notice The slot a handle keys to. + /// + /// @dev Public so an indexer, a Rust deployer and a browser can all reach + /// the same key, and so a caller can check two spellings land together + /// before it sends anything. + function slotOf(bytes32 platformId, string memory handle) public pure returns (bytes32) { + return keccak256(abi.encode(HANDLE_SLOT_V1, platformId, keccak256(bytes(_canonical(handle))))); + } + + /// @notice The form of a handle this contract keys on. + /// @dev Exposed so the transform can be checked against the naming system's + /// own by anybody, in any language. + function canonicalHandle(string memory handle) public pure returns (string memory) { + return _canonical(handle); + } + + function _sendNative(address to, uint256 amount) private { + (bool ok,) = to.call{value: amount}(""); + if (!ok) revert NativeTransferFailed(to, amount); + } + + // ─── The key ──────────────────────────────────────────────────── + + /// @dev The form a handle keys on, WITHOUT reading a platform's rules. + /// + /// Rules-independent on purpose. Deriving the slot from + /// `HandleNormalizer.normalize` would tie every held balance to the + /// configuration of the moment, and the owner narrowing a platform's + /// rules would move money already deposited onto keys nobody asks + /// about. What varies by platform belongs in the check on the way in, + /// not in the key. + /// + /// It is a PREFIX of the naming system's transform: trim ASCII spaces, + /// fold A-Z. Both steps are also the first steps of `tryNormalize`, + /// neither touches a byte the other cares about, and both are + /// idempotent — so for text the platform accepts, this returns exactly + /// what `normalize` returns, whatever the rules are. + /// + /// The one place the two could disagree is `stripLeadingAt`, which + /// folds an at-prefixed spelling and a bare one together in the naming + /// system. So this drops one leading at-sign too, and the two spellings + /// reach one slot instead of splitting one identity's money across two + /// keys with half of it unreachable. + function _canonical(string memory handle) private pure returns (string memory) { + bytes memory input = bytes(handle); + + uint256 start = 0; + uint256 end = input.length; + while (start < end && input[start] == 0x20) { + start++; + } + while (end > start && input[end - 1] == 0x20) { + end--; + } + + // Drop ONE leading at-sign, exactly as `stripLeadingAt` does. Doing it + // unconditionally is safe because no expressible `Rules` accepts a + // handle whose text starts with one: a non-email platform refuses + // `0x40` outright, and an email needs a name part before its `@`. So + // for a platform that strips, both spellings reach one slot and agree + // with the resolver; for one that does not, the spelling with the + // at-sign is refused by the rules check and never funds anything. + // + // The invariant to keep: a platform must never accept a handle + // beginning with an at-sign. If one ever could, `@x` and `x` would be + // two identities sharing a slot. + if (start < end && input[start] == 0x40) { + start++; + } + + uint256 length = end - start; + if (length == 0) revert UnusableHandle(HandleNormalizer.Problem.Empty); + + bytes memory out = new bytes(length); + for (uint256 i = 0; i < length; i++) { + bytes1 c = input[start + i]; + if (c >= 0x41 && c <= 0x5A) { + c = bytes1(uint8(c) + 0x20); + } + out[i] = c; + } + return string(out); + } + + /// @dev The slot for text the platform can actually accept. + /// + /// Both halves matter. `rulesOf` reverts for a platform that is not + /// wired, which catches a mistyped `platformId` before it takes + /// anybody's money. `tryNormalize` then decides whether this text + /// could ever be a handle there, which is the question `resolveHandle` + /// cannot answer: it returns the zero address for a handle nobody + /// holds and for text nobody could hold, and those two must not be + /// confused when money is about to move. + function _requireUsableSlot(bytes32 platformId, string calldata handle) private view returns (bytes32) { + HandleNormalizer.Rules memory rules = _s().names.rulesOf(platformId); + (HandleNormalizer.Problem problem,) = HandleNormalizer.tryNormalize(handle, rules); + if (problem != HandleNormalizer.Problem.None) revert UnusableHandle(problem); + return slotOf(platformId, handle); + } + + // ─── Upgrade ──────────────────────────────────────────────────── + + function _authorizeUpgrade(address) internal override onlyOwner {} + + /// @dev Renouncing would leave no way to repair a broken deployment, and + /// the upgrade is the only lever there is. + function renounceOwnership() public pure override { + revert("renounce disabled"); + } +} diff --git a/solidity/contracts/escrow/IIdentityNames.sol b/solidity/contracts/escrow/IIdentityNames.sol new file mode 100644 index 0000000..c1a13bd --- /dev/null +++ b/solidity/contracts/escrow/IIdentityNames.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {HandleNormalizer} from "../identity/HandleNormalizer.sol"; + +/// @notice The two questions the escrow asks the naming system. +/// +/// @dev A local interface rather than an import of the whole contract, the way +/// `IIdentityVerifier`, `IRegistry` and `INotary` are declared where they +/// are used. Two functions do not justify pulling in the naming contract's +/// whole surface, and a narrow interface says exactly what the escrow +/// depends on. +/// +/// `HandleNormalizer.Rules` IS imported rather than redeclared. A copy +/// would be a second definition of the struct the naming system stores, +/// and the two would drift the first time a field is added. The escrow +/// needs the library anyway, to run the same transform on the text a +/// depositor supplies. +interface IIdentityNames { + /// @notice The wallet that last proved this handle, or the zero address. + /// + /// @dev Zero also means "nobody holds it any more": a handle whose account + /// renamed away is retired, and reads back with no owner. + function resolveHandle(bytes32 platformId, string calldata handle) external view returns (address); + + /// @notice How this platform's handles normalize, as configured now. + /// @dev Reverts for a platform that is not wired. + function rulesOf(bytes32 platformId) external view returns (HandleNormalizer.Rules memory); +} diff --git a/solidity/contracts/escrow/test/HandleEscrow.t.sol b/solidity/contracts/escrow/test/HandleEscrow.t.sol new file mode 100644 index 0000000..9d1dd3c --- /dev/null +++ b/solidity/contracts/escrow/test/HandleEscrow.t.sol @@ -0,0 +1,861 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {Test} from "forge-std/Test.sol"; +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +import { + ReentrancyGuardTransientUpgradeable +} from "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardTransientUpgradeable.sol"; + +import {HandleNormalizer} from "../../identity/HandleNormalizer.sol"; +import {HandleVectors} from "../../identity/HandleVectors.sol"; +import {IdentityNames} from "../../identity/IdentityNames.sol"; +import {IIdentityVerifier} from "../../identity/IIdentityVerifier.sol"; +import {MockIdentityVerifier} from "../../identity/test/MockIdentityVerifier.sol"; +import {MockERC20} from "../../transfer/MockERC20.sol"; +import {HandleEscrow} from "../HandleEscrow.sol"; +import {IIdentityNames} from "../IIdentityNames.sol"; + +/// @notice Takes a cut of every transfer, the way a fee-on-transfer token does. +contract FeeToken is MockERC20 { + uint256 public constant FEE_BPS = 100; // 1% + + constructor() MockERC20("Fee", "FEE") {} + + function transferFrom(address from, address to, uint256 amount) public override returns (bool) { + uint256 fee = (amount * FEE_BPS) / 10_000; + _transfer(from, address(0xdead), fee); + _transfer(from, to, amount - fee); + _spendAllowance(from, msg.sender, amount); + return true; + } +} + +/// @notice A second version that APPENDS to the namespaced root, which is the +/// only change the storage rule allows. +/// +/// @dev Upgrading to a byte-identical implementation proves nothing about the +/// layout. This adds a field after the existing ones and reads the old +/// ones back, so a reordered or removed field shows up as a wrong balance +/// rather than as a passing test. +contract HandleEscrowV2 is HandleEscrow { + /// @custom:storage-location erc7201:libid.storage.HandleEscrow + struct V2Storage { + mapping(bytes32 => mapping(address => uint256)) held; + IIdentityNames names; + uint256 appended; + } + + function _v2() private pure returns (V2Storage storage $) { + assembly { + $.slot := 0xfcca8d7d2c66f78c2760f3fcd99e0bf938b0aeb0d0b471f481dd50b8aff6b400 + } + } + + function setAppended(uint256 v) external { + _v2().appended = v; + } + + function appended() external view returns (uint256) { + return _v2().appended; + } + + /// Read the pre-existing fields through the V2 layout. + function heldThroughV2(bytes32 slot, address token) external view returns (uint256) { + return _v2().held[slot][token]; + } + + function namesThroughV2() external view returns (address) { + return address(_v2().names); + } +} + +/// @notice Refuses every native transfer. +contract RejectEther { + // No receive, no fallback. +} + +/// @notice Calls `deposit` again from inside its own transfer, the way a token +/// with a receiver hook does. +/// +/// @dev This is what the guard on `deposit` is for. The credit is the balance +/// the contract GAINED, measured across the transfer — so a transfer that +/// re-enters and deposits again folds the inner deposit's tokens into the +/// outer one's measurement, and the books end up promising more than the +/// contract holds. +contract ReenteringToken is MockERC20 { + HandleEscrow public escrow; + bytes32 public platformId; + bool private entered; + + constructor() MockERC20("Hook", "HOOK") {} + + function arm(HandleEscrow escrow_, bytes32 platformId_) external { + escrow = escrow_; + platformId = platformId_; + _approve(address(this), address(escrow_), type(uint256).max); + } + + function transferFrom(address from, address to, uint256 amount) public override returns (bool) { + bool ok = super.transferFrom(from, to, amount); + if (!entered && address(escrow) != address(0)) { + entered = true; + escrow.deposit(platformId, "bob", address(this), 1 ether); + } + return ok; + } +} + +/// @notice Calls `claim` again from inside the native payout. +contract ReenteringClaimer { + HandleEscrow private immutable escrow; + bytes32 private immutable platformId; + string private handle; + bool private entered; + + constructor(HandleEscrow escrow_, bytes32 platformId_, string memory handle_) { + escrow = escrow_; + platformId = platformId_; + handle = handle_; + } + + function take() external { + escrow.claim(platformId, handle, address(0), address(this)); + } + + receive() external payable { + if (entered) return; + entered = true; + escrow.claim(platformId, handle, address(0), address(this)); + } +} + +/// @notice The handle-keyed escrow, against the real naming system. +/// +/// @dev Wired to a real `IdentityNames` behind its proxy rather than a mock, so +/// a rename and a recycled handle are stageable exactly as they happen. +contract HandleEscrowTest is Test { + IdentityNames internal names; + MockIdentityVerifier internal verifier; + HandleEscrow internal escrow; + MockERC20 internal token; + + bytes32 internal constant X = HandleVectors.PLATFORM_X; + bytes32 internal constant GITHUB = HandleVectors.PLATFORM_GITHUB; + bytes32 internal constant UNWIRED = keccak256("no such platform"); + + address internal alice = makeAddr("alice"); + address internal bob = makeAddr("bob"); + address internal sender = makeAddr("sender"); + address internal owner = makeAddr("owner"); + + uint32 internal constant V1 = 1; + uint64 internal constant NO_FUTURE_ALLOWANCE = 0; + address internal constant NATIVE = address(0); + + function setUp() public { + IdentityNames namesImpl = new IdentityNames(); + names = IdentityNames( + address(new ERC1967Proxy(address(namesImpl), abi.encodeCall(IdentityNames.initialize, (owner)))) + ); + verifier = new MockIdentityVerifier("mock"); + + vm.startPrank(owner); + names.setPlatform(X, HandleVectors.rulesFor(X)); + names.setVerifier(X, V1, IIdentityVerifier(address(verifier)), NO_FUTURE_ALLOWANCE); + names.setPlatform(GITHUB, HandleVectors.rulesFor(GITHUB)); + names.setVerifier(GITHUB, V1, IIdentityVerifier(address(verifier)), NO_FUTURE_ALLOWANCE); + vm.stopPrank(); + + HandleEscrow escrowImpl = new HandleEscrow(); + escrow = HandleEscrow( + address( + new ERC1967Proxy( + address(escrowImpl), + abi.encodeCall(HandleEscrow.initialize, (owner, IIdentityNames(address(names)))) + ) + ) + ); + + token = new MockERC20("Token", "TKN"); + token.mint(sender, 1_000 ether); + vm.prank(sender); + token.approve(address(escrow), type(uint256).max); + + vm.deal(sender, 100 ether); + vm.warp(1_000_000); + } + + /// Prove `handle` for `who`, the way a login does. + function _bind(address who, string memory userId, string memory handle, uint64 at) internal { + verifier.stage(userId, handle, who, at); + vm.prank(who); + names.bind(X, hex"", false); + } + + function _depositNative(string memory handle, uint256 amount) internal { + vm.prank(sender); + escrow.deposit{value: amount}(X, handle, NATIVE, amount); + } + + // ─── The key ──────────────────────────────────────────────────── + + /// The lemma the whole design rests on: for every handle the naming system + /// accepts, the escrow's key of the raw text and of the normalized text are + /// the SAME slot. If they ever differed, one identity's money would be + /// split across two keys and half of it would be unreachable. + function test_everyAcceptedVectorKeysWithItsNormalizedForm() public view { + HandleVectors.Vector[] memory vectors = HandleVectors.all(); + for (uint256 i = 0; i < vectors.length; i++) { + HandleVectors.Vector memory v = vectors[i]; + if (!v.accepted) continue; + + bytes32 platformId = _platformIdFor(v.platform); + assertEq( + escrow.slotOf(platformId, v.input), + escrow.slotOf(platformId, v.output), + string.concat("vector ", vm.toString(i), " keys differently from its normalized form") + ); + } + } + + /// A literal, so Rust and TypeScript cannot compute a different key and + /// still pass their own tests. + /// + /// Derived from the formula rather than copied out of this contract: + /// keccak256(keccak256("libid.escrow.handle-slot.v1") + /// ‖ keccak256("dyaka.identity.platform.x") + /// ‖ keccak256("alice_1")) + function test_theSlotDerivationIsPinned() public view { + assertEq(escrow.slotOf(X, " Alice_1 "), escrow.slotOf(X, "alice_1")); + assertEq(escrow.slotOf(X, "alice_1"), 0x48d2123d2510af6875a95858dec72232eba05885e8773f13d56b792d6cccc7e2); + } + + function test_theCanonicalFormTrimsAndFolds() public view { + assertEq(escrow.canonicalHandle(" ALICE "), "alice"); + assertEq(escrow.canonicalHandle("Alice_1"), "alice_1"); + // Nothing else is touched: two texts that differ in more than case and + // padding stay two handles. + assertEq(escrow.canonicalHandle("ali-ce"), "ali-ce"); + } + + /// The naming system folds an at-prefixed spelling into the bare handle, + /// so this must too — otherwise one identity's money splits across two + /// keys and half of it is unreachable. + function test_aLeadingAtSignFoldsIntoTheBareHandle() public { + assertEq(escrow.slotOf(X, "@alice"), escrow.slotOf(X, "alice")); + assertEq(escrow.slotOf(X, " @Alice "), escrow.slotOf(X, "alice")); + assertEq(escrow.canonicalHandle("@alice"), "alice"); + // One at-sign, the same as `stripLeadingAt`. A second is a character + // the platform refuses, so the deposit below never lands. + assertEq(escrow.canonicalHandle("@@alice"), "@alice"); + + // Both spellings pay into the same slot, and reading either sees both. + vm.startPrank(sender); + escrow.deposit{value: 1 ether}(X, "@alice", NATIVE, 1 ether); + escrow.deposit{value: 2 ether}(X, "alice", NATIVE, 2 ether); + vm.stopPrank(); + assertEq(escrow.escrowed(X, "@alice", NATIVE), 3 ether); + assertEq(escrow.escrowed(X, "alice", NATIVE), 3 ether); + } + + /// The read pairs with `resolveHandle` on whatever text a user typed: the + /// naming system answers for an at-prefixed spelling, so this must not + /// revert on it. + function test_readingPairsWithTheResolverOnEitherSpelling() public { + // Escrowed first, while nobody holds it — a deposit after the bind + // would pay through and leave nothing to read. + _depositNative("alice", 1 ether); + _bind(alice, "1", "alice", 100); + + assertEq(names.resolveHandle(X, "@alice"), alice); + assertEq(escrow.escrowed(X, "@alice", NATIVE), 1 ether); + } + + /// Text with nothing left after trimming and the at-sign has no slot. + function test_aBareAtSignHasNoSlot() public { + vm.expectRevert(abi.encodeWithSelector(HandleEscrow.UnusableHandle.selector, HandleNormalizer.Problem.Empty)); + escrow.slotOf(X, " @ "); + } + + function test_textWithNothingInItHasNoSlot() public { + vm.expectRevert(abi.encodeWithSelector(HandleEscrow.UnusableHandle.selector, HandleNormalizer.Problem.Empty)); + escrow.slotOf(X, " "); + } + + /// Different platforms are different keyspaces, so the same text on two of + /// them is two slots. + function test_theSlotIsPerPlatform() public view { + assertTrue(escrow.slotOf(X, "alice") != escrow.slotOf(GITHUB, "alice")); + } + + /// The platform id is load-bearing in the key, not decoration: the same + /// text on two platforms is two different people, and their money must not + /// meet. Proving it on one platform reaches only that one's slot. + function test_theSameTextOnTwoPlatformsIsTwoEscrows() public { + vm.startPrank(sender); + escrow.deposit{value: 1 ether}(X, "alice", NATIVE, 1 ether); + escrow.deposit{value: 2 ether}(GITHUB, "alice", NATIVE, 2 ether); + vm.stopPrank(); + + _bind(alice, "1", "alice", 100); // on X only + + vm.prank(alice); + escrow.claim(X, "alice", NATIVE, alice); + assertEq(alice.balance, 1 ether); + + vm.prank(alice); + vm.expectRevert(abi.encodeWithSelector(HandleEscrow.NotTheHolder.selector, address(0), alice)); + escrow.claim(GITHUB, "alice", NATIVE, alice); + + assertEq(escrow.escrowed(GITHUB, "alice", NATIVE), 2 ether, "the other platform's escrow moved"); + } + + /// Why the at-sign is refused rather than handled: the naming system reads + /// both spellings as ONE handle. Keying them apart would split one + /// identity's money across two slots, and only one of them would ever be + /// claimable. + function test_theNamingSystemReadsBothSpellingsAsOneHandle() public { + _bind(alice, "1", "@alice", 100); + + assertEq(names.resolveHandle(X, "alice"), alice); + assertEq(names.resolveHandle(X, "@alice"), alice); + } + + // ─── Depositing ───────────────────────────────────────────────── + + function test_depositHoldsNativeAgainstTheHandle() public { + _depositNative("alice", 1 ether); + + assertEq(escrow.escrowed(X, "alice", NATIVE), 1 ether); + assertEq(address(escrow).balance, 1 ether); + } + + function test_depositHoldsTokensAgainstTheHandle() public { + vm.prank(sender); + escrow.deposit(X, "alice", address(token), 10 ether); + + assertEq(escrow.escrowed(X, "alice", address(token)), 10 ether); + assertEq(token.balanceOf(address(escrow)), 10 ether); + } + + /// Two spellings of one handle land in one slot and add up. + function test_spellingsOfOneHandleAccumulate() public { + _depositNative("alice", 1 ether); + _depositNative(" ALICE ", 2 ether); + + assertEq(escrow.escrowed(X, "alice", NATIVE), 3 ether); + } + + /// The books must never promise more than the contract holds. + function test_aFeeOnTransferTokenCreditsWhatArrived() public { + FeeToken fee = new FeeToken(); + fee.mint(sender, 100 ether); + vm.startPrank(sender); + fee.approve(address(escrow), type(uint256).max); + escrow.deposit(X, "alice", address(fee), 100 ether); + vm.stopPrank(); + + assertEq(escrow.escrowed(X, "alice", address(fee)), 99 ether, "credited more than arrived"); + assertEq(fee.balanceOf(address(escrow)), 99 ether); + } + + /// A handle nobody holds yet is the whole point. + function test_depositingForAnUnclaimedHandleIsFine() public { + assertEq(names.resolveHandle(X, "nobody"), address(0)); + _depositNative("nobody", 1 ether); + assertEq(escrow.escrowed(X, "nobody", NATIVE), 1 ether); + } + + /// The escrow exists for the window before a handle is claimed. Once it + /// resolves, holding the value would only add a claim transaction to reach + /// the same wallet, so the deposit is a payment. + function test_depositForAHeldHandleIsPaidStraightThrough() public { + _bind(alice, "1", "alice", 100); + + uint256 before = alice.balance; + _depositNative("alice", 1 ether); + + assertEq(alice.balance, before + 1 ether, "the holder was not paid"); + assertEq(escrow.escrowed(X, "alice", NATIVE), 0, "the value was escrowed instead"); + assertEq(address(escrow).balance, 0, "the escrow kept it"); + } + + function test_aForwardedDepositIsAnnouncedAsSuch() public { + _bind(alice, "1", "alice", 100); + bytes32 slot = escrow.slotOf(X, "alice"); + + vm.expectEmit(true, true, true, true, address(escrow)); + emit HandleEscrow.Forwarded(slot, NATIVE, sender, alice, X, "alice", 1 ether); + vm.prank(sender); + escrow.deposit{value: 1 ether}(X, "alice", NATIVE, 1 ether); + } + + function test_tokensForAHeldHandleGoStraightToTheHolder() public { + _bind(alice, "1", "alice", 100); + + vm.prank(sender); + escrow.deposit(X, "alice", address(token), 10 ether); + + assertEq(token.balanceOf(alice), 10 ether, "the holder was not paid"); + assertEq(token.balanceOf(address(escrow)), 0, "the escrow kept tokens"); + assertEq(escrow.escrowed(X, "alice", address(token)), 0); + } + + /// The price of paying through: the call depends on the recipient. Failing + /// is the honest outcome — the sender learns, instead of the value waiting + /// in a slot only that same wallet could ever claim. + function test_aHolderThatCannotReceiveFailsTheDeposit() public { + address rejector = address(new RejectEther()); + _bind(rejector, "1", "alice", 100); + + vm.prank(sender); + vm.expectRevert(abi.encodeWithSelector(HandleEscrow.NativeTransferFailed.selector, rejector, 1 ether)); + escrow.deposit{value: 1 ether}(X, "alice", NATIVE, 1 ether); + } + + /// The window the escrow is for: deposit while unclaimed, and the same + /// handle pays through once it is claimed. + function test_theSameHandleEscrowsThenPaysThrough() public { + _depositNative("alice", 1 ether); + assertEq(escrow.escrowed(X, "alice", NATIVE), 1 ether); + + _bind(alice, "1", "alice", 100); + + uint256 before = alice.balance; + _depositNative("alice", 2 ether); + assertEq(alice.balance, before + 2 ether, "the second deposit did not pay through"); + // The first one still waits for its claim. + assertEq(escrow.escrowed(X, "alice", NATIVE), 1 ether, "the waiting balance moved"); + } + + function test_aDepositOfNothingIsRefused() public { + vm.prank(sender); + vm.expectRevert(HandleEscrow.ZeroAmount.selector); + escrow.deposit(X, "alice", NATIVE, 0); + } + + function test_nativeValueMustEqualTheAmount() public { + vm.prank(sender); + vm.expectRevert(abi.encodeWithSelector(HandleEscrow.ValueMismatch.selector, 2 ether, 1 ether)); + escrow.deposit{value: 1 ether}(X, "alice", NATIVE, 2 ether); + } + + /// Ether sent alongside a token deposit has no slot to land in. + function test_aTokenDepositCarriesNoValue() public { + vm.prank(sender); + vm.expectRevert(abi.encodeWithSelector(HandleEscrow.ValueMismatch.selector, 0, 1 ether)); + escrow.deposit{value: 1 ether}(X, "alice", address(token), 10 ether); + } + + /// A mistyped platform id takes nobody's money. + function test_anUnwiredPlatformIsRefused() public { + vm.prank(sender); + vm.expectRevert(abi.encodeWithSelector(IdentityNames.UnknownPlatform.selector, UNWIRED)); + escrow.deposit{value: 1 ether}(UNWIRED, "alice", NATIVE, 1 ether); + } + + /// The reason `rulesOf` exists. Text this platform could never accept would + /// otherwise fund a slot no proof can ever claim, and there is no refund. + function test_aHandleThePlatformCouldNeverAcceptIsRefused() public { + vm.startPrank(sender); + + // A space inside is not a handle on X. + vm.expectRevert(abi.encodeWithSelector(HandleEscrow.UnusableHandle.selector, HandleNormalizer.Problem.BadChar)); + escrow.deposit{value: 1 ether}(X, "ali ce", NATIVE, 1 ether); + + // A hyphen is GitHub's, not X's. + vm.expectRevert(abi.encodeWithSelector(HandleEscrow.UnusableHandle.selector, HandleNormalizer.Problem.BadChar)); + escrow.deposit{value: 1 ether}(X, "ali-ce", NATIVE, 1 ether); + + // Past X's length. + vm.expectRevert(abi.encodeWithSelector(HandleEscrow.UnusableHandle.selector, HandleNormalizer.Problem.TooLong)); + escrow.deposit{value: 1 ether}(X, "a123456789012345", NATIVE, 1 ether); + + vm.stopPrank(); + } + + // ─── Claiming ─────────────────────────────────────────────────── + + function test_theHolderTakesWhatIsHeld() public { + _depositNative("alice", 1 ether); + _bind(alice, "1", "alice", 100); + + vm.prank(alice); + escrow.claim(X, "alice", NATIVE, alice); + + assertEq(alice.balance, 1 ether); + assertEq(escrow.escrowed(X, "alice", NATIVE), 0); + assertEq(address(escrow).balance, 0); + } + + /// The claimer names where it goes, so a wallet that holds the name can pay + /// out somewhere else. + function test_theClaimerChoosesTheRecipient() public { + _depositNative("alice", 1 ether); + _bind(alice, "1", "alice", 100); + + vm.prank(alice); + escrow.claim(X, "alice", NATIVE, bob); + + assertEq(bob.balance, 1 ether); + assertEq(alice.balance, 0); + } + + function test_aClaimTakesOnlyTheTokenItNames() public { + _depositNative("alice", 1 ether); + vm.prank(sender); + escrow.deposit(X, "alice", address(token), 10 ether); + _bind(alice, "1", "alice", 100); + + vm.prank(alice); + escrow.claim(X, "alice", NATIVE, alice); + + assertEq(escrow.escrowed(X, "alice", NATIVE), 0); + assertEq(escrow.escrowed(X, "alice", address(token)), 10 ether, "the token balance moved too"); + } + + /// Any spelling of the handle reaches the same slot. + function test_aClaimCanUseAnySpelling() public { + _depositNative("alice", 1 ether); + _bind(alice, "1", "alice", 100); + + vm.prank(alice); + escrow.claim(X, " ALICE ", NATIVE, alice); + + assertEq(alice.balance, 1 ether); + } + + function test_somebodyElseCannotClaim() public { + _depositNative("alice", 1 ether); + _bind(alice, "1", "alice", 100); + + vm.prank(bob); + vm.expectRevert(abi.encodeWithSelector(HandleEscrow.NotTheHolder.selector, alice, bob)); + escrow.claim(X, "alice", NATIVE, bob); + } + + /// Nobody holds it yet, so nobody can take it. The value waits. + function test_anUnclaimedHandleCannotBeDrained() public { + _depositNative("alice", 1 ether); + + vm.prank(bob); + vm.expectRevert(abi.encodeWithSelector(HandleEscrow.NotTheHolder.selector, address(0), bob)); + escrow.claim(X, "alice", NATIVE, bob); + + assertEq(escrow.escrowed(X, "alice", NATIVE), 1 ether); + } + + function test_claimingAnEmptySlotIsRefused() public { + _bind(alice, "1", "alice", 100); + // Read the slot BEFORE the prank: an external call inside the revert + // argument is itself the next call and would consume it. + bytes32 slot = escrow.slotOf(X, "alice"); + + vm.prank(alice); + vm.expectRevert(abi.encodeWithSelector(HandleEscrow.NothingHeld.selector, slot, NATIVE)); + escrow.claim(X, "alice", NATIVE, alice); + } + + function test_aRecipientThatRefusesNativeValueFailsTheClaim() public { + _depositNative("alice", 1 ether); + _bind(alice, "1", "alice", 100); + address rejector = address(new RejectEther()); + + vm.prank(alice); + vm.expectRevert(abi.encodeWithSelector(HandleEscrow.NativeTransferFailed.selector, rejector, 1 ether)); + escrow.claim(X, "alice", NATIVE, rejector); + } + + /// A token that calls back inside its own transfer would otherwise have the + /// outer deposit credit the inner deposit's tokens as well — two slots + /// funded by one transfer, and more promised than held. + function test_aTokenThatReentersDepositIsRefused() public { + ReenteringToken hook = new ReenteringToken(); + hook.mint(sender, 100 ether); + hook.mint(address(hook), 10 ether); + hook.arm(escrow, X); + + vm.startPrank(sender); + hook.approve(address(escrow), type(uint256).max); + // The guard specifically, not any revert: a mis-staged token or a + // missing approval would also revert and would also look green. + vm.expectRevert( + abi.encodeWithSelector(ReentrancyGuardTransientUpgradeable.ReentrancyGuardReentrantCall.selector) + ); + escrow.deposit(X, "alice", address(hook), 100 ether); + vm.stopPrank(); + + assertEq(escrow.escrowed(X, "alice", address(hook)), 0); + assertEq(escrow.escrowed(X, "bob", address(hook)), 0); + assertEq(hook.balanceOf(address(escrow)), 0, "the escrow kept tokens it never credited"); + } + + /// The payout is an external call to an address the claimer chose. + /// + /// Somebody else's escrow is funded alongside, and that is the point: a + /// second drain is only observable when the contract holds more than the + /// claimed slot. Without it, the second transfer runs out of balance and + /// fails for a reason that has nothing to do with reentrancy. + function test_aReenteringClaimerCannotDrainTwice() public { + ReenteringClaimer claimer = new ReenteringClaimer(escrow, X, "alice"); + _depositNative("alice", 1 ether); + _depositNative("bob", 1 ether); // not the claimer's + _bind(address(claimer), "1", "alice", 100); + + vm.expectRevert(abi.encodeWithSelector(HandleEscrow.NativeTransferFailed.selector, address(claimer), 1 ether)); + claimer.take(); + + assertEq(escrow.escrowed(X, "alice", NATIVE), 1 ether, "the slot was drained"); + assertEq(escrow.escrowed(X, "bob", NATIVE), 1 ether, "somebody else's escrow moved"); + assertEq(address(escrow).balance, 2 ether); + assertEq(address(claimer).balance, 0, "the claimer took anything at all"); + } + + /// A payout to nobody is not a payout. The zero address ACCEPTS a native + /// transfer, so without this the slot would be emptied, the value burned + /// and `Claimed` would report success. + function test_aClaimToNobodyIsRefused() public { + _depositNative("alice", 1 ether); + _bind(alice, "1", "alice", 100); + + vm.prank(alice); + vm.expectRevert(abi.encodeWithSelector(HandleEscrow.BadRecipient.selector, address(0))); + escrow.claim(X, "alice", NATIVE, address(0)); + + assertEq(escrow.escrowed(X, "alice", NATIVE), 1 ether, "the slot was emptied"); + assertEq(address(0).balance, 0, "value was burned"); + } + + /// A payout to this contract would zero the books and leave the value here + /// as surplus no slot points at — unreachable, with no refund and no owner + /// lever. + function test_aClaimBackIntoTheEscrowIsRefused() public { + vm.prank(sender); + escrow.deposit(X, "alice", address(token), 10 ether); + _bind(alice, "1", "alice", 100); + + vm.prank(alice); + vm.expectRevert(abi.encodeWithSelector(HandleEscrow.BadRecipient.selector, address(escrow))); + escrow.claim(X, "alice", address(token), address(escrow)); + + assertEq(escrow.escrowed(X, "alice", address(token)), 10 ether); + assertEq(token.balanceOf(address(escrow)), 10 ether); + } + + /// The ERC-20 payout branch, which no other test reaches: every other + /// claim in this suite takes the native token. + function test_aTokenClaimPaysTheRecipient() public { + vm.prank(sender); + escrow.deposit(X, "alice", address(token), 10 ether); + _bind(alice, "1", "alice", 100); + + vm.prank(alice); + escrow.claim(X, "alice", address(token), bob); + + assertEq(token.balanceOf(bob), 10 ether, "the recipient was not paid"); + assertEq(token.balanceOf(address(escrow)), 0, "the escrow kept tokens"); + assertEq(escrow.escrowed(X, "alice", address(token)), 0); + } + + /// `Deposited` is the only on-chain record tying a slot back to a handle — + /// a slot cannot be turned back into a string — so its payload is worth an + /// assertion of its own. + function test_depositAndClaimAnnounceTheirPayloads() public { + bytes32 slot = escrow.slotOf(X, "alice"); + + vm.expectEmit(true, true, true, true, address(escrow)); + emit HandleEscrow.Deposited(slot, NATIVE, sender, X, "alice", 1 ether); + vm.prank(sender); + escrow.deposit{value: 1 ether}(X, "alice", NATIVE, 1 ether); + + _bind(alice, "1", "alice", 100); + + vm.expectEmit(true, true, true, true, address(escrow)); + emit HandleEscrow.Claimed(slot, NATIVE, alice, bob, 1 ether); + vm.prank(alice); + escrow.claim(X, "alice", NATIVE, bob); + } + + /// A fee-on-transfer token delivers less than was asked for, and the event + /// is the only record of a payment that never entered the books. + function test_aForwardedPaymentReportsWhatArrived() public { + FeeToken fee = new FeeToken(); + fee.mint(sender, 100 ether); + _bind(alice, "1", "alice", 100); + + vm.startPrank(sender); + fee.approve(address(escrow), type(uint256).max); + vm.expectEmit(true, true, true, true, address(escrow)); + emit HandleEscrow.Forwarded(escrow.slotOf(X, "alice"), address(fee), sender, alice, X, "alice", 99 ether); + escrow.deposit(X, "alice", address(fee), 100 ether); + vm.stopPrank(); + + assertEq(fee.balanceOf(alice), 99 ether, "the holder received something else"); + } + + // ─── Consequences accepted on purpose ─────────────────────────── + + /// NOT a vulnerability. The escrow is keyed by the handle and nothing else, + /// so a platform that frees a handle and gives it to somebody new hands the + /// new holder whatever accumulated for the old one. This is the decision, + /// and this test exists so changing it fails here. + function test_ACCEPTED_aRecycledHandlePaysTheNewHolder() public { + // Escrowed while nobody held it, and never claimed. + _depositNative("alice", 1 ether); + _bind(alice, "1", "alice", 100); + + // The platform frees the handle: alice renames away, bob's account + // takes it. + _bind(alice, "1", "alice2", 200); + _bind(bob, "2", "alice", 300); + + vm.prank(bob); + escrow.claim(X, "alice", NATIVE, bob); + + assertEq(bob.balance, 1 ether, "the new holder did not receive it"); + } + + /// NOT a vulnerability. Between a rename and the next proof of the freed + /// handle, the handle resolves to nobody and the slot waits — including + /// against the account that just renamed away from it. + function test_ACCEPTED_aRenamedAwayHandleIsClaimableByNobody() public { + _depositNative("alice", 1 ether); + _bind(alice, "1", "alice", 100); + _bind(alice, "1", "alice2", 200); + + vm.prank(alice); + vm.expectRevert(abi.encodeWithSelector(HandleEscrow.NotTheHolder.selector, address(0), alice)); + escrow.claim(X, "alice", NATIVE, alice); + + // And it did not follow the account to its new name. + assertEq(escrow.escrowed(X, "alice2", NATIVE), 0, "the balance followed the account"); + assertEq(escrow.escrowed(X, "alice", NATIVE), 1 ether); + } + + /// NOT a vulnerability. A deposit is a gift to a name, so the sender has no + /// way back. There is no deadline and no refund by decision. + function test_ACCEPTED_theDepositorCannotTakeItBack() public { + _depositNative("alice", 1 ether); + + vm.prank(sender); + vm.expectRevert(abi.encodeWithSelector(HandleEscrow.NotTheHolder.selector, address(0), sender)); + escrow.claim(X, "alice", NATIVE, sender); + + assertEq(escrow.escrowed(X, "alice", NATIVE), 1 ether); + } + + /// The owner is not a way in either: there is no owner function that moves + /// a balance, and being the owner does not make it the holder. + function test_theOwnerCannotTakeADeposit() public { + _depositNative("alice", 1 ether); + + vm.prank(owner); + vm.expectRevert(abi.encodeWithSelector(HandleEscrow.NotTheHolder.selector, address(0), owner)); + escrow.claim(X, "alice", NATIVE, owner); + } + + // ─── Wiring ───────────────────────────────────────────────────── + + /// Repointing it would redirect every entitlement held, so there is no + /// setter. Moving it is an upgrade, which leaves a record. + function test_theNamingContractIsReadableAndHasNoSetter() public view { + assertEq(address(escrow.names()), address(names)); + } + + function test_initializeRefusesAZeroNamingContract() public { + HandleEscrow impl = new HandleEscrow(); + vm.expectRevert(HandleEscrow.NoNames.selector); + new ERC1967Proxy(address(impl), abi.encodeCall(HandleEscrow.initialize, (owner, IIdentityNames(address(0))))); + } + + function test_ownershipCannotBeRenounced() public { + vm.prank(owner); + vm.expectRevert("renounce disabled"); + escrow.renounceOwnership(); + } + + function test_onlyTheOwnerMayUpgrade() public { + HandleEscrow next = new HandleEscrow(); + + vm.prank(bob); + vm.expectRevert(); + escrow.upgradeToAndCall(address(next), ""); + + vm.prank(owner); + escrow.upgradeToAndCall(address(next), ""); + } + + /// The balances have to survive it, or the upgrade is the theft the pause + /// was refused to avoid. + function test_balancesSurviveAnUpgrade() public { + _depositNative("alice", 1 ether); + bytes32 slot = escrow.slotOf(X, "alice"); + // A version that APPENDS a field, not a copy of the same bytecode: a + // byte-identical upgrade cannot detect a reordered or removed one, + // which is the only mistake this test exists to catch. + HandleEscrowV2 next = new HandleEscrowV2(); + + vm.prank(owner); + escrow.upgradeToAndCall(address(next), ""); + + HandleEscrowV2 upgraded = HandleEscrowV2(payable(address(escrow))); + assertEq(upgraded.heldThroughV2(slot, NATIVE), 1 ether, "the balance moved under the new layout"); + assertEq(upgraded.namesThroughV2(), address(names), "the naming pointer moved"); + assertEq(upgraded.appended(), 0, "the appended field read somebody else's bytes"); + + // The old surface still answers, and the new field is its own slot. + assertEq(escrow.escrowed(X, "alice", NATIVE), 1 ether); + upgraded.setAppended(7); + assertEq(upgraded.appended(), 7); + assertEq(escrow.escrowed(X, "alice", NATIVE), 1 ether, "writing the new field disturbed a balance"); + } + + /// The load-bearing claim of the whole design: a rules change cannot MOVE + /// money already held. Nothing else in this suite calls `setPlatform` + /// twice. + function test_ACCEPTED_aRulesChangeCannotMoveHeldValueButCanStrandIt() public { + _depositNative("alice_9", 1 ether); + bytes32 slot = escrow.slotOf(X, "alice_9"); + _bind(alice, "1", "alice_9", 100); + + // The owner narrows X: no underscore any more. + vm.prank(owner); + names.setPlatform( + X, + HandleNormalizer.Rules({ + maxLength: 15, stripLeadingAt: true, isEmail: false, allowUnderscore: false, allowHyphen: false + }) + ); + + // The key did not move — that is what the rules-independent form buys. + assertEq(escrow.slotOf(X, "alice_9"), slot, "the slot re-keyed"); + assertEq(escrow.escrowedAt(slot, NATIVE), 1 ether, "the value moved"); + + // And what it does NOT buy: the handle no longer resolves, so the + // holder cannot claim until compatible rules return. + assertEq(names.resolveHandle(X, "alice_9"), address(0)); + vm.prank(alice); + vm.expectRevert(abi.encodeWithSelector(HandleEscrow.NotTheHolder.selector, address(0), alice)); + escrow.claim(X, "alice_9", NATIVE, alice); + + // Restoring the rules restores the claim, at the same key. + vm.prank(owner); + names.setPlatform(X, HandleVectors.rulesFor(X)); + vm.prank(alice); + escrow.claim(X, "alice_9", NATIVE, alice); + assertEq(alice.balance, 1 ether); + } + + // ─── Helpers ──────────────────────────────────────────────────── + + function _platformIdFor(string memory platform) internal pure returns (bytes32) { + bytes32 key = keccak256(bytes(platform)); + if (key == keccak256("x")) return HandleVectors.PLATFORM_X; + if (key == keccak256("github")) return HandleVectors.PLATFORM_GITHUB; + if (key == keccak256("google")) return HandleVectors.PLATFORM_GOOGLE; + revert("unknown platform in the vector table"); + } +} diff --git a/solidity/contracts/identity/IdentityNames.sol b/solidity/contracts/identity/IdentityNames.sol index 2cc1731..8651ef6 100644 --- a/solidity/contracts/identity/IdentityNames.sol +++ b/solidity/contracts/identity/IdentityNames.sol @@ -463,6 +463,27 @@ contract IdentityNames is Initializable, UUPSUpgradeable, Ownable2StepUpgradeabl return _s().platforms[platformId].latestVersion; } + /// @notice How this platform's handles normalize, as configured right now. + /// + /// @dev The one part of `Platform` another contract has to be able to read. + /// A contract that keys on handles needs to ask whether some text + /// could be a handle here AT ALL, and `resolveHandle` cannot answer + /// that: it returns the zero address both for a handle nobody holds + /// and for text nobody could ever hold. Without this, such a contract + /// either accepts text that can never resolve, or keeps its own copy + /// of the rules and disagrees with this one the first time `setPlatform` + /// runs. + /// + /// Reverts for a platform that is not wired, like the resolvers do. + /// + /// What is read here is today's configuration, and the owner may + /// change it. A reader that stores anything derived from these rules + /// inherits the re-keying the contract comment describes, so store the + /// rules-independent form and read this only to validate. + function rulesOf(bytes32 platformId) external view returns (HandleNormalizer.Rules memory) { + return _requireUsable(platformId).rules; + } + // ─── Binding ──────────────────────────────────────────────────── /// @notice Prove an identity and bind it to the caller. diff --git a/solidity/contracts/identity/test/IdentityNames.t.sol b/solidity/contracts/identity/test/IdentityNames.t.sol index 97fd3ae..412ba09 100644 --- a/solidity/contracts/identity/test/IdentityNames.t.sol +++ b/solidity/contracts/identity/test/IdentityNames.t.sol @@ -318,6 +318,31 @@ contract IdentityNamesTest is Test { vm.expectRevert(abi.encodeWithSelector(IdentityNames.UnknownPlatform.selector, unwired)); names.resolvePair(unwired, "alice", "123"); + + vm.expectRevert(abi.encodeWithSelector(IdentityNames.UnknownPlatform.selector, unwired)); + names.rulesOf(unwired); + } + + /// `rulesOf` reports the configuration as it stands, so another contract + /// can ask whether text could be a handle here at all — the question + /// `resolveHandle` cannot answer, because it returns the zero address both + /// for a handle nobody holds and for text nobody could hold. + function test_rulesOfReportsThePlatformsCurrentRules() public { + HandleNormalizer.Rules memory rules = names.rulesOf(X); + HandleNormalizer.Rules memory expected = HandleVectors.rulesFor(X); + + assertEq(rules.maxLength, expected.maxLength); + assertEq(rules.stripLeadingAt, expected.stripLeadingAt); + assertEq(rules.isEmail, expected.isEmail); + assertEq(rules.allowUnderscore, expected.allowUnderscore); + assertEq(rules.allowHyphen, expected.allowHyphen); + + // It follows a reconfiguration, rather than reporting what was set + // when the platform was first wired. + vm.prank(owner); + names.setPlatform(X, HandleVectors.rulesFor(GITHUB)); + + assertEq(names.rulesOf(X).allowHyphen, true, "rulesOf did not follow setPlatform"); } function test_resolvePairAgreesWhileOneAccountHoldsBoth() public { diff --git a/solidity/script/Deploy.s.sol b/solidity/script/Deploy.s.sol index d334b72..d6d9e94 100644 --- a/solidity/script/Deploy.s.sol +++ b/solidity/script/Deploy.s.sol @@ -20,6 +20,8 @@ import {XIdentityVerifier} from "../contracts/identity/XIdentityVerifier.sol"; import {GitHubIdentityVerifier} from "../contracts/identity/GitHubIdentityVerifier.sol"; import {GoogleIdentityVerifier} from "../contracts/identity/GoogleIdentityVerifier.sol"; import {IdentityJwksRoots} from "../contracts/identity/IdentityJwksRoots.sol"; +import {HandleEscrow} from "../contracts/escrow/HandleEscrow.sol"; +import {IIdentityNames} from "../contracts/escrow/IIdentityNames.sol"; /// @notice Deploy full stack to any EVM chain. /// @@ -230,6 +232,19 @@ contract Deploy is Script, BankDiamondDeployer { _wireIdentityPlatform(names, HandleVectors.PLATFORM_GOOGLE, googleIdentityAddr); } + // 15. The handle escrow: value sent to a name before anybody claims it. + // + // Wired to the naming proxy and never repointed — moving it would + // redirect every entitlement it holds, so there is no setter and + // changing it is an upgrade. + HandleEscrow escrowImpl = new HandleEscrow(); + address handleEscrowAddr = address( + new ERC1967Proxy( + address(escrowImpl), + abi.encodeCall(HandleEscrow.initialize, (deployer, IIdentityNames(identityNamesAddr))) + ) + ); + vm.stopBroadcast(); console.log("=== Deployment complete ==="); @@ -247,6 +262,7 @@ contract Deploy is Script, BankDiamondDeployer { console.log("GITHUB_IDENTITY_VERIFIER_ADDRESS= ", githubIdentityAddr); console.log("GOOGLE_IDENTITY_VERIFIER_ADDRESS= ", googleIdentityAddr); console.log("IDENTITY_JWKS_ROOTS_ADDRESS= ", jwksRootsAddr); + console.log("HANDLE_ESCROW_ADDRESS= ", handleEscrowAddr); if (googleIdentityAddr != address(0)) { // Nothing is trusted until a notarized reading of Google's JWKS // lands. Until then every Google bind reverts `UntrustedModulus`, diff --git a/ts/packages/contracts/scripts/codegen.mjs b/ts/packages/contracts/scripts/codegen.mjs index e1b4cd3..72ef6b1 100644 --- a/ts/packages/contracts/scripts/codegen.mjs +++ b/ts/packages/contracts/scripts/codegen.mjs @@ -53,6 +53,7 @@ const contracts = [ name: 'GoogleOidcVerifier', exportName: 'googleOidcVerifierAbi', }, + { file: 'HandleEscrow.sol', name: 'HandleEscrow', exportName: 'handleEscrowAbi' }, { file: 'LibidFactory.sol', name: 'LibidFactory', exportName: 'libidFactoryAbi' }, { file: 'MockERC20.sol', name: 'MockERC20', exportName: 'mockErc20Abi' }, { file: 'WTIA9.sol', name: 'WTIA9', exportName: 'wtia9Abi' }, diff --git a/ts/packages/contracts/src/abis/handleEscrow.ts b/ts/packages/contracts/src/abis/handleEscrow.ts new file mode 100644 index 0000000..ffbaa57 --- /dev/null +++ b/ts/packages/contracts/src/abis/handleEscrow.ts @@ -0,0 +1,690 @@ +// Generated by ts/packages/contracts/scripts/codegen.mjs. Do not edit. +// Source artifact: solidity/out/HandleEscrow.sol/HandleEscrow.json + +import type { Abi } from 'viem' + +export const handleEscrowAbi = [ + { + "type": "constructor", + "inputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "NATIVE", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "UPGRADE_INTERFACE_VERSION", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "string", + "internalType": "string" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "acceptOwnership", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "canonicalHandle", + "inputs": [ + { + "name": "handle", + "type": "string", + "internalType": "string" + } + ], + "outputs": [ + { + "name": "", + "type": "string", + "internalType": "string" + } + ], + "stateMutability": "pure" + }, + { + "type": "function", + "name": "claim", + "inputs": [ + { + "name": "platformId", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "handle", + "type": "string", + "internalType": "string" + }, + { + "name": "token", + "type": "address", + "internalType": "address" + }, + { + "name": "recipient", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "deposit", + "inputs": [ + { + "name": "platformId", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "handle", + "type": "string", + "internalType": "string" + }, + { + "name": "token", + "type": "address", + "internalType": "address" + }, + { + "name": "amount", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "escrowed", + "inputs": [ + { + "name": "platformId", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "handle", + "type": "string", + "internalType": "string" + }, + { + "name": "token", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "escrowedAt", + "inputs": [ + { + "name": "slot", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "token", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "initialize", + "inputs": [ + { + "name": "owner_", + "type": "address", + "internalType": "address" + }, + { + "name": "names_", + "type": "address", + "internalType": "contract IIdentityNames" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "names", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "contract IIdentityNames" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "owner", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "pendingOwner", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "proxiableUUID", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "renounceOwnership", + "inputs": [], + "outputs": [], + "stateMutability": "pure" + }, + { + "type": "function", + "name": "slotOf", + "inputs": [ + { + "name": "platformId", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "handle", + "type": "string", + "internalType": "string" + } + ], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "pure" + }, + { + "type": "function", + "name": "transferOwnership", + "inputs": [ + { + "name": "newOwner", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "upgradeToAndCall", + "inputs": [ + { + "name": "newImplementation", + "type": "address", + "internalType": "address" + }, + { + "name": "data", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [], + "stateMutability": "payable" + }, + { + "type": "event", + "name": "Claimed", + "inputs": [ + { + "name": "slot", + "type": "bytes32", + "indexed": true, + "internalType": "bytes32" + }, + { + "name": "token", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "claimer", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "recipient", + "type": "address", + "indexed": false, + "internalType": "address" + }, + { + "name": "amount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "Deposited", + "inputs": [ + { + "name": "slot", + "type": "bytes32", + "indexed": true, + "internalType": "bytes32" + }, + { + "name": "token", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "depositor", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "platformId", + "type": "bytes32", + "indexed": false, + "internalType": "bytes32" + }, + { + "name": "handle", + "type": "string", + "indexed": false, + "internalType": "string" + }, + { + "name": "amount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "Forwarded", + "inputs": [ + { + "name": "slot", + "type": "bytes32", + "indexed": true, + "internalType": "bytes32" + }, + { + "name": "token", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "depositor", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "holder", + "type": "address", + "indexed": false, + "internalType": "address" + }, + { + "name": "platformId", + "type": "bytes32", + "indexed": false, + "internalType": "bytes32" + }, + { + "name": "handle", + "type": "string", + "indexed": false, + "internalType": "string" + }, + { + "name": "amount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "Initialized", + "inputs": [ + { + "name": "version", + "type": "uint64", + "indexed": false, + "internalType": "uint64" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "OwnershipTransferStarted", + "inputs": [ + { + "name": "previousOwner", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "newOwner", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "OwnershipTransferred", + "inputs": [ + { + "name": "previousOwner", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "newOwner", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "Upgraded", + "inputs": [ + { + "name": "implementation", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "error", + "name": "AddressEmptyCode", + "inputs": [ + { + "name": "target", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "error", + "name": "BadRecipient", + "inputs": [ + { + "name": "recipient", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "error", + "name": "ERC1967InvalidImplementation", + "inputs": [ + { + "name": "implementation", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "error", + "name": "ERC1967NonPayable", + "inputs": [] + }, + { + "type": "error", + "name": "FailedCall", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidInitialization", + "inputs": [] + }, + { + "type": "error", + "name": "NativeTransferFailed", + "inputs": [ + { + "name": "recipient", + "type": "address", + "internalType": "address" + }, + { + "name": "amount", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "NoNames", + "inputs": [] + }, + { + "type": "error", + "name": "NotInitializing", + "inputs": [] + }, + { + "type": "error", + "name": "NotTheHolder", + "inputs": [ + { + "name": "holder", + "type": "address", + "internalType": "address" + }, + { + "name": "caller", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "error", + "name": "NothingHeld", + "inputs": [ + { + "name": "slot", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "token", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "error", + "name": "OwnableInvalidOwner", + "inputs": [ + { + "name": "owner", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "error", + "name": "OwnableUnauthorizedAccount", + "inputs": [ + { + "name": "account", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "error", + "name": "ReentrancyGuardReentrantCall", + "inputs": [] + }, + { + "type": "error", + "name": "SafeERC20FailedOperation", + "inputs": [ + { + "name": "token", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "error", + "name": "UUPSUnauthorizedCallContext", + "inputs": [] + }, + { + "type": "error", + "name": "UUPSUnsupportedProxiableUUID", + "inputs": [ + { + "name": "slot", + "type": "bytes32", + "internalType": "bytes32" + } + ] + }, + { + "type": "error", + "name": "UnusableHandle", + "inputs": [ + { + "name": "problem", + "type": "uint8", + "internalType": "enum HandleNormalizer.Problem" + } + ] + }, + { + "type": "error", + "name": "ValueMismatch", + "inputs": [ + { + "name": "expected", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "provided", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "ZeroAmount", + "inputs": [] + } +] as const satisfies Abi diff --git a/ts/packages/contracts/src/abis/identityNames.ts b/ts/packages/contracts/src/abis/identityNames.ts index 08a88e1..83b09f1 100644 --- a/ts/packages/contracts/src/abis/identityNames.ts +++ b/ts/packages/contracts/src/abis/identityNames.ts @@ -390,6 +390,52 @@ export const identityNamesAbi = [ ], "stateMutability": "view" }, + { + "type": "function", + "name": "rulesOf", + "inputs": [ + { + "name": "platformId", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "outputs": [ + { + "name": "", + "type": "tuple", + "internalType": "struct HandleNormalizer.Rules", + "components": [ + { + "name": "maxLength", + "type": "uint16", + "internalType": "uint16" + }, + { + "name": "stripLeadingAt", + "type": "bool", + "internalType": "bool" + }, + { + "name": "isEmail", + "type": "bool", + "internalType": "bool" + }, + { + "name": "allowUnderscore", + "type": "bool", + "internalType": "bool" + }, + { + "name": "allowHyphen", + "type": "bool", + "internalType": "bool" + } + ] + } + ], + "stateMutability": "view" + }, { "type": "function", "name": "setLatestVersion", diff --git a/ts/packages/contracts/src/abis/index.ts b/ts/packages/contracts/src/abis/index.ts index 96ba585..d734754 100644 --- a/ts/packages/contracts/src/abis/index.ts +++ b/ts/packages/contracts/src/abis/index.ts @@ -16,6 +16,7 @@ export { xIdentityVerifierAbi } from './xIdentityVerifier.js' export { identityJwksRootsAbi } from './identityJwksRoots.js' export { xZkVerifierAbi } from './xZkVerifier.js' export { googleOidcVerifierAbi } from './googleOidcVerifier.js' +export { handleEscrowAbi } from './handleEscrow.js' export { libidFactoryAbi } from './libidFactory.js' export { mockErc20Abi } from './mockErc20.js' export { wtia9Abi } from './wtia9.js'